Swift5.2のcallAsFunctionの使い所

Swift5.2でcallAsFunctionという機能が追加されました。

たとえば、以下の型のexecuteを実行するにはrequest.execute()と記述します。

struct FooRequest {
    func execute() { print("execute") }
}

let request = FooRequest()
request.execute()

callAsFunctionというのはこれを以下のようにrequest()と記述して実行できるようにする機能です。

struct FooRequest {
    func execute() { print("execute") }
    func callAsFunction() { execute() }
}

let request = FooRequest()
request()

クラスや構造体を関数のように利用することが可能になり、RequestやRegisterなど操作を表すクラス・構造体でcallAsFunctionを使うとシンプルに記述することができます。

しかしexecuteを呼ぶことはあまり手間ではありません。逆にrequestが何なのかよく分からなくなる可能性もあります。ですので私はこのような利用法では積極的に活用しようと思いませんでした。

ではどのような時にcallAsFunctionを活用するとよいのでしょうか。私はまだよく研究できていませんが、以下のような場面で便利に使うことができました。その事例を紹介します。

以下のような関数を書きました。

typealias Yield<T> = (T) -> ()

func p1(_ f: (Yield<String>) -> ()) {
    let yield: Yield<String> = { print($0) }
    f(yield)
}

少し分かりにくいので、利用の仕方を示してみます。

p1 { yield in
    yield("first")
    yield("second")
//    yield(1) <- エラー
}

このように、p1関数の引数fクロージャに渡した引数yieldを実行すると何らかの処理が行われるというものです。

利用法にあるようにyieldにStringは渡せますがIntは渡せません。もしIntも処理するとなるとyieldString, yieldIntの2つをfクロージャに渡す必要がでてきます。

そこで次のように書き換えました。

struct Printer {
    func callAsFunction(_ string: String) {
        print(string)
    }
    func callAsFunction(_ num: Int) {
        print(num)
    }
}

func p2<Y>(_ yield: Y, f: (Y) -> ()) {
    f(yield)
}

これは以下のように記述することができます。

p2(Printer()) { yield in
    yield("first")
    yield("second")
    yield(2)
}

PrinterがYieldの代わりになります。p2の第一引数にPrinterを渡して、第二引数であるfにクロージャを渡します。fクロージャには第一引数(ここではPrinter)が渡されて、f内での処理が第一引数(Printer)に引き継がれます。

このように1つの関数で2つ以上の型を取り扱うためにcallAsFunctionを利用することで関数の代用ができました。

この事例では型の変化に対応しましたが、型だけでなく引数の数が変化するときにもcallAsFunctionが使えると考えられます。

以上、callAsFunctionの使い所(のひとつ)でした。

この記事が気に入ったらサポートをしてみませんか?