継続について

継続とは

継続とは、続きの計算を表すオブジェクトです。 継続を得るには、callcc という関数を使います。 callcc は、それが呼び出された時点の継続を捕まえ、 引数として与えられた関数にその継続を渡します。下に例を挙げます。

 
1 + 2 * callcc^(x){ cont = x; 0 } - 3
say cont(4)  #=> 6

この例において、変数 cont には、 次のような関数が代入されたと考えることができます。

 
^(x){ 1 + 2 * x - 3 }

つまり、「引数を受け取り、それを callcc の評価結果とした場合の トップレベルまでの残りの計算をする関数」となります。

継続を使用することによって、大域脱出やコルーチンといった、 通常は処理系に手を加えなければ実装が困難な機能も、簡単に実装できます。 その例は、サンプルフォルダ内の coroutine.cy や semi-coroutine.cy にあります。

継続の実装

Cyan における継続は、評価器の状態を保存したオブジェクトとして実装されています。 そのため、まずは評価器の仕組みについて説明します。

評価器(evaluator)とは、抽象構文木をコンパイルした命令列を実行するものです。 そのため、下に挙げる4つの情報を持っています。

  1. これから実行する命令列
  2. データスタック
  3. 環境
  4. 実行しているコードのファイル名と行番号

評価器は命令列の先頭にある命令を実行し、実行した命令は命令列から削除されます。 そのため、評価器にはこれから実行する命令列しか残っていません。 データスタックとは、計算の過程でオブジェクトを保存しておくスタックです。 環境では、変数の内容やそのスコープを管理しています。

継続は、これらの4つの情報を保存し、 好きな時に取り出して評価器にセットできるという仕組みです。 継続を呼び出すと、与えられた引数がデータスタックにプッシュされ、 残っていた命令列を実行します。

ちなみに、Cyan において、実行した命令は命令列から削除されるため、 評価器におけるループが実装できません。 このため、Cyan では継続を用いてループを実現しています。 その定義は、初期化ファイル群の中の control.cy にあります。

継続と評価器

Cyan の評価器は、関数やマクロを呼び出す際、その時点での継続を渡します。 そして、呼び出された側では、評価器の命令列とデータスタックを削除し、 自身の評価に必要な命令とデータをセットします。 その際、渡された継続を、最後に実行の結果を引数として呼び出すようにしておきます。 すると、実行後に継続が呼ばれ、呼び出す前の命令列とデータスタックが復活します。 データスタックには実行の結果がプッシュされているので、 関数の呼び出しから戻ってきたことになります。

分かりにくいと思うので、単純化して説明します。 関数を呼ぶ際には継続を渡します。 関数から戻るときには継続を呼び出します。 つまり、関数から「戻る」のではなく、継続を「呼び出す」のです。 関数を呼び出し、継続を呼び出すことで、結果的に関数の実行結果が データスタックにプッシュされているのです。

ちなみに、関数に渡された継続は、return という変数にセットされているので、 それを呼び出せば関数から戻ることができます。そして、callcc の定義は、 この return を利用しています。最後にその定義を載せて、 継続についての説明を終わります。

 
def(callcc)^(func):
  func(return)