j3259の日記: 2.8 の味見: 継続編
以下 Tiark Rompf 著A Taste of 2.8: Continuations の和訳。
Scala 2.8 より継続(continuation) という便利な制御フローの抽象観念がサポートされる。継続を用いることで、例えば、コールバック指向でイベント・ドリブンな API を一文ずつ書かれた、より素直なスタイルで使う事ができる。
これは数々のプログラミング作業を簡潔化できる可能性がある:
- Java NIO を用いた非同期IO
- Executor やスレッド・プールの使用
- ウェブ・アプリケーションにおける複数のリクエストをまたいだ制御フロー
Scala 2.8 では以上の例のための新しいAPI は提供されないが、その基となる継続機構が定まった後では色々期待できるだろう。
shift と reset を茶々っと
プログラムのある時点で「捕捉」された継続はそれ以降の「残り」のプログラムを具象化する。興味深いのは、継承は普通の関数値として取り扱うことができ、例えばデータ構造に保存したり、後で呼び出したり、呼び出さなかったり、複数回呼び出したりすることができる。Scala の継続は Scheme や ML などの継承とは違い複合可能なものである(これを「限定」であるという)。どういう事かというと、限定継続は残りの全てのプログラムを具象化するわけではなく、プログラマが定義した一定の境界までの部分的な残りを具象化するということである。
継続は shift と reset という二つの構造によって制御される。shift を呼び出すことで現在の継続を捕捉され、reset が継続がどこまで届くかという境界を定義する。ここに例をあげる:
reset {
...
shift { k: (Int=>Int) => // 継続 k は '_ + 1' となる
k(7)
} + 1
}
// 結果: 8
もちろん、shift と reset はライブラリ関数であり、新しい予約語ではない。また、文法的には両方を同時に使う必要もない。実際、shift と reset の組み合わせは全く関係ないメソッド間で起こる事もありえる。型システムは万全を保証するため、単独の shift が現れるメソッドの戻り型には @cps という型注釈をつけることを要求します。しかし、多くの場合はこの型注釈はプログラマが気づく事なく自動的に推論されます。
def foo(): Int @cps[Int,Int] = { // 戻り型は省略可
shift { k: (Int=>Int) =>
k(7)
} + 1
}
reset(2 * foo()) // 結果: 16
コールバック指向な API を用いた例を紹介する:
import java.util.{Timer,TimerTask}
val timer = new Timer()
def sleep(delay: Int) = shift { k: (Unit => Unit) =>
timer.schedule(new TimerTask {
def run() = k() // 通常は k をスレッド・プールで実行する
}, delay)
}
println("わーい")
sleep(1000)
println("スレッド無し!")
冒険好きな気分なら、Scala コンパイラのための継承プラグインを試してはいかがだろう(ソース・レポジトリより入手可能)。Scala 2.8 において継承がどのように実装されるのかという込み入った詳細に関してはこの論文 (ICFP'09 にて受理された)を参照のこと。
2.8 の味見: 継続編 More ログイン