/ SWIFT

Callable Objects and callAsFunction() in Swift

In Swift, any object that has the callAsFunction method can be called like a function. Such objects are named callable.

The function-call syntax forwards arguments to the corresponding callAsFunction method [SE-0253]:

struct WannabeFunction {
    func callAsFunction() {
        print("Hi there")
    }
}

let f = WannabeFunction()
f() // Hi there

There are three ways of invoking callAsFunction() on f:

let f = WannabeFunction()

f() // Callable sugar
f.callAsFunction() // Member function sugar
WannabeFunction.callAsFunction(f)() // No sugar

The callAsFunction() method obeys all Swift function rules. It can be declared in an extension:

struct WannabeFunction {}

extension WannabeFunction {
    func callAsFunction() { ... }
}

And overloaded:

struct WannabeFunction {
    func callAsFunction() { print(#function) }

    // Argument label overloading
    func callAsFunction(x: Int) { print(#function, x) }
    func callAsFunction(y: Int) { print(#function, y) }

    // Argument type overloading
    func callAsFunction(x: String) { print(#function, x) }

    // Generic type constraint overloading
    func callAsFunction<T>(value: T) where T: Numeric { print(#function, value) }
    func callAsFunction<T>(value: T) where T: StringProtocol { print(#function, value) }
}

let f = WannabeFunction()
f() // callAsFunction()
f(x: 1) // callAsFunction(x:) 1
f(y: 2) // callAsFunction(y:) 2
f(value: 3) // callAsFunction(value:) 3
f(value: "str") // callAsFunction(value:) str
f([1, 2, 3]) // ❌ Error: Type of expression is ambiguous without more context

And passed around:

let foo = f.callAsFunction(y:)
foo(10) // callAsFunction(y:) 10

Does callAsFunction() bring to Swift anything more than yet another syntactic sugar? Let’s find out by checking the use cases below.

Types representing functions

Types that represent functions, such as mathematical operations or machine learning computations, typically contain a single primary method, which can be conveniently sugared using callable syntax.

A prominent example is TensorFlow. The Layer protocol, which represents a neural network layer, contains the callAsFunction(_:) method requirement.

Another example is reducer from the Redux architecture. A motivating example can be found in the Composable Architecture.

Parsers, calculators, shell commands fall into this category.

Passing generic functions

Referencing and passing generic functions would not have been possible without callable syntax. A compiler error pops up if we try to reference a generic function:

func bar<T>(_ x: T) { print(x) }
let f = bar<Int>() // ❌ Error: Cannot explicitly specialize a generic function

However, we can achieve this using callAsFunction():

struct Bar<T> {
    var f: (T) -> Void
    func callAsFunction(_ x: T) { f(x) }
}

let f = Bar<Int> { print($0) }
f(1)

Although f is factually a callable object, it can be considered a generic function at a point of use.

Function identity

Two functions are considered equal if for each input they produce the same output. Given that some categories are infinite, the only possible way to achieve this is by wrapping functions into a nominal type.

The next use case shows how we can add Identifiable, Hashable, and Equatable conformance to a function by wrapping it into a nominal type. The method callAsFuntion() allows us to invoke the wrapper using a function-like syntax:

struct Function<Input, Output>: Identifiable {
    let id: UUID
    let f: (Input) -> Output

    init(id: UUID = UUID(), f: @escaping (Input) -> Output) {
        self.id = id
        self.f = f
    }

    func callAsFunction(_ input: Input) -> Output {
        f(input)
    }
}

extension Function: Equatable {
    static func == (lhs: Function<Input, Output>, rhs: Function<Input, Output>) -> Bool {
        lhs.id == rhs.id
    }
}

extension Function: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

Consider Swift key paths as an alternative to the above technique as they already conform to Hashable and Equatable.

Bound functions

Partial application and bound closures can be modeled using callable syntax.

Partial application is a functional programming technique that means binding some arguments to a function without fully evaluating it.

Here is a curried add() function which is then partially applied:

func add(_ x: Int) -> (_ y: Int) -> Int {
    { y in return x + y }
}

let addTwo = add(2)
print(addTwo(3)) // 5

I am touching on the subject in Functions in Swift.

The same technique can be modeled using callable syntax:

// 1.
struct Sum {
    var x: Int
    func callAsFunction(_ y: Int) -> Int { x + y }
}

// 2.
let addTwo = Sum(x: 2)
print(addTwo(3)) // 5

Here are the key highlights:

  1. Declare type Sum, which reprents the arithmetic addition operation.
  2. The argument 2 is stored in the struct instance, which essentially bounds 2 to the method callAsFunction().

Delegation

The auto-weak delegate pattern, which I’ve learned from @olegdreyman, uses callAsFunction() to improve readability at a call site:

class Delegate<Input, Output> {
    init() {}

    private var block: ((Input) -> Output?)?
    func delegate<T: AnyObject>(on target: T, block: ((T, Input) -> Output)?) {
        self.block = { [weak target] input in
            guard let target = target else { return nil }
            return block?(target, input)
        }
    }

    func call(_ input: Input) -> Output? {
        return block?(input)
    }

    func callAsFunction(_ input: Input) -> Output? {
        return call(input)
    }
}

Say, there is a DataLoader which fetches data and notifies when it’s done:

class DataLoader {
    let onLoad = Delegate<(), Void>()
    func loadData() {
        onLoad(())
    }
}

Then, in the onLoad callback, we receive weakified self, and there is no need to write [weak self] every time:

class ViewController: UIViewController {
    let loader = DataLoader()

    override func viewDidLoad() {
        super.viewDidLoad()
        loader.loadData()
        loader.onLoad.delegate(on: self) { (self, _) in
            self.setupLoadedUI()
        }
    }

    func setupLoadedUI() {}
}

See the original Oleg’s post here for more details on the pattern.

Wrapping Up

Apart from the subjective improvements that callable syntactic sugar brings into the Swift language, callAsFunction() has two real benefits which are not achievable by any other means. These are:

  • passing generic function, and
  • functions identity.

I also believe that the types-representing-functions, bound functions, and auto-weak delegation pattern will find their application in many modern codebases.


Thanks for reading!

If you enjoyed this post, be sure to follow me on Twitter to keep up with the new content. There I write daily on iOS development, programming, and Swift.

Vadim Bulavin

Creator of Yet Another Swift Blog. Coding for fun since 2008, for food since 2012.

Follow