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 の比較的最近のバージョンでも同様の出力になることも確認しました)。