Swift: Call by name
A blast from the past
Back when computers came with some assembly required and real men/women booted by toggling switches on the front panel, a language called Algol introduced many programmers to call-by-name parameter passing.
For many of today's languages, parameters to a function call are evaluated in the caller's context then copied to the callee. That's call-by-value because the only thing the callee gets is a copy of the value; any changes are local to the callee only.
That's also a bit restricting, so many languages offer call-by-reference. Sometimes you get nice syntax for it like Swift's inout
or C#'s ref
. Other times you pass pointers by value like in C. The pointer itself is just a copied value, but the address inside the pointer can be written to, making the changes visible to the caller. The semantics can vary and sometimes different languages confuse the terms, but this is generally how it works.
What many people aren't familiar with is call-by-name. With call-by-name, the parameters aren't evaluated at call time, they are substituted into the callee instead. Logically they are still evaluated in the caller's context, but effectively it was as if there were no function call boundary as far as those parameters are concerned; this is a subtle but important difference because it means if the parameter is used multiple times in the callee it will be re-evaluated each time.
Let's look at an example. Assume Swift had a byname
keyword similar to inout
.
func Caller() {
var vec = [1,2,3,4,5]
var index = 0
Callee(vec.count, vec[index++])
}
func Callee(count: Int, byname item:Int) {
for var i = 0; i < count; i++ {
println("Hello #\(item)")
}
}
The key here is that each iteration of the loop in Callee will re-evaluate item
in Caller's context, meaning each time index
will be incremented and item
will be a different value. If you're like me and mostly have experience with the Basics and C-based languages, this should strike you as absolutely insane (but also really cool in certain contexts).
With great power comes great responsibility and it seems that language designers largely abandoned call-by-name as being too much power in the hands of horribly irresponsible developers.
Swift gives us back that power because it does in fact have byname
, only Swift spells it @auto_closure
.
func Caller() {
var vec = [1,2,3,4,5]
var index = 0
Callee(vec.count, vec[index++])
}
func Callee(count: Int, item : @auto_closure ()->Int) {
for var i = 0; i < count; i++ {
println("Hello #\(item())")
}
}
We put the @auto_closure
attribute in a different location and turn the parameter into a function that takes no parameters and returns Int. That means we need to invoke item
, but otherwise the Caller is unmodified.
@auto_closure
tells the Swift compiler that if an expression is supplied for this parameter then automatically wrap it in a closure for the caller. It only works if the Callee's parameter is a function taking no parameters (otherwise the compiler wouldn't know what to fill in for the closure's parameters).
Compared to true language support for call-by-name, we're exposed to a bit of the guts of the implementation here but this is close enough to how Algol and other languages implement the feature anyway, using a thunk or jump to evaluate the expression in the caller's stack frame.
Enjoy!
This blog represents my own personal opinion and is not endorsed by my employer.