Swiftのコールバックを閉じ込めるデータ構造

# はじめに

非同期プログラミングを行う時など、ある関数にコールバック関数（クロージャー）を渡していく手法がSwiftの界隈では広く行われている。ただ、コールバック関数の中でさらにコールバック関数を受け取るような処理を行うと、すぐにコールバック地獄と呼ばれるコールバック関数が大量にネストする状態になって、プログラムの見通しが悪くなってしまう恐れがある。そこで、この記事では`Cont`と呼ばれるデータ構造を紹介し、それによってコールバック関数をデータ構造に隠蔽する方法について考える。この記事で紹介するソースコードは次のリポジトリに置かれている。

- https://github.com/y-yu/Cont-in-Swift

この記事を読んで何か疑問に思った点や改善点などがあれば、気軽にコメントなどで教えて欲しい。

# コールバック関数を使った足し算

コールバック関数を受け取るような関数は、主にネットワーク通信が発生する非同期処理で発生しがちだが、ここでは話を単純にするため、非常に簡単な「足し算」を用いてコールバック処理を考える。
まず、整数の足し算をする関数`plus`を考えると、次のようになる。

```swift:Plus.swift
func plus(_ x: Int, _ y: Int) -> Int {
    return x + y
}
```

関数`plus`を用いて`1 + 2 + 3`を実行するには次のようにする。

```swift:main.swift
plus(1, plus(2, 3))
```

この実行結果は次のようになる。

```
6
```

さて、この関数`plus`は見て分かるように2回呼び出されている。そこで関数`plus`を改造してコールバックを受け取るようにしたらどうなるだろうか。

```swift:Plus.swift
func plus_k(_ x: Int, _ y: Int, _ k: (Int) -> Int) -> Int {
    return k(x + y)
}
```

新たに追加した引数`k`がコールバックである。`x`と`y`の足し算結果を、コールバック処理に渡して、最終的にコールバック関数の返り値を関数の返り値としている。この関数`plus_k`を用いて$1 + 2 + 3$を実行すると次のようになる。

```swift:main.swift
plus_k(1, 2, { r in r + 3 })
```

これを実行すると正しく$6$が計算されることが分かる。ところで、足し算を実装しようとしているので、`{ r in r + 3 }`に含まれる`r + 3`の部分も関数`plus_k`を用いて計算したい。ところがこの後にはコールバック処理が何もない。そこで、受け取った値をそのまま返す関数`{ x in x }`を使って次のように書き換える。

```swift:main.swift
plus_k(1, 2, { r in plus_k(r, 3, { x in x }) })
```

このように、何もしないコールバック関数`{ x in x }`を使って、`plus_k`のみで足し算を表現することができると分った。さて、では$1 + 2 + 3 + 4 + 5$を見ると、次のようになる。

```swift:main.swift
plus_k(1, 2, { x in
    plus_k(x, 3, { y in
        plus_k(y, 4, { z in
            plus_k(z, 5, { a in
                a
            })
        })
    })
})
```

このようにすれば正しく計算はできるものの、コールバック地獄になることが分かる。このように、簡単な例であってもコールバック関数を取る関数を繋げていくと、簡単にコールバックだらけになる。

# コールバックを表すデータ構造`Cont`

さて、このようにコールバックを取る関数を使うと容易にネストが深くなるので、このコールバックを表すようなデータ構造を用いることにする。コールバックを閉じ込めるデータ構造`Cont`は次のような定義になる。定義している関数`map`や`flatMap`については例で解説するので、この定義で詳細に追う必要はない。

```swift:Cont.swift
struct Cont<R, A> {
    let run: ((A) -> R) -> R
    
    init(_ f: @escaping ((A) -> R) -> R) {
        self.run = f
    }
    
    func flatMap<B>(_ f: @escaping (A) -> Cont<R, B>) -> Cont<R, B> {
        return Cont<R, B> {
            k in self.run { a in f(a).run(k) }
        }
    }
    
    func map<B>(_ f: @escaping (A) -> B) -> Cont<R, B> {
        return Cont<R, B> {
            k in self.run { a in k(f(a)) }
        }
    }
}
```

まず、`Cont<R, A>`の型パラメータ`R`と`A`は何を表しているのかを説明する。型パラメータ`A`はコールバックの引数の型であり、型パラメータ`R`はコールバックの結果の型である。すると、関数`run`は型`(A -> R) -> R`であり、そのままコールバックであると言える。

# `Cont`を用いた足し算の関数

さきほど定義したコールバック関数を使う足し算の関数`plus_k`は次のようになっていた。

```swift:Plus.swift
func plus_k(_ x: Int, _ y: Int, _ k: (Int) -> Int) -> Int {
    return k(x + y)
}
```

`Cont`を用いたバージョンを作るまえに、少し関数`plus_k`について考える。まずコールバック関数の引数は現在の足し算の結果であるから、`Int`であるし、$1 + 2 + 3 + \dots $と後続の足し算がコールバック関数であるので結果の型は`Int`であった。従って次のようになる。

