Swift 5.2: Object References as Functions in Swift

Here’s a Swift language feature that you may not have noticed when it arrived in Swift 5.2. The following syntax can now be made legal:

let p = Person(firstName: "Matt", lastName: "Neuburg")
let s = p()
print(s) // Matt

Wait, what just happened? I treated an instance as a function, and it worked (in the sense that it was legal and it actually did something). This is made possible through a special method, callAsFunction. Here’s how Person is implemented:

struct Person {
    let firstName : String
    let lastName : String
    func callAsFunction() -> String { return self.firstName }
}

That’s really all there is to it: if your object type has that method, the object can be treated as a function, and a call to that function will be routed to this method. The notation is syntactic sugar for an actual call to callAsFunction:

let p = Person(firstName: "Matt", lastName: "Neuburg")
let s = p.callAsFunction() // same thing

Additional points:

  • Your callAsFunction must be an instance method; class/static methods cannot use it, probably because the calling syntax — parentheses directly following the reference — already implies a call to some form of init when the reference is the name of a type.

  • All the usual overloading rules apply, so as long as there is a way to disambiguate, you can have as many implementations of callAsFunction as you like.

  • This is an ordinary function, so labels work; contrast that to “closures” (anonymous functions), where they don’t.

Apple’s own motivating examples have to do with situations where a type is itself really nothing but a wrapper for holding and calling a function. In a case like that, the type might as well be a function, as far as the syntax is concerned. Suppose I have a struct Adder, whose job is to store a base value and add it to any addend we care to supply:

struct Adder {
    let base: Int
    init(_ base:Int) {
        self.base = base
    }
    func callAsFunction(_ addend:Int) -> Int {
        return self.base + addend
    }
}

We can use Adder like this:

let add3 = Adder(3)
let sum = add3(4)
print(sum) // 7

In the second line, we treat add3 as a function that takes an Int and returns another Int, namely the first Int with 3 added to it. The notion of a function that acts as a factory for a family of functions is common enough in Swift. But in fact add3 is not a function! It is an instance of a struct. We are treating an instance as a function.

The function notation in add3(4) is mere syntactic sugar. But it’s very nice syntactic sugar, because it reflects the truth more compactly. To be sure, we could have given Adder an add method that performs the addition, or a makeAdder method that produces a function that performs the addition:

struct Adder {
    let base: Int
    init(_ base:Int) {
        self.base = base
    }
    func add(_ addend:Int) -> Int {
        return self.base + addend
    }
    // or:
    func makeAdder() -> (Int) -> Int {
        return { addend in self.base + addend }
    }
}

Either of those would have been just fine. But the job of Adder is to act like a function, so rather than having it contain or produce that function, we can use callAsFunction to let an Adder effectively be that function.

The fact that we can distinguish calls to different callAsFunction methods purely by their parameter labels is particularly elegant. Consider a Greeter struct that supplies a friendly and an unfriendly greeting:

struct Greeter {
    let friendlyGreet : (String) -> String = { "Howdy, " + $0 }
    let unfriendlyGreet : (String) -> String = { "Go away, " + $0 }
    func greet(_ whom:String, with f:(String)->String) -> String { f(whom) }
}

And I can call it like this:

let g = Greeter()
g.greet("Matt", with:g.friendlyGreet)

That’s all very well, but in order for me to call greet with one of Greeter’s functions, I have to know the name of one of Greeter’s functions. This exposes more about Greeter than I should need to know. An elegant alternative is to expose the choice of whether to greet in a friendly or unfriendly way as the label used in a function call:

struct Greeter {
    private let friendlyGreet : (String) -> String = { "Howdy, " + $0 }
    private let unfriendlyGreet : (String) -> String = { "Go away, " + $0 }
    func callAsFunction(welcomeGreetee s:String) -> String {self.friendlyGreet(s)}
    func callAsFunction(unwelcomeGreetee s:String) -> String {self.unfriendlyGreet(s)}
}

And now I can talk like this:

let greet = Greeter()
greet(welcomeGreetee:"Matt")

Now there is effectively just one function, greet, with different parameter labels, welcomeGreetee: or unwelcomeGreetee:, expressing what I want to do. The existence of the underlying friendlyGreet and unfriendlyGreet methods, what they do, and even where they come from, is neatly concealed.

You Might Also Like…

Updating Your App to iOS 14

Each year, like clockwork, for better or worse — sometimes, in my opinion, very much for worse — Apple releases a new version of iOS. We’re all used to the forced march of the calendar by now. In June, WWDC is held, and the new version appears in beta. In September, it goes final. And …

Updating Your App to iOS 14 Read More »

    Sign Up

    Get more articles and exclusive content that takes your iOS skills to the next level.