Result BuilderでSwiftUIのようなオリジナル文法を作成しよう

Result Builderは、元となる子要素を組み合わせ、何か一つの親要素を作り上げるのに非常に便利な文法です。
この方法を用いれば、SwiftUIのような書き方で様々な子要素の組み合わせコンポーネントを作成できるようになります。ここではそのメリット、具体的な使い方について説明します。

## Result Builderのメリット
例えば、このような画面を表示したいとします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069252/cbd82ffb-4e85-2021-b79e-06badbe59f72.png" width=300>
今はSwiftUIで便利に実装できる部分もありますが、これを`NSAttributeString`で丁寧に実装しようとすると、以下のようなコードを書く必要があるかと思われます。
```swift
private var richText2: NSAttributedString {
    let formerAttributeString = NSMutableAttributedString(string: "We like ")
    let latterAttributedString = NSMutableAttributedString(string: "swift")
    latterAttributedString.addAttribute(NSAttributedString.Key.underlineStyle,
                                        value: 1,
                                        range: .init(location: 0,
                                                     length: latterAttributedString.length))
    formerAttributeString.append(latterAttributedString)
    return formerAttributeString
}
```

これがもし、resultBuilderを使った場合、このように書くことができます。
```swift
@RichTextBuilder
private var richText: NSAttributedString {
    RichText(string: "We like ")
    RichText(string: "swift")
        .lined()
}
```

このように可読性が非常に高いカスタムコードが書けることこそ、ResultBuilderの最大のメリットです。

## Result Builderを使ってみる
Result Builderは以下のように、`enum`で`@resultBuilder`修飾子をつけることで使用することが出来るようになります
```swift
@resultBuilder
enum RichTextBuilder {
    static func buildBlock(_ components: NSAttributedString...) -> NSAttributedString {
        let attributedString = NSMutableAttributedString()

        for component in components {
            attributedString.append(component)
        }

        return attributedString
    }
}
```
こちらを見ていただくと、個数制限のないcomponentを引数に取り、最終的に一つのアウトプットを出すようなコードになっていることがわかると思います。
さらにここから可読性を上げていくため、`typealias`や`extension`メソッドを駆使して、さらにSwiftUIのようなシンプルで可読性の高いコードに仕上げていきます。
```swift
typealias RichText = NSMutableAttributedString

private extension NSMutableAttributedString {
    func lined() -> NSMutableAttributedString {
        self.addAttribute(NSAttributedString.Key.underlineStyle,
                          value: 1,
                          range: .init(location: 0,
                                       length: self.length))
        return self
    }
}
```

以上の二つのコードを追加することで、上記に例示したテキストを、以下のようなコードで表現できるようになります。
```swift
@RichTextBuilder
private var richText: NSAttributedString {
    RichText(string: "We like ")
    RichText(string: "swift")
        .lined()
}
```
## Result Builderの応用編
例えば、配列をループさせて値を代入し、特定の場合のみ下線を引く、のような処理を実現したい場合を考えてみます。

### 完成予想図
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069252/eecd940e-283d-bdf9-3ffb-e52a7c216562.png width=300>

まず、配列をループさせるために、result builderに新たな関数を追加します。

```swift
private let langs = ["Ruby", "PHP", "Golang", "Swift"]
@RichTextBuilder
private var richText: NSAttributedString {
    for lang in langs {
        RichText(string: "We like ")
        RichText(string: lang)
            .lined()
    }
}

@resultBuilder
enum RichTextBuilder {
    ...
    // 追加
    static func buildArray(_ components: [NSAttributedString]) -> NSAttributedString {
        let attributedString = NSMutableAttributedString()

        for component in components {
            attributedString.append(component)
        }

        return attributedString
    }
}
```
result builder中にarrayが入った場合の処理をここでは記述しています。
さらに、swiftの場合でのみ下線を引く、のような条件分岐をする際には、以下のようなコードを記述します。
```swift
private let langs = ["Ruby", "PHP", "Golang", "Swift"]
@RichTextBuilder
private var richText: NSAttributedString {
    for lang in langs {
        RichText(string: "We like ")
        if lang == "Swift" {
            RichText(string: lang)
                .lined()
        } else {
            RichText(string: lang)
        }
    }
}

@resultBuilder
enum RichTextBuilder {
    ...
    // 二つの関数を追加
    static func buildEither(first component: NSAttributedString) -> NSAttributedString {
        return component
    }

    static func buildEither(second component: NSAttributedString) -> NSAttributedString {
        return component
    }
}
```
ここで関数を二つ追加しているのは、firstがif-elseのifがtrueの場合、secondがif-elseのifがfalseの場合(つまり、elseに行く場合)の処理を示しており、その両方に対して対処できるようにするためです。

これでほぼ完成ですが、このままだと全ての文章が繋がって見えてしまうため、適切に改行やカンマ、ピリオドを打っていきたいと思います。

句読点を意味する、`Punctuations`を以下のように定義します。

```swift
enum Punctuations {
    case period
    case comma
}
```

そして、こちらをNSAttributeStringへ変換できるようにするため、最終的に以下のようなコードを追加します。
```swift
private let langs = ["Ruby", "PHP", "Golang", "Swift"]
@RichTextBuilder
private var richText: NSAttributedString {
    for lang in langs {
        RichText(string: "We like ")
        if lang == "Swift" {
            RichText(string: lang)
                .lined()
            Punctuations.period
        } else {
            RichText(string: lang)
            Punctuations.comma
        }
    }
}

@resultBuilder
enum RichTextBuilder {
    ...
    // 追加
    static func buildExpression(_ expression: Punctuations) -> NSAttributedString {
        switch expression {
            case .period:
                return RichText(".\n")
            case .comma:
                return RichText(",\n")
        }
    }

    // 追加(上記を実装すると、builder内の全てのコンポーネントがPunctuationsとして扱われてしまうため、
    // ここでNSMutableAttributeStringも処理対象であることを明示する。)
    static func buildExpression(_ expression: NSMutableAttributedString) -> NSAttributedString {
        return expression
    }
}
```
これで、[上記の例に示した](#完成予想図)ようなレイアウトを、SwiftUIのような書き方で書けるようになりました。

## まとめ
- Result Builderによってカスタムの言語仕様を作ることが出来るようになる。
- 完成したResult Builderはcomputed property、関数、クロージャに使うことができる。
- `buildBlock()`を定義することで、子要素を組み合わせたコンポーネントを可読性高く作成できる。
- `typealias`や`extension`を作成することでSwiftUIのような書き方が可能になり、さらに可読性が上げられる。
- `buildEither(first:)`/`buildEither(second:)`, `buildArray()`, `buildExpression()`を用いてループや条件分岐、型に対応することが出来るようになる。

### 最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。