| コールバックの引数の型 | コールバックの結果の型 |
|:--------------------:|:--------------------:|
| `Int`                | `Int`                |

つまり、この場合`Cont`の型パラメータ`R`と`A`はいずれも`Int`となることが分かる。`Cont`を用いた足し算の関数は次のようになる。

```swift:Plus.swift
func plus_cont(_ x: Int, _ y: Int) -> Cont<Int, Int> {
    return Cont { k in k(x + y) }
}
```

次のように実行できる。`Cont`を実行して結果を得るときには、関数`plus_k`と同様に`{ x in x }`を用いる。

```swift:main.swift
plus_cont(1, 2).run { x in x }
```

実行結果は次のようになる。

```
3
```

$1 + 2 + 3$のように後続の処理を繋げる場合は、`flatMap`関数を使って次のように書けばよい。

```swift:main.swift
plus_cont(1, 2).flatMap { x in plus_cont(x, 3) }.run { x in x }
```

この結果は次のようになる。

```
6
```

正しく計算ができることが分かる。ただ、これでは`flatMap`になにかの関数が入っているだけのように見え、結局複雑な計算を行うとネストが深くなりそうである。そこで、まずは`plus_cont`をカリー化して次のような関数`plus_cont_curry`を作る。

```swift:Plus.swift
func plus_cont_curry(_ x: Int) -> (Int) -> Cont<Int, Int> {
    return { y in plus_cont(x, y) }
}
```

すると、さきほどの$1 + 2 + 3$は関数`plus_cont_curry`を用いて次のようになる。

```swift:main.swift
plus_cont(1, 2).flatMap(plus_cont_curry(3)).run { x in x }
```

さらに、$1 + 2 + 3 + 4 + 5$も次のようになる。

```swift:main.swift
plus_cont(1, 2).flatMap(plus_cont_curry(3))
               .flatMap(plus_cont_curry(4))
               .flatMap(plus_cont_curry(5))
               .run { x in x }
```

`flatMap`を表す二項演算子`>>>=`を作れば、さらに短くなる。

```swift:Cont.swift
precedencegroup Left {
    associativity: left
}

infix operator >>>=: Left

func >>>=<R, A, B>(_ ma: Cont<R, A>, _ f: @escaping (A) -> Cont<R, B>) -> Cont<R, B> {
    return ma.flatMap(f)
}
```

```swift:main.swift
(plus_cont(1, 2) >>>= plus_cont_curry(3)
                 >>>= plus_cont_curry(4)
                 >>>= plus_cont_curry(5))
                 .run { x in x }
```

# `map`とリフト

たとえば、足し算の結果を数値（`Int`）から文字列（`String`）へ変換したくなった時、`map`を使うと便利である。次のような`Int`から`String`へ変換する関数があるとする。

```swift:String.swift
func int_to_string(_ n: Int) -> String {
    return String(n)
}
```

この関数を用いて、`Cont<Int, String>`を次のように`map`を用いて作成できる。

```swift:main.swift
let _: Cont<Int, String> = (plus_cont(1, 2) >>>= plus_cont_curry(3)).map(int_to_string)
```

このように、任意の関数`A -> B`を用いて`Cont<R, A>`から`Cont<R, B>`を作ることができる。

# 異なる型を持つ`Cont`の合成

足し算を行う`Cont<Int, Int>`を作成したが、たとえば次のように与えられた文字列に`hello`を連結して返すような関数`add_hello`と、文字列の文字数を数える関数`count`をそれぞれ次のように作ったとする。

```swift:String.swift
func add_hello(_ str: String) -> Cont<Int, String> {
    return Cont { k in k("hello" + str) }
}

func count(_ str: String) -> Cont<Int, Int> {
    let n = str.characters.count
    return Cont { k in k(n) }
}
```

関数`add_hello`は`Cont<Int, String>`を返す。一方で関数`count`は`Cont<Int, Int>`を返す関数である。この二つの関数を合成することができる。これらを用いて、たとえば文字列`world!`に`hello`を加えた文字列の長さに、10を足すという処理を次のように書ける。

```main.swift
add_hello("world!").flatMap(count).flatMap(plus_cont_curry(10)).run { x in x }
```

結果は次のようになる。

```
21
```

さきほどの二項演算子`>>>=`を使えば短くなる。

```swift:main.swift
(add_hello("world!") >>>= count >>>= plus_cont_curry(10)).run { x in x }
```

このように、`Cont`の関数`flatMap`を用いることで、`Cont<R, A>`の型パラメータ`R`が等しければ、`Cont<R, B>`のような型を持つ値と合成できる。

# まとめ

このように`Cont`というデータ構造を用いることでコールバックを隠蔽できることを紹介した。この`Cont`は継続モナドと呼ばれ、HaskellやScalaなどで実装や研究が盛んに行われている。Swiftでもこのようなデータ構造を用いてよりよいプログラムを書けたらよいと思う。

# 参考文献

- [継続モナドを使ってWebアプリケーションのコントローラーを自由自在に組み立てる](http://qiita.com/pab_tech/items/fc3d160a96cecdead622)