SE-0250: Swift Code Style Guidelines and Formatter

What is your evaluation of the proposal?

+1 on the idea of having an official formatter; +1 on the idea of having a default style; -1 on having an "official" style (this distinction is subtle but very important) and -1 on this proposal because it leaves me with more questions than answers and more promises than solutions.

In short, -1.

In my opinion, the proposal focuses on supporting "one true style to rule them all" and omits the importance of extensive configurability (which is mentioned just once in the whole thing).

Is the problem being addressed significant enough to warrant a change to Swift?

No, it's not, for four reasons.

1. I don't see meaningful discussions about code style that often during code review (both in open-source and closed-source projects). If there are discussions, they are quickly resolved by author either adjusting to the project style or justifying the custom style they used.

2. Swift has a well-established and well-designed grammar. Modifying coding mindset to match the project style is not a very hard thing to do.

3. Tools like SwiftLint are more than good enough solution for linting and formatting.

4. In practice, code style emerges from the architectural decisions and frameworks used. A project based on FRP framework (such as ReactiveCocoa) wouldn't and shouldn't use the same style as a project based on vanilla UIKit.

Does this proposal fit well with the feel and direction of Swift?

No, it doesn't, for two reasons.

1. Up until this proposal, Swift was marketed as an easy to learn language and expressive like no other language before.

Introducing an official style would render those two features obsolete. I don't accept the argument of it being "optional". In my opinion, thinking that there would be no pressure to adopt the official style in as many projects as possible is naive.

If this proposal is accepted, I can already see recruiters refusing to hire a developer because they don't follow the official Swift style. I can see teachers giving lower marks because there should be no space before the colon. I can see teams agreeing to ditch using a custom style in a well-argued case just because it would deviate too much from the official one.

2. This proposal is a poor proposal, in general. It doesn't present any concrete solutions to the problems it lists, especially regarding configurability. And it doesn't come with a pull request.

If you have used other languages with a similar feature, how do you feel that this proposal compares to those?

I used many tools which do linking or formatting or both: SwiftLint, ESLInt, StandardJS, Rubocop, PyLint. All those tools (except StandardJS) have one thing in common: they're configurable like crazy. This proposal doesn't remotely match that.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the proposal and read the pitch thread until it blew up and was impossible to follow anymore.

5 Likes

I'd like to bring attention to @minikin's insightful comment that got lost amongst the noise:

I would say a Swift version of dartfmt is exactly what I'd wish to see happen.

I especially like this passage in the formatting section of the Dart style guide:

Formatting is tedious work and is particularly time-consuming during refactoring. Fortunately, you don’t have to worry about it. We provide a sophisticated automated code formatter called dartfmt that does do it for you. We have some documentation on the rules it applies, but the official whitespace-handling rules for Dart are whatever dartfmt produces .

The remaining formatting guidelines are for the few things dartfmt cannot fix for you.

I hope we'd be able to adopt similar wording in the future.

As a Swift programmer, I only want to deal with beautifully formatted code. At the same time, I do not wish to waste any time on manually formatting code to fit into the intricate coding style that specifies how to achieve such beauty.

I want a tool that automates all aspects of code formatting.

18 Likes

+1000 to @lorentey's comment about dartfmt .

1 Like

Regarding Dart.

When you use Dart with Flutter you will have two effects. a) Hey, nice there is a tool in my IDE that just does this for me. Great lets focus on the code. b) WTF. why cant i format this huge blob of a hierarchy of components into more readable chunks so it not only satisfies the machine but also me (<insert reference about how code is 10x more read then written here>). Well, too bad i will have to live with it, Sigh.

8 Likes

Who wouldn’t agree with this? But of course beauty is in the eye of the beholder. That’s the catch. There are good reasons for people working in different contexts to have different preferences. This is especially true with regards to idioms that adopy specific formatting rules which are not applicable outside the idiom. No guidelines or formatter can handle all of these idioms gracefully because many of them have yet to be invented!

7 Likes

This is exactly the kind of issue I have been concerned about all along. If a user is fighting the formatter to breaking code into readable chunks because the formatter is not familiar with the idiom in use there is a significant problem with the tool, not the code.

A formatter should be usable with any idiom a programmer might have good reason to adopt (including new and innovative ones). This is especially the case when discussing the default behavior of a tool that ships with the language. I think any style community-wide style guidelines should be very wary of leaving room for unanticipated idioms (often very useful ones). A consequence of this is that we must whole-heartedly embrace the notion that there is no one style to rule them all.

2 Likes

There is a wide gulf between recognizing "no one style" and leaving room for an innumerable number of styles stretching to even "unanticipated" ones "yet to be invented."

Swift was designed, as the core team has said, to be an opinionated language, with a center of gravity in its design, and with a goal specifically to avoid having multiple "dialects" of the language. The tools that ship with the language should reflect that philosophy.

12 Likes

Thank you! Finally we have a (somewhat) concrete example where the standard formatter gets in the way, rather than being helpful.

