Swift5.1で導入されるFunction Builderを学ぶため、Function Builderで契約プログラミング(DbC)っぽいことをできるようにしてみます。(実用性は無いですが)
まずIn, Out, Bodyを構成するための型を用意します。
public struct In<S> {
var message: String
var contract: (S) -> Bool
public init(_ message: String = "", _ contract: @escaping (S) -> Bool) {
self.message = message
self.contract = contract
}
}
public struct Out<T> {
var message: String
var contract: (T) -> Bool
public init(_ message: String = "", _ contract: @escaping (T) -> Bool) {
self.message = message
self.contract = contract
}
}
public struct Body<S, T> {
var body: (S) -> T
public init(body: @escaping (S) -> T) {
self.body = body
}
}
次にFunction Bulider用の型を記述します。
@_functionBuilder public struct DbCBuilder {
static func buildBlock<S, T>(_ a: In<S>, _ b: Body<S, T>) -> (S) -> T {
return { s in
precondition(a.contract(s), a.message)
return b.body(s)
}
}
static func buildBlock<S, T>(_ a: Out<T>, _ b: Body<S, T>) -> (S) -> T {
return { s in
let t = b.body(s)
precondition(a.contract(t), a.message)
return t
}
}
static func buildBlock<S, T>(_ a: In<S>, _ b: Out<T>, _ c: Body<S, T>) -> (S) -> T {
return { s in
precondition(a.contract(s), a.message)
let t = c.body(s)
precondition(b.contract(t), b.message)
return t
}
}
}
buildBlockを3つ用意しました。In+Body, Out+Body, In+Out+Bodyです。
このFunction Builderを利用する関数を記述します。
func dbc<S, T>(@DbCBuilder block: () -> (S) -> T) -> (S) -> T {
return block()
}
これで準備ができました。
これらを利用した関数を記述します。
func calc(_ i: Int) -> Int {
dbc {
In { $0 > 1 }
Body { $0 * $0 }
}(i)
}
func add(_ a: Int, _ b: Int) -> Int {
dbc {
In { $0 > 0 && $1 > 0 }
Out { $0 > 2 }
Body { $0 + $1 }
}((a, b))
}
calc(2)
add(1, 2)
dbc関数の中でIn+BodyやIn+Out+Bodyを返すことでDbCBuilderの各buildBlockが実行され(S)->Tが返され、dbc関数の返値となります。