call/cc パズルを Squeak Smalltalk で
Lisp Scheme Part21 >>885 経由で Scheme:call/ccパズル が確かに面白そうだったのでこれにチャレンジ。
問題は、次の式がどんな動きをするか?というもの。
(let* ((yin ((lambda (foo) (newline) foo) (call/cc (lambda (bar) bar)))) (yang ((lambda (foo) (write-char #\*) foo) (call/cc (lambda (bar) bar))))) (yin yang))
ただ、あいにく私の LISP 脳は貧弱で、このコードの動きを追う以前に、いったい何が書かれているのかすら分からない状態なので、いったん Squeak Smalltalk に書き直し(^_^;)。
| yin yang | World findATranscript: nil. yin := [:foo | Transcript cr; endEntry. foo] value: [:bar | bar] callCC. yang := [:foo | Transcript show: $*. foo] value: [:bar | bar] callCC. yin value: yang
(なお念のため、実際にこのスクリプトを評価するには、NewCompiler と Continuation のインストールが必要です。)
以下、ネタバレ注意。
[:foo | Transcript cr; endEntry. foo]
これは、トランスクリプトに改行を出力して、受け取ったブロック変数 foo の値をそのまま返すだけのブロックです。なので、
yin := [:foo | Transcript cr; endEntry. foo] value: [:bar | bar] callCC.
は、yin に値が代入される直前の継続([:bar | bar] callCC)を yin に代入するだけの式です。ただ、代入されるときにトランスクリプトに改行を出力します。同様に、
yang := [:foo | Transcript show: $*. foo] value: [:bar | bar] callCC.
は、yang に値が代入される直前の継続を yang に代入します。ただし代入時には * がトランスクリプトに出力されます。
以上、二つの式が評価された時点では、yin には最初の yin への代入直前の継続(以下、yin1 と呼ぶ)が、yang には最初の yang への代入直前の継続(同じく yang1)が代入された状態で、さらにトランスクリプトには、改行と * が出力されています。
ここで yin value: yang が評価されると、yin は yin1 なので最初の yin への代入直前の状態に戻ります。この際に評価される [:bar | bar] は渡された値を返すだけのブロックなので、yang1 が返り、結果、改行出力を伴って yin には yang1 が代入されます。引き続き、yang には自身に値が代入される直前の継続(yang2)が * の出力を伴って代入されます。
すると次の yin value: yang は、yang1 value: yang2 となり、処理は yin1 で巻き戻された際の yang 代入直前に巻き戻されます。yang には yang2 が * の出力を伴って代入され、結果、ここまでの出力は、
* **
となります。さらに、ここでの実行コンテキストは yin に yin1 が、yang に yang2 が代入された状態となっています。
したがって、続く yin value: yang の評価は yin1 value: yang2 となり、改めて yin に改行出力を伴って yang2 が代入され、yang には * の出力を伴って yang3 が代入されます。
よって、この時点での出力は、
* ** *
これに続く yin value: yang は yang2 value: yang3 となるので、yang には yang3 が * の出力を伴って代入されます。この実行コンテキストでは yin には yang1 が代入されており、続く yin value: yang は yang1 value: yang3 で、* 出力を伴う yang への yang3 の代入になります。
* ** ***
この時点での実行コンテキストでは、yin には yin1 が代入されており、続く yin value: yang は yin1 value: yang3 となり改行と * を出力。
以降も同様に、yang3 value: yang4 → yang2 value: yang4 → yang1 value: yang4 → yin1 value: yang4 → yang4 value: yang5 …となって出力は、
* ** *** **** * ...
と改行を挿んで * の数が一つずつ増える無限ループとなるはずです。
で、実行すると実際にそうなります。めでたし、めでたし(念のため、Guile と Gauche の比較的最近のバージョンでも同様の出力になることも確認しました)。