I don't know anything about Flutter, but, looking at the sample code they provide, it looks like it's a DSL for building things out of nested function calls, initializers and closure expressions.

  • What exactly is wrong with dartfmt's formatting? (I.e., are they using a suboptimal typesetting algorithm, or is it a problem with the particular aesthetic style they chose to implement?)

  • Do you think Flutter’s expressions would be impossible to automatically typeset? Why?

  • Would you honestly prefer to manually format large expressions like Flutter’s deeply nested constructions?

Personally, I don't see a reason why it wouldn't be possible to format these expressions automatically. On the other hand, I see plenty of reasons why I wouldn’t want to manually format these -- these are exactly the sort of expressions that are annoying to typeset. I definitely would not want to laborously fix up indentation and line wrapping issues whenever I slightly change a tiny bit of a huge superstructure.

Although I don't know Flutter, I have some meaningful experience with DSLs like these -- in fact, in a previous life I worked on code that automatically pretty-prints deeply nested code like this. The underlying aesthetic principles are surprisingly(?) similar to human text; in fact, I had the best results with a straightforward adaptation of Knuth & Plass (1981).

3 Likes

I’m curious: how do you solve this exact issue right now?

It’s true that there are major readability issues with deeply nested syntactic constructs of any kind. (Expressions, statements, types, whatever.)

Consistent formatting can help mitigate these problems to an extent, so to me an automated formatter would clearly be helpful.

I don’t see how you can argue that bad code can be made better by judicious use of inconsistent formatting.

4 Likes

I’m not sure I understand the question precisely. In general I don’t choose to adopt tools which prevent me from writing the code I wish to write. I do fight with Xcode’s auto-formatting behavior from time to time. I find that annoying but it’s not a huge deal and its easy enough to tweak the code to get it formatted as necessary.

I have written code using various kinds of DSL-like syntax. When working on a new library it often takes some experimentation to identify the clearest way to format the syntax. Sometimes the result is idiosyncratic, but perfectly appropriate in context.

If this was universally true I don’t think markup languages would be as successful as they have been. I don’t think nesting is an issue when the syntax mirrors a tree structure in the domain, especially when the context is a declarative DSL. It is important to choose a layout that is appropriate to the context though.

I agree, if the formatter understands the context. If the formatter does not understand the context it will attempt to apply a set of rules that may be appropriate in general but are inappropriate in a specific context.

I am certainly not arguing this at all. I’m not sure what led you to think that I am. My argument is that style guidelines are necessarily context-dependent. Humans are able to adapt to various contexts often without even thinking about it (i.e. when writing multi-line method chains). This is much more difficult for tools, especially when you recognize that it is impossible to enumerate the idioms that will be used because new ones are invented on a not infrequent basis.

There are certainly some rules (such as left-colon-hugging) that can be applied universally. But placement and handling of line breaks and indentation are quite a bit more nuanced.

Are you aware of any formatters that exist today (for any programming language) that automatically understand context involving syntactic constructs where the particular usages differ solely by whitespace but are otherwise syntactically equivalent?

What's important isn't whether the tool understands context, but instead whether it can preserve certain kinds of context. The difference here is important; what you're asking for with the word "understanding" is for the tool to know the answer to the question "what choices can I make here and how do I choose one", when a more tractable position is for it to recognize certain formatting choices and say "the user made this choice and I recognize it as a valid one".

That goes back to the example cited in the pitch thread, where a user talked about a "function call" that can be a representation of an imperative subroutine invocation, or a markup-like DSL, or something else. Syntactically the same, but in terms of formatting, those might look quite different:

// imperative
let result = someFunction(
  someNestedFunction(oneValue, anotherValue))

// DSL
let result = someFunction(
  someNestedFunction(
    oneValue,
    anotherValue
  )
)

A formatter can recognize both of those as valid choices (for example, by respecting newlines in appropriate locations placed by the user, as swift-format does), and that's quite sufficient. It needn't be able to take the statement above written on a single line and automatically put it into the correct format, because syntactically there is not enough context for it to make that choice. It need only preserve the user's explicit intent. But it can still do that and normalize any other inconsistencies (for example, incorrect amounts of indentation within that structure) and apply fixes to any other style violations.

3 Likes
//ignore-format
let result = someFunction( 
someNestedFunction( 
oneValue,
 anotherValue ) )

can we have our cake and eat it too?

No and that is a large part of my point.

Sure, if a formatter is able to recognize user choice and leave it alone I would be fine with that. In some sense my argument is that a tool should do this by default (so it is compatible with idioms it cannot recognize and / or was not designed for). At least this is one way for a tool to be compatible with a wide range of idioms and avoid users having to fight with or configure the tool.

Of course a formatter that does this is going to produce significantly lower stylistic cohesion than a tool which aggressively reformats code. For example, I think it will be difficult to distinguish user intent from sloppy line breaks and indentation that really “should” be corrected, thus leaving some sloppiness in a codebase where it exists prior to formatting. This is going to leave a lot of users unhappy, especially those who want to be able to stop thinking about formatting altogether and have it be 100% automated. I think that’s ok - it’s certainly better than having a formatter which by default is incompatible with some common (as well as yet-to-be-invented) idioms.

