LoginSignup
14
15

More than 5 years have passed since last update.

Swiftでdelay/force

Last updated at Posted at 2015-05-01

@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である.たった一行で書けてしまう.

14
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
15