SwiftUIとともに颯爽と登場した@_functionBuilder
(aka @resultBuilder
)ですが、いまいち使い所を見出せないまま時は過ぎました。
自分の観測範囲に限れば、SwiftUI以外で@resultBuilder
が有効に使われているライブラリは見当たらず、持て余してるのは自分だけじゃないと思って溜飲を下げていたところです。
そんな中、Swift 5.7が正規表現に対応し、その中で正規表現構築用のDSLを用意してきました。
RegexBuilder | Apple Developer Documentation
swift-evolution/0351-regex-builder.md at main · apple/swift-evolution
「その手があったか!」と感心しました。
以前より、スクリプトで正規表現を構築する、という手法があることは認識していましたが、@resultBuilder
で構造的に、Swiftの強力な型システムやXcodeのコード補完の恩恵を受けながら誤解のないように記述させる、というのは、なるほど説得力があります。
前置きが長くなりましたが、そんな事があり、今一度@resultBuilder
に向き合ってみようと思い、以前習作として書いた、@resultBuilder
でHTMLを書き出すDSLを見直してみることにしました。
割と長いので全部は掲載しませんが、上記GistのコードをSwift Playgroundにコピペすれば実行できるはずです。
何をやっているかは大体コメントに書いております。
このDSLのポイントは、@resultBuilder
内のすべての要素の基盤となるElement
内のrender()
になります。
L4-
/// 要素
protocol Element {
/// HTMLを書き出すレンダー
/// - Returns: HTML
func render() -> String
}
L47-
// 内部タグ
let i = innerHTML?.reduce(into: "") {
$0 += $1.render()
} ?? ""
render()
内でネストされた要素(内部タグ)のrender()
を呼び出すことにより、タグが連鎖的に書き出されることになります。
要素をネストさせる場合、DSL内部の全ての要素に共通する基盤のprotocol
を用意し、連鎖的に実行させる仕組みは必要になってくると思われます。
また今回手直ししたのは、単なるテキストを表示させる部分で、以前はText()
というstruct
を用意していたのですが、String
をElement
に適合させることによってだいぶスッキリ記述できるようにしました。
変更前
struct Text: Element {
var innerText = ""
init(_ innerText: String) {
self.innerText = innerText
}
func render() -> String {
innerText
}
}
P(["style": "color:red"]) {
Text("テキスト") // String
p
1 // Int
}
変更後
extension String: Element {
func render() -> String {
self
}
}
P(["style": "color:red"]) {
"テキスト" // String
p
1 // Int
}
その意味でも共通する基盤のprotocol
の準備はあった方が良いと思われます。
ついでにInt
にも対応しました。
手直ししていて、@resultBuilder
は「記述を簡便にする」という使い方よりも、「ガチガチに制限を入れて曖昧な部分を無くす」という使い方の方が合っていそうです。
Xcodeのコード補完があれば、制限をガチガチにしても記述はそこまで手間にならないと思います。
現状タグの属性をDictionaryで指定するようにしていますが、ここをメソッドにするなどでさらに制限を加える事ができます。
HTML自体はそこまで厳密なものではないですが、「構造化されたデータを曖昧さがないように記述させる」というところが@resultBuilder
の使い所の気がします。