A generic argument list refers to the bit in angle brackets that follow a method name and precede the parenthesized argument list. These clauses specify generic type arguments and the constraints that limit their application. Swift generic specification can grow very big very quickly. And they are a pain to wrap.
The Swift Standard Library group has come up with a creative way to handle this situation, which is about as aesthetically pleasing as Soviet Brutalist Architecture:
public init< S : Sequence where S.Iterator.Element == Iterator.Element >(_ elements: S) { self.init() append(contentsOf: elements) }
The line that really kills it for me is the one with the closing angle bracket, the argument list, and the opening brace all on the same line.
As the generic argument and parameter lists grow longer, Apple’s folding style becomes less standardized. This example puts where
on its own line (not a bad thing) but breaks out the function parameter list into three lines
internal func _transcodeSomeUTF16AsUTF8< Input : Collection where Input.Iterator.Element == UInt16>( input: Input, _ startIndex: Input.Index ) -> (Input.Index, _StringCore._UTF8Chunk) { typealias _UTF8Chunk = _StringCore._UTF8Chunk ... }
The example that follows co-aligns where
but confuses me with its long parameter list, which is broken into lines of three apparently unrelated undefaulted parameters and a following line with a defaulted parameter.
static func fromCodeUnits< Input : Collection, // Sequence? Encoding : UnicodeCodec where Input.Iterator.Element == Encoding.CodeUnit >( input: Input, encoding: Encoding.Type, repairIllFormedSequences: Bool, minimumCapacity: Int = 0 ) -> (_StringBuffer?, hadError: Bool) {
Swift allows the angle bracket to be spaced away from a function name even though that’s an atypical use and I am, frankly, not a fan. For example, the following example compiles and works as you’d expect
// This will compile protocol MyProtocol {} func myFunction <T: MyProtocol> (x: T) { ... }
And you can further break down the generic argument clause onto new lines. Xcode doesn’t really like this so you have to hand-indent the components:
func myFunction < T: MyProtocol > (x: T) { print(x) }
So if you really wanted to, you could build something more like this:
internal func _transcodeSomeUTF16AsUTF8 < Input : Collection where Input.Iterator.Element == UInt16 >( input: Input, _ startIndex: Input.Index ) -> (Input.Index, _StringCore._UTF8Chunk) { typealias _UTF8Chunk = _StringCore._UTF8Chunk ... }
Whether you’d want to is another discussion. This refold certainly emphasizes the generics, parameters, and return type sections.
Moving back a little into reality world, here’s a deployed example from the Github Focus repo. In this example, extra indentation helps move the where clauses closer to the starting bracket of the argument list.
public func • <Left : LensType, Right : LensType where Left.Target == Right.Source, Left.AltTarget == Right.AltSource> (l : Left, r : Right) -> Lens<Left.Source, Left.AltSource, Right.Target, Right.AltTarget> { return l.compose(r) }
Here’s one from TJ:
While I like the intent, I really don’t like the abnormal indentation. So I pulled up an old example from my massive “notes and tests” playground to share. This one uses multiple generic arguments and constraints but fairly standard indentation. Unlike all the preceding examples, my where
keyword and constraints are spaced in further than the type arguments.
func ====< Seq1: SequenceType, Seq2: SequenceType where Seq1.Generator.Element == Seq2.Generator.Element, Seq1.Generator.Element: Equatable >(seq1: Seq1, seq2: Seq2) -> Bool { var gen2 = seq2.generate() return !seq1 .lazy .contains({ $0 != gen2.next() }) && gen2.next() == nil }
In terms of readability I think it’s pretty clear where each part of the generic story is expressed. Using the same approach, here is the refolded fromCodeUnits
example:
static func fromCodeUnits< Input : Collection, // Sequence? Encoding : UnicodeCodec where Input.Iterator.Element == Encoding.CodeUnit >( input: Input, encoding: Encoding.Type, repairIllFormedSequences: Bool, minimumCapacity: Int = 0 ) -> (_StringBuffer?, hadError: Bool) { ... }
What do you think? Are there any specific rules you follow? And do you have any folding examples to share? Let me know.
2 Comments
This last approach seems really good compared to the others. My code doesn’t usually get complex enough to require careful thought about this, but in these examples the folding is definitely important.
I’m not a fan of leaving open/close brackets/parentheses on their own line, but i’ll admit it does make indentation a lot easier.
All the way through I was saying, “no but you should just…”, or “but that doesn’t work because…” until we came to the final example – your own – where I was saying, “but that’s what I’ve been trying to tell you all this time!”
So, in short, I completely agree with you 🙂
Well… I’m not 100% sure about the >(, but I could get used to it. I don’t think there’s any good way to get between two equally indented, yet separate, sections.