Swift Dynamically Callable Types

Apr 20, 2023#swift

In Swift, dynamically callable types using @dynamicCallable attribute is a feature of Swift 5 that allows you to mark a type as being directly callable. This means you can use a simpler syntax to invoke methods on that type, instead of using dot notation and parentheses.

// For example, instead of writing this:
let greeter = Greeter()
greeter.greet(name: "Nick") // Prints "Hi, Nick."

// You can write this:
let greeter = Greeter()
greeter(name: "Nick") // Prints "Hi, Nick."

This feature is useful for interoperability with dynamic languages such as Python and JavaScript, where you can use natural syntax to call methods from those languages in Swift. It can also make your code more concise and expressive when working with types that have many methods or overloads.

To use @dynamicCallable, you need to implement one or both of following methods in your types (struct, classe, enum, and protocol):

  1. Method dynamicallyCall(withArguments:) is called when you use the object like a function. It receives an array of arguments and should return a value.
@dynamicCallable
struct MyCallable {
    func dynamicallyCall(withArguments args: [Int]) -> Int {
        return args.reduce(0, +)
    }
}

let callable = MyCallable()

let result = callable(1, 2, 3, 4) 
// equivalent to callable.dynamicallyCall(withArguments: [1, 2, 3, 4])

print(result) // prints 10
  1. Method dynamicallyCall(withKeywordArguments:) is called when you use the object like a function with named arguments. It receives a dictionary of arguments and should return a value.
@dynamicCallable
struct MyCallable {
    func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Int {
        return args.reduce(0) { $0 + $1.value }
    }
}

let callable = MyCallable()

let result = callable(a: 1, b: 2, c: 3, d: 4) 
// equivalent to callable.dynamicallyCall(withKeywordArguments: ["a": 1, "b": 2, "c": 3, "d": 4])

print(result) // prints 10

You can call an instance of a dynamically callable type as if it’s a function that takes any number of arguments. If you implement both dynamicallyCall methods, dynamicallyCall(withKeywordArguments:) is called when the method call includes keyword arguments. In all other cases, dynamicallyCall(withArguments:) is called.