■
pefungeのGはスタックの奥底に眠ってる値をトップにコピーする命令だ。
pefunge自身でGを実装しようとしたときに何が問題になるかというと、スタックの先頭二つしか入れ替えられないことだ。深さnから値を取り出す関数が呼び出された直後のスタックは
[…,v3, v2, v1, v0, ret_c, n]
ここで、スタックは右に向かって伸びる。
ret_cは返り値を返すチャンネル。
n=0の場合はスタックを操作して、
[…,v3, v2, v1, ret_c, v0]
とすればよい。これは「$\$」で pop, swap, pop とすればいいので簡単である。
問題は n>0 の場合で、スタックを
[…,v3, v2 v1, ret_c, n-1]
のようにしないといけない。これは pop とswap (と先頭から1引く)だけではどう考えても不可能である。
これを解決するためには、ret_c と n をまとめてひとつの値として扱えればいいわけだ。
チャンネルにこの二つをメッセージとして保存しておけば、簡単に実装できる。
まず、「&」で新しいチャンネルをトップに置く
[…,v3, v2, v1, ret_c, n-1, ch]
チャンネルに送信コマンドはスタックに「チャンネル、値、メッセージの長さ」の順で積んでおかないといけないので、並び替えて
[…,v3, v2, v1, ch, ret_c, n-1, 2]
にして「!」を実行すれば、送信できるはずである。
でもこの状態にもっていくのにも先頭二つのスワップでは不可能だ…
そこでチャンネルには一個ずつ送信して値を蓄えておくことにする。まず
[…,v3, v2, v1, ret_c, ch, n-1, 0, 2]
にして ch に (n-1, 0) を送信して、続いて
[…,v3, v2, v1, ch, ret_c, 1, 2]
にして ch に(ret_c, 1) を送信する。1とか0とかを付加して受信したときにどちらかを区別できるようにしてある。
この結果
[…,v3, v2, v1, ch((n-1,0),(ret_c,1))]
となるので、めでたく v1 をスタックから取り除くことができるわけだ。
[…,v3, v2, ch((n-1,0),(ret_c,1))]
次は、チャンネルから n-1 を読み出して、0 と比較すればよい。
しかし、「?」で読み出すと (n-1,0) が読み出させるか (ret_c,1) を読み出せるかは不定である。
なぜかというと、送信の操作は並列で行う以外に方法がないからだ。
片方の送信が終わってから、もう一方を送るなんてことはできない。
しかたがないので、「?」で (ret_c,1) が読み出されたら、それは ch に送信しなおして、もう一回読み出すことになる…
とここまでが昨日実装した「G」の内容で、今日は
PefungeでGを使わずに複数のローカル変数を扱う一般的な手法
を思いついたわけだ。
フレームはチャンネルにメッセージとして保存する。チャンネルごとに1フレーム、1メッセージとする。メッセージ(=フレーム=チャネル)のフォーマットは
Γ' = (Γ, v1, v2, v3, …, vn, n+2)
ここで、Γはひとつ上のフレームを保存したチャンネルであり、v はローカル変数の値、最後にフレームのサイズを記録する。
フレームの操作中以外の実行中は常に最新のフレームがスタックのトップにあるものとする。
フレームから値を読み出すには、「?」で受信すればよい。受信するとチャンネルが空になるので、再送信しないといけないが、フレームのサイズが最後についてるので、簡単に
>:! :?| >残りの処理
とするだけで、チャンネルをリフレッシュできる。
これを使うと、フレーム内の任意の変数 v に対して(奥底に埋もれていてもよい)
[…, Γ]
から
[…, Γ, v]
とすることができる。
新しいフレームを作って、値「1 2 3」を記録する
>123! &\| >$残りの処理
フレームから複数の値v1, v2, v3, …を取り出してスタックを
[…, Γ, v1, v2, v3, …, vn, Γ]
の状態にするには、n = 0 のときは「:」を実行するだけで、n-1までできたとして
[…, Γ, v1, v2,v3, …, vn-1, Γ]
の状態から、上に書いた変数の読み出しを実行して
[…, Γ, v1, v2, v3, …, vn-1, Γ, vn]
として、「\」を実行すれば完成。
同様の操作を使えば、フレームΓの奥底に沈んでいる変数 v1, v2, … を任意に選び出して
Γ' = Γ, v1, v2, …
な新しいフレームを作成できる。
遅延評価されるフレームなんて簡単に作れるね。