call/cc でコルーチンっぽいことをする


call/cc …というか継続(a Continuation)が使えるようになったので「 Scheme 入門 16. 継続」の最後の項「 4.3. コルーチン」にある、call/cc を用いたコルーチンもどきの例を試してみる。


まず手始めに、スレッドで同じことを記述。

| rstream |

rstream := Array new writeStream.

[(1 to: 10) do: [:each | rstream nextPut: each. Processor yield]] fork.
[($a to: $j) do: [:each | rstream nextPut: each. Processor yield]] forkAndWait.

^ rstream contents
=> #(1 $a 2 $b 3 $c 4 $d 5 $e 6 $f 7 $g 8 $h 9 $i 10 $j)


同様のことを call/cc を使って。

| rstream pqueue start pause |

rstream := Array new writeStream.
pqueue := OrderedCollection new.

start := [pqueue removeFirst value].
pause := [[:cont | pqueue add: [cont value: nil]. start value] callCC].

pqueue add: [(1 to: 10) do: [:each | rstream nextPut: each. pause value]].
pqueue add: [($a to: $j) do: [:each | rstream nextPut: each. pause value]].

start value.

^ rstream contents
=> #(1 $a 2 $b 3 $c 4 $d 5 $e 6 $f 7 $g 8 $h 9 $i 10 $j)

余談

Squeak システムの Smalltalk 処理系には、「スレッドの動きを確認したいときにトランスクリプトへの出力を使ってはいけない」(ストリームなどを別に用意してそこへの出力に代える)といったバッドノウハウがあるのですが、call/cc 版だとそうした問題に配慮する必要はなさそうですね。

World findATranscript: nil. Transcript cr.

[(1 to: 10) do: [:each | Transcript space; show: each. Processor yield]] fork.
[($a to: $j) do: [:each | Transcript space; show: each. Processor yield]] fork
=> 1 a b 4 5 f 7 g 8 h i 10 j  ←出力がぐだぐだになる
| pqueue start pause |

pqueue := OrderedCollection new.

start := [pqueue removeFirst value].
pause := [[:cont | pqueue add: [cont value: nil]. start value] callCC].

pqueue add: [(1 to: 10) do: [:each | Transcript space; show: each. pause value]].
pqueue add: [($a to: $j) do: [:each | Transcript space; show: each. pause value]].

World findATranscript: nil. Transcript cr.
start value
=> 1 a 2 b 3 c 4 d 5 e 6 f 7 g 8 h 9 i 10 j  ←問題なし