Type Inference
Swift's type checker is very good at inferring types — a blessing like no other when golfing — but sometimes it needs a little help. Consider this code (borrowed from one of my own answers):
let function = { (decoy: String, target: String) in
let array = ([decoy] + { [String].init }()(target, 511)).shuffled()
// ...
}
(For context, this creates a 512-element array where all but one of the elements are identical.)
Thanks to closure tricks, this is already pretty golfy once whitespace is removed and names are shortened:
let f={(d:String,t:String)in let a=([d]+{[String].init}()(t,511)).shuffled()
/* ... */}
But there are three words that stick out like sore thumbs, and they all start with an S
and end with a tring
. Thankfully, we can get rid of all three by combining two more tricks.
Type Placeholders (Swift 5.6 and newer)
If the type checker can't infer an entire type on its own, but it can infer some of its type parameters (i.e. things like the T
in SomeType<T>
), then you can replace those parameters with an underscore to tell the type checker to figure them out for you. For instance, Array<String>
can become Array<_>
(as long as something else makes it clear that the type parameter is a String
).
Of course, this also applies to the syntactic sugar versions of Array<Element>
, Dictionary<Key, Value>
, and Optional<Wrapped>
, as well as tuple types, function types, and anything else that counts as a type. So [String]
can be replaced with [_]
in much the same manner.
Taking advantage of this, we can save 5 bytes in our example:
let f={(d:String,t:String)in let a=([d]+{[_].init}()(t,511)).shuffled()
/* ... */}
Now we've got the other two to deal with. We can't use the type placeholder trick with these, since the type checker will then struggle with just about everything. So we've got to hint at these types another way...
Appending Literals
Let's start with an absolutely horrible example that still illustrates the point well:
let p={(n:Int)in print(n)}
Let's say you want to get rid of the bulky Int
type signature. You can't just outright remove it, because then the type checker won't have any way of knowing that n
is an Int
. So you need to give it some other, shorter way of figuring the type out.
One way to do this is by doing something with the value that doesn't affect how the program runs, but still makes it clear what the type is. In this instance, you can simply add 0
to n
:
let p={n in print(n+0)}
Since we no longer have to declare any explicit types, we can do away with the closure header altogether and use implicit closure parameters instead:
let p={print($0+0)}
Note, however, that this only saves bytes if you only use each parameter once or twice, and that implicit parameters can't be used inside of other closures.
Here's a table of literals you can use in this manner:
Type |
Type Hint |
Array |
$0+[] |
Dictionary |
$0+[:] |
String |
$0+"" |
Int |
$0+0 |
Double |
$0+0.0 |
Using these, our first example can be reduced to:
let f={d,t in let a=([d+""]+{[_].init}()(t,511)).shuffled()
/* ... */}
t
can be inferred on its own without a problem here, so we don't need to append an empty literal to it.
With implicit closure parameters, we can make this even smaller:
let f={let a=([$0+""]+{[_].init}()($1,511)).shuffled()
/* ... */}