FunctionBuilderで遊ぼう

# FunctionBuilder とは
Xcode 11で追加されたSwiftの新機能で、有り体に言えばSwift言語内にDSLを定義できる言語機能になります。
2019/12/07時点で、publicではない機能として定義されていますが、SwiftUIにおいて一部のViewの記述に利用されています。

まだpublicではないので、公式ドキュメントは存在しません。代わりに公式のフォーラムがあるのでそちらを参照すると良いでしょう。
https://forums.swift.org/t/function-builders/25167/10

なおこのFunctionBuilderはORTとは異なりSwift5.1のランタイムを必要としない機能のため、iOS13未満のOSでも動作します。

SwiftUIはORTが必要なのでiOS13以降であることが必須ですが、FunctionBuilderだけなら今から使えるわけです。(推奨するとは言っていない)
体験版ですね。遊んでみましょう。

## FunctionBuilderの価値
**FunctionBuilderを使うことで、シンプルな構文と複雑な型を両立させることが可能になる。**
これがFunctionBuilderにおける最も大きな価値でしょう。
ただシンプルな構文を作るだけなら、`Array<AnyFoo>`であっても、順番に変数を並べるだけでも構わなかったはず。なぜFunctionBuilderなのか。その結論にコードを書きながら至ることがこの記事の目的です。
結論から書くと、複雑な型のstatic関数が鍵になります。

# TableViewにおける考察
**「僕の考えた最強のTableView」**
浪漫に溢れたコーディングです。誰だって大好きです。私も大好きです。
今回はFunctionBuilderを使って、TableViewDataSourceを生成する事を題材に考えていきます。

## FunctionBuilderの集合の型を準備する
FunctionBuilderでは、1行毎の返り値を複数束ねてその集合を型として生成することで、DSLとして成り立つようになっています。
単体ならそのままで良いので、直和と直積、空集合についての定義を追加します。

```swift
struct Empty {
}

struct Pair<C0, C1> {
  var c0: C0
  var c1: C1
}

enum Either<C0, C1> {
  case c0(C0)
  case c1(C1)
}
```

簡単ですねv

## FunctionBuilderを構成する
FunctionBuilderが定める関数を定義します。
いずれも返り値の群を引数に取り、集合を返す関数になります。

```swift
@_functionBuilder
struct UIBuilder {
    static func buildBlock() -> Empty { .init() }
    static func buildBlock<C>(_ c: C) -> C { c }
    static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> Pair<C0, C1> { .init(c0: c0, c1: c1) }
    static func buildIf<C>(_ c: C?) -> Either<C, Empty> { .init(from: c) /* extension書いてネ */ }
    static func buildEither<T, F>(first: T) -> Either<T, F> { .c0(first) }
    static func buildEither<T, F>(second: F) -> Either<T, F> { .c1(second) }
}
```

この宣言で `UIBuilder` において2値までの返り値が使えるようになります。

## 集合の型を一般化する
Protocolを作って一般化しましょう。

```swift
protocol TableViewCellProtocol {
}

extension Empty: TableViewCellProtocol {
}

extension Pair: TableViewCellProtocol where C0: TableViewCellProtocol, C1: TableViewCellProtocol {
}

extension Either: TableViewCellProtocol where C0: TableViewCellProtocol, C1: TableViewCellProtocol {
}
```

## DataSourceを作ってみる - FunctionBuilderを使ってみる
ここまでの作業で実際にFunctionBuilderとしてコンパイル可能になっています。
関数を定義して呼び出し、コンパイルしてみましょう。

```swift
extension UITableView {
    func generateDataSource<Item, C: TableViewCellProtocol>(items: [Item], @UIBuilder _ tableViewCells: @escaping (UITableView, IndexPath, Item) -> C) -> UITableViewDataSource? {
       dataSource
    }
}
```

```swift
class MyTableViewCell0: UITableViewCell, TableViewCellProtocol {}
class MyTableViewCell1: UITableViewCell, TableViewCellProtocol {}
```

```swift
lazy var dataSource = self.tableView.generateDataSource(items: [1, 2, 3]) { tableView, indexPath, item in
    if item % 2 == 0 {
        MyTableViewCell0() // 今回は本質ではないのでDequeueは省略
    } else {
        MyTableViewCell1()
    }
}
```

