@autoclosure
が使えない所で@autoclosure
的なことを実現してみた備忘録.
@autoclosureを書けないが式を評価して欲しくない例
次のような関数f
で,気分としてはh
の第二引数を@autoclosure
的にしたいときがある.
func f<T,U,V>(x: T, g: T -> U, h: (T, U) -> V) -> V {
return h(x, g(x)) // ここでg(x)を評価せず,hの中で評価したい
}
let v = f(0, {...}, {...}) // 直接クロージャを渡してfを呼ぶ
...
(注):クロージャ定義では@autoclosure
は書けない.
delay/forceの導入
そこで,schemeなどにあるdelay/forceの仕組みを作る.
func delay<T>(@autoclosure(escaping) f: () -> T) -> () -> T {
return f
}
関数delay
は@autoclosure
を使って引数を評価せずにそのまま返すだけ.
関数f
は,
func f<T,U,V>(x: T, g: T -> U, h: (T, () -> U) -> V) -> V {
return h(x, delay(g(x)))
}
let v = f(0, {...}, { ...$0,... $1() }) // $1()でdelayしたものをforceする
...
delayを受け取れるようにhのタイプを(T, U) -> V
から(T, () -> U) -> V
へ変更する.
force処理はfの第3実引数であるクロージャ内で()
つけて起動すればよい.そのクロージャの第二引数($1
)がdelayされた評価待ちの式(クロージャ)である.
動作確認
func f(x: Int, g: Int -> Double, h: (Int, () -> Double) -> String) -> String {
print("f ")
return h(x, delay(g(x)))
}
let g0 = {(x: Int) -> Double in print("g "); return Double(x) * 2 }
let h0 = {(x: Int, k: () -> Double) -> String in print("h "); return "\(Double(x) + k())" }
print(f(1, g0, h0)) // f h g 3.0
ちゃんと"f h g"の順番で表示され,gの評価が遅延されてることが分かる.
利用例
こんなの必要なの?という疑問が湧くので,便利そうな例を挙げておこう.
func foldr<G: GeneratorType, T>(var gen: G, initVal: T, f: (G.Element, () -> T) -> T) -> T {
if let v = gen.next() {
return f(v, delay(foldr(gen, initVal, f)))
}
return initVal
}
func reduce<S: SequenceType, T>(seq: S, initVal: T, f: (S.Generator.Element, () -> T) -> T) -> T {
return foldr(seq.generate(), initVal, f)
}
このreduce
は組み込みのものと違い,シーケンスを全スキャンしないような処理が可能である.
例として,シーケンスの中に条件に合うものがある場合に真を返す関数hasElement
を定義してみよう.
func hasElement<S: SequenceType>(seq: S, pred: S.Generator.Element -> Bool) -> Bool {
return reduce(seq, false) { pred($0) ? true : $1() }
// return reduce(seq, false) { pred($1) ? true : $0 } // 組み込みのreduce
}
要素が見つかった場合,$1()
が実行されないので,シーケンスのスキャンはそこでストップする.
for e in seq {
if pred(e) {
return true
}
}
return false
と同等の動作をするのがこのreduce
である.たった一行で書けてしまう.