代数的効果とは何か
何を依頼するかと、どう処理するかを分離します。
9.1 perform は「操作の依頼」
関数が何かの処理を要求したいとき、Tauto は 代数的効果(algebraic effects) を使えます。呼び出す側は perform で操作を依頼し、周りの handle が、その意味を与えます。
ch09-ask.taut
effect ask(n: i64): i64 resume ω
let result := handle {
perform ask(5) + 1
} with {
ask(n), k => k(n) + k(n + 1)
return v => v
}
perform show("result", result)
実行結果
result = 13
result = 13
ask(5) は、普通の関数呼び出しではありません。ハンドラは、操作が起きたあとの残りの計算を k として受け取ります。
9.2 継続 k を追いかける
perform ask(5) + 1 の「残りの計算」は、応答に 1 を足す処理です。したがって k(5) は 6、k(6) は 7 を返します。ハンドラは両方を足して 13 を返します。
resume ω は、この継続を何度でも再開できるという意味です(次の章で詳しく見ます)。継続を複製しても資源が二重に消費されないように、所有権の検査と再開量が組み合わされています。
9.3 失敗を handler で表す
ch09-fail.taut
effect fail(why: Text): i64
fn safe_div(a: i64, b: i64): i64 {
if b == 0 {
perform fail("ゼロでは割れません")
} else {
div(a, b)
}
}
let answer := handle { safe_div(84, 0) } with {
fail(why), k => 0 - 1
return value => value
}
perform show("answer", answer)
実行結果
・ safe_div answer = -1
answer = -1
このハンドラは k を呼びません。残りの計算に戻らずに -1 を返すので、「例外のように処理を打ち切る」振る舞いになります。
標準ライブラリにも fail があり、fail<E>: (e: E) -> Never resume 0 と宣言されています。結果が Never で、再開量が 0(再開しない)です。上の例が宣言している fail は、これとは別の、この例だけのものです。再開しない効果は、k 自体を書きません。
9.4 効果の行(effect row)
関数の返り値の型のあとに ! {out_of_bounds} のような注記があると、その関数が外側へ残す効果を表します。! {} は、効果が残らない状態です。関数を合成するときは、効果の行も検査されます。
演習 9.1:ask(n), k => k(n) * k(n + 1) に変えたら、値はいくつになるでしょうか。