コンパイルは通ると思いますが、このままではまだ使い物になりません。

## DataSourceを作ってみる - Cellのレンダリングをする

DataSourceクラスを作りましょう。Itemの配列を持ち、セットされたらリロードします。また、クロージャーからCellを生成できるようにします。
とは言っても、難しいことは何もせずに受け取ったクロージャを実行するだけです。

```swift
class TableViewDataSource<Item>: NSObject, UITableViewDataSource {
    var items: [Item] {
        didSet {
            reloadData(oldValue, self.items)
        }
    }
    var reloadData: ([Item], [Item]) -> ()
    var cellForItem: (UITableView, IndexPath, Item) -> UITableViewCell

    init(items: [Item], reloadData: @escaping ([Item], [Item]) -> (), cellForItem: @escaping (UITableView, IndexPath, Item) -> UITableViewCell) {
        self.items = items
        self.reloadData = reloadData
        self.cellForItem = cellForItem
        super.init()
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        cellForItem(tableView, indexPath, items[indexPath.row])
    }
}
```

DataSourceとして動作させるために、`UITableViewCell` を取り出す必要があります。
空の `TableViewCellProtocol` は実質 `Any` なので宣言を変更して対応します。

```swift
protocol TableViewCellProtocol {
    func asTableViewCell() -> UITableViewCell?
}

extension Empty: TableViewCellProtocol {
    func asTableViewCell() -> UITableViewCell? { nil }
}

extension Pair: TableViewCellProtocol where C0: TableViewCellProtocol, C1: TableViewCellProtocol {
    func asTableViewCell() -> UITableViewCell? { c0.asTableViewCell() ?? c1.asTableViewCell() }
}

extension Either: TableViewCellProtocol where C0: TableViewCellProtocol, C1: TableViewCellProtocol {
    func asTableViewCell() -> UITableViewCell? {
        switch self {
        case .c0(let c0): return c0.asTableViewCell()
        case .c1(let c1): return c1.asTableViewCell()
        }
    }
}

extension TableViewCellProtocol where Self: UITableViewCell {
    func asTableViewCell() -> UITableViewCell? { self }
}
```

このDataSourceクラスを、先の `generateDataSource` で生成、利用します。

```swift
extension UITableView {
    func generateDataSource<Item, C: TableViewCellProtocol>(items: [Item], @UIBuilder _ tableViewCells: @escaping (UITableView, IndexPath, Item) -> C) -> TableViewDataSource<Item> {
       let dataSource = TableViewDataSource(
            items: items,
            reloadData: { [weak self] _, _ in self?.reloadData() },
            cellForItem: { tableView, indexPath, item in
                return tableViewCells(tableView, indexPath, item).asTableViewCell()!
        })
        self.dataSource = dataSource
        return dataSource
    }
}
```

DataSourceが大体動くようになりました。

## DataSourceを作ってみる - registerできるようにする
今回はdequeueを省略したので動いてますが、TableViewを使う場合は本来dequeueが必要であり、対となるregisterも必要です。
`TableViewCellProtocol` に `register` するための関数を生やします。

```swift
// ※先の章で宣言した項目について省略
protocol TableViewCellProtocol {
    static func register(to tableView: UITableView)
}

extension Empty: TableViewCellProtocol {
    static func register(to tableView: UITableView) {

    }
}

extension Pair: TableViewCellProtocol where C0: TableViewCellProtocol, C1: TableViewCellProtocol {
    static func register(to tableView: UITableView) {
        C0.register(to: tableView)
        C1.register(to: tableView)
    }
}

extension Either: TableViewCellProtocol where C0: TableViewCellProtocol, C1: TableViewCellProtocol {
    static func register(to tableView: UITableView) {
        C0.register(to: tableView)
        C1.register(to: tableView)
    }
}

extension TableViewCellProtocol where Self: UITableViewCell {
    static func register(to tableView: UITableView) {
         print("Called!! \(Self.self)")
    }
}
```

printの行が呼ばれれば、これを本来の`tableView.register`の呼び出しに変更すれば動作することは自明ですね。
呼び出し側でこの関数を利用しましょう。