I agree that this is probably the only viable approach. What I am arguing is that this preservation must be prioritized, on by default, and its performance evaluated against as wide a range of idioms as possible. Any rules that conflict with this preservation should not be enabled by default. This necessarily limits what the formatter is able to do by default.

Secondary to the tooling question, this also raises the question of how to handle style guidelines which should not be universally applied, instead leaving room for idiomatic user intent around line breaks and indentation. Ideally (IMO) the guidelines would recognize and embrace the presence of a range of idioms and state up front that any layout recommendations are necessarily specific to an idiom / context. Guidelines would probably still focus primarily or exclusively on the prevalent style used in “normal” Swift code - that’s fine as well as long as they explicitly leave room for other idioms.

5 Likes

I don't think the above is viable. A formatting tool should prioritize consistency across code bases (it's whole purpose ? ). If I want my formatting tool to implicitly read my code and infer personal preference then I probably a) should make that preference explicit (some config) so I can communicate with my team my intent or b) turn off the formatting globally, by file, or locally by some sort of commenting system like I pointed up thread or c) choose a different formatting tool?

1 Like

I am not talking about personal preferences here. What I am talking about is idioms that require idiosyncratic formatting, such as the declarative and fluent idioms demonstrated upthread.

I very strongly believe that programmers should not need to modify the default configuration of the formatter just because they are programming in an idiom such as these. If Swift is going to have an "official" formatter its default configuration should be compatible with idioms that many Swift programmers use, and ideally compatible with idioms invented in the future. The point I am making is that this is in tension with the goal of absolute consistency and indeed limits the scope of rules that may be applied by default.

4 Likes

May I contribute a concrete example that occurred during the development of SwiftLint?

Users requested a rule about spacing line comments and a pull request was submitted to implement it. The rule was basically that line comments should be spaced like this:

statement() // some comment

I assume we can all agree that the following look sloppy and the deviation is not likely a conscious decision:

statement()//some comment
statement() //some comment
statement() //  some comment

But when the rule was tested against real code, examples of meaningful deviation were quickly discovered, including forms like the following:

enum Stuff {
    case short                           // Notes...
    case mediumLength                    // More notes...
    case somethingWhichIsMuchLongerStill // Still more notes...
}
//   ∞         n + 1     2n − 1
//   ∑    ( (−1)      __θ________ )
// n = 1               (2n − 1)!

The pull request was returned for revision to account for these. Direction was given on how to adjust implementation, but the pull request authors opted not to invest the effort.

The points that can be learned from it are the following:

  1. There is such thing as meaningful deviation. (Though I personally don’t think it should be interpreted quite as broadly as some here seem to think on either side of the discussion; I do not consider this to apply to things like brace positioning.)

  2. The existence of meaningful deviation does not invalidate the usefulness of rule. Even the code bases containing those exceptions would have benefitted from having the other 99% of their comments formatted consistently.

  3. As long as the meaningful deviation is identified by the tool developers, they can usually find a reasonable implementation that recognizes the difference between thoughtful exceptions and sloppiness.


I do agree with @masters3d that there are some kinds of variation better handled other ways, (though again, I do not interpret that as broadly as others here):

  • Configuration: Project A wants English identifiers. Project B wants Chinese identifiers. (But in my opinion colon spacing is not a candidate for configuration.)
  • Path level exceptions: Generated files (like package test manifests) in the event their generators are not up to date with the current guidelines.
  • A directive based escape hatch:
    // @noformat
    doStuff()
    // @format
    
    While in theory I believe a sufficiently well designed tool should never need this, the reality is there will always be minor bugs and oversights in any release. The ability to use directives like this enables the tool to still be used on the rest of the project without breaking the problem spots where the issue manifests.
5 Likes

This is a great example of meaningful deviation. A related example is column alignment in switch statements which I use when there is a single statement in each case and line length is acceptable.

switch {
case .first(let x):  return x.value
case .second(let y): return y.value
}

I find the effort to maintain this is very much worth the gain in readability. An official formatter must respect choices like this. If it is able to recognize and automate the idiom even better! On the other hand, if it were to increase the effort required to use this style I would find it unacceptable.

These kinds of idiomatic style decisions are much different than things like colon hugging where there is no meaningful reason to deviate and which should be applied universally and consistently throughout a codebase.

6 Likes

Is should add the caveat that every time I say “such‐an‐such a rule has no meaningful exceptions,” what I really mean is, “I have never seen a meaningful exception yet.” Anyone is welcome to prove me wrong by demonstrating with an example. (Though it must exhibit clear and strong reasoning; it cannot just be a difference out of habit.)

I think the tool developers should maintain the same attitude as they do their work and the tool evolves. Do you agree @allevato?

4 Likes

Absolutely, no question. It's impossible to predict what requirements might arise in the future, so the idea of building a tool that is static and never considering new patterns or choices that might emerge is neither realistic nor desirable.

3 Likes

Understood. I highly doubt that the formater you described will be delivered by the swift project. The good thing is that nobody is forcing anybody to use this formater. Honestly I am probably going to keep contributing to SwiftLint and make it what I want. Look at the swift package manager. Most people use other package managers. Same thing we got going on here. If anything a swift-format tool could just help on making a configuration file that other tools could adopt.

1 Like