Posted at

Swift5.1のFunction Builderで契約プログラミングっぽい記述をする

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関数の返値となります。