How Learning Ruby Helped Me Understand Swift
I have a confession to make. Until recently, closure syntax confused me to no end. Specifically, I’m talking about Swift closures, but it also applies to other languages. I read Swift’s language specification, Paul Hudson’s guide on closures, and Stack Overflow posts. I even found this appropriately named website https://fuckingclosuresyntax.com/. If there’s a website about it, I knew I couldn’t be the only one struggling. I didn’t see why I couldn’t always use my for i in ...
iterator when I needed to. Nothing made sense. Until I learned Ruby.
What are Closures?
Closures are just enclosed functions with access to fields outside their definition. They have the additional benefit of being able to store references to variables so you can use them even after the original executing block exits. This sounds confusing at first. Most programmers are familiar with variables exiting scope when a function completes. When a function uses a closure, the closure can still reference variables after the function has terminated. This is where closures get their names - they close over any references from the context they were defined. Quinn “The Eskimo” does a better job explaining it than I do.
A very simple example of this in Ruby:
item = [1, 3, 3, 7]
item.each_with_index do | item, index |
puts("#{index}: #{item}")
end
# Prints the following:
0: 1
1: 3
2: 3
3: 7
# What about item?
print(item)
# Still holds the array we declared.
[1, 3, 3, 7]
This type of closure is called a block. The block created by item.each_with_index
creates a new scope within the body and closes over the references we introduced to it, each element in the list with the variable item
and the index with an appropriately similar name. How did this help me understand Swift closure syntax? I don’t know. To me this is just a far more approachable representation. It’s simple and elegant, in my opinion.
Swift Syntax
In Swift 5, there are just so many ways to create a closure. I think that started my confusion. If we want to sum up all of the values in an Array
of Int
s we can do something like this (stealing this again from Quinn “The Eskimo”):
let sum = items.reduce(0, { (total:Int, value:Int) -> Int in
total + value
})
In this snippet, we’re creating the constant sum
by iterating over each element with the reduce
method. reduce
takes two parameters, the first being the starting value of the accumulator (0
). The second is a closure that will be executed on each element in the array. reduce
passes in two parameters to the closure, the accumulator (total
), and the current element (value
). It returns an Int
. All the closure does is return the accumulator + the current element in total + value
with an implicit return. Sure, makes sense.
But we can also do trailing closure syntax and move the closure outside of the parenthesis of the reduce
. This may improve readability in some circumstances. The compiler is intelligent enough to know that reduce
takes two parameters and since the last parameter is a closure, it can trail behind the method parameters like so:
let sum = items.reduce(0) { (total:Int, value:Int) -> Int in
total + value
}
I’m not a fan of this syntax, though it is highly encouraged in the Swift community. If a function takes two arguments, I want to give it two arguments and not have to understand the inner workings of the compiler to understand some syntactic sugar.
But we can go further. The compiler already knows the types of elements in the array (and the type in the accumulator). So we can strip away even more and remove the type declarations and the parenthesis around the parameters to the closure.
let sum = items.reduce(0) { total, value in
total + value
}
We can even get rid of the parameters altogether and refer to them by their shorthand syntax. $0
represents the accumulator, and $1
represents the element.
let sum = items.reduce(0) {
$0 + $1
}
For the final piece of magic, we’re required to plug the closure back in as a parameter inside the parenthesis. The compiler can intelligently deduce the parameters and just apply the method (+
) inside the body of the closure. So we could write something like:
let sum = items.reduce(0, +)
Maybe I’m just too junior of a Swift programmer to consider writing most of the latter examples into production. I like explicitness. I like when I can look at the code and realize what’s going on. I like type declarations. Sure, the last example is super-readable. If you understand what’s going on. But you might have to read (or write!) a whole blog article to understand why it works.
Conclusion
I remember sitting by the TV when Apple announced it at WWDC 2014. I don’t use it professionally, so I am far from an expert in the language. I love Swift. It has so many features which I really like. It’s compiled but still has an interactive repl. It’s statically typed. It’s memory safe. And I absolutely love argument labels being separate from parameter names.
The Programmatic Programmer, the Bible for many programmers, preaches that developers should branch out to other languages in lieu of clinging to just one. The reason is that learning different languages allows you to think about problems in different ways. Perhaps one language puts more emphasis on something than another. Ruby seems heavy on blocks, and I immediately took to their syntax. It just made sense. Maybe because I was picking up Ruby as a newcomer and didn’t have years of syntactic baggage. Who knows. Either way, it helped me understand the application of closures in Swift. Though, I might keep to the explicit, long syntax for a while.