```swift
extension UITableView {
    func generateDataSource<Item, C: TableViewCellProtocol>(items: [Item], @TableViewBuilder _ tableViewCells: @escaping (UITableView, IndexPath, Item) -> C) -> TableViewDataSource<Item> {
       C.register(to: self) // ここに追加!!
       let dataSource = TableViewDataSource(
            items: items,
            reloadData: { [weak self] _, _ in self?.reloadData() },
            cellForItem: { tableView, indexPath, item in
                return tableViewCells(tableView, indexPath, item).asTableViewCell()!
        })
        self.dataSource = dataSource
        return dataSource
    }
}
```

たった一行追加するだけで、TableViewCellのRegisterが完了するようになりました。

## 3個以上の返り値に対応する
必要な材料は揃っているので、 `TableViewBuilder` に雪だるま式に `buildBlock` を追記していくだけで動くようになります。

```swift
extension TableViewBuilder {
static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> Pair<Pair<C0, C1>, C2> {
        .init(c0: .init(c0: c0, c1: c1), c1: c2)
    }

    static func buildBlock<C0, C1, C2, C3>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3) -> Pair<Pair<Pair<C0, C1>, C2>, C3> {
        .init(c0: .init(c0: .init(c0: c0, c1: c1), c1: c2), c1: c3)
    }

    ...
}
```

```swift
lazy var dataSource = self.tableView.generateDataSource(items: [1, 2, 3]) { tableView, indexPath, item in
    if item == 0 {
        MyTableViewCell0()
    } else if item == 1 {
        MyTableViewCell1()
    } else if item == 2 {
        MyTableViewCell2()
    } else {
        MyTableViewCell3()
    }
}
```

`MyTableViewCell0` ~ `MyTableViewCell3` の `register` が呼び出せることが確認できます。

# FunctionBuilderの価値(2回目)
最後に書いた4種類のCellを返す関数ですが、どこにも `Pair` や `Either` 、ましてやそれがネストした凶悪な型はどこにも見当たりません。返り値もそれなりにシンプルな `TableViewDataSource<Item>` です。
しかし、実行時に各Cellの `register` をコールするスタックトレースの底にある型は、`Either<Either<MyTableViewCell0, MyTableViewCell1>, Either<MyTableViewCell2, MyTableViewCell3>>` です。
この型から `Either` の `register` を再帰的に呼び出すことで、`MyTableViewCell0` ~ `MyTableViewCell3` の`register` がコールされる。
ちょうどMLにおけるDefine and Runで計算グラフを作るように、事前の処理を定義できるのです。
ビルドされた関数を実行するだけでなく、その関数の型の評価に価値がある、そういったケースでFunctionBuilderは真価を発揮します。

## ココがダメだよFunctionBuilder
では実際にこれが最強のTableViewになるかというと、そんな事はありません。
FunctionBuilderには以下の制限があります。
#### if let, if caseが使えない
OptionalBindingが使えません。したがってOptionalを非Optionalとして取り出す方法は

```swift
if value != nil {
  MyComponent(value!)
}
```

になります。SmartCastが欲しくなる。

#### switchが使えない
残念ながらSwitchも使えません。したがってAssociatedValueを取り出す方法は、

```swift
extension MyEnum {
  var myCase: MyCase? {
    switch self {
    case myCase(let value): return value
    default: return nil
    }
  }
}
```
```swift
if value.myCase != nil {
  MyComponent(value.myCase!)
}
```
になります。これはちょっと厳しい…

#### capture listが使えない
SwiftUIは全て値型だったのでcapture listを必要としない世界でした。
UIKitはそうではないので、参照カウントに気をつけなければならない。しかしどうやらcapture listは使えません。(バグ?)
したがって関数の外に手書きします。しかもOptionalBindingの呪いも刺さります。

```swift
weak var weakSelf = self
tableView.generateDataSource(items: [1,2,3]) { (_, _, _) in
    if weakSelf != nil {
        weakSelf!.createCell(...)
    }
}
```

## おまけ
すぐに動かせるサンプルをGithubに用意しておきました。
[https://github.com/tarunon/UIViewBuilder](https://github.com/tarunon/UIViewBuilder)
プロダクトで使うクオリティではない、というのとFunctionBuilderは未だpublicではないということにご注意下さい。
CollectionViewへの拡張や、差分更新などはやっていませんが、既存の他のコードと組み合わせて十分再現可能だと思います。
是非遊んでみて、そしてあわよくば自分のコードに取り込んでみてください。完成させてくれても嬉しいのよ。
それでは。