Shiroさんに教えてもらったyin-yang puzzleの回答。

課題のプログラムは次の通りです。

( let* ([ yin (( lambda ( foo ) ( newline ) foo ) ( call/cc ( lambda ( bar ) bar )))] [ yang (( lambda ( foo ) ( write-char #\* ) foo ) ( call/cc ( lambda ( bar ) bar )))]) ( yin yang ))

以下ネタバレ。

let*をlambdaと関数適用に分解して、名前をわかりやすくすると次のようになります。

(( lambda ( yin ) (( lambda ( yang ) ( yin yang )) (( lambda ( yang* ) ( write-char #\* ) yang* ) ( call/cc ( lambda ( cc ) cc )) ))) (( lambda ( yin* ) ( newline ) yin* ) ( call/cc ( lambda ( cc ) cc )) ))

注目するべき点は、Bで捕捉される継続では、yinになんらかの値が束縛されている、ということです。実際には、AかBで捕捉される継続のいずれかが束縛されます。Bの継続が束縛されている場合、その継続ではyinになんらかの値、具体的にはAかBで捕捉される継続のいずれかが束縛されており、Bの継続が束縛されている場合、その継続では……。

Aの継続が呼び出されると、改行して、引数で渡された値がyinに束縛されます。

Bの継続が呼び出されると、その継続が捕捉された時点でのyinへの束縛が再現され、アスタリスクが出力され、引数で渡された値がyangに束縛されます。

ステップを追うと次のとおり。

1. (newline), そしてAの継続がyinに渡される。ここで名前環境を{yin=A}と書く。

2. (write-char #\*), そしてBの継続がyangに渡される。このBの継続において、上述したとおり、yinにはAの継続が束縛されている。これを{yin=A, yang=B{yin=A}}と書く。

3. (yin yang)を呼ぶということは、(A B{yin=A})を呼ぶということ。Aに戻って(newline), そしてyin*, yinにはB{yin=A}が渡される。{yin=B{yin=A}}

4. (write-char #\*), そしてyangにはBの継続が渡されるが、このBの継続において、yinにはB{yin=A}が束縛されている。つまり{yin=B{yin=A}, yang=B{yin=B{yin=A}}}。

5. (yin yang)は(B{yin=A} B{yin=B{yin=A}})。Bに戻るが、そのときyinにはAが束縛された状態。(write-char #\*)を呼んで、yangには(yin yang)の引数であるB{yin=B{yin=A}}が渡される。{yin=A, yang=B{yin=B{yin=A}}}。

6. (yin yang)は(A B{yin=B{yin=A}})。Aに戻って(newline), そしてyinにはB{yin=B{yin=A}}が渡される。{yin=B{yin=B{yin=A}}}。

7. (write-char #\*), そしてyangにはBの継続が渡されるが、このBの継続において、yinにはB{yin=B{yin=A}}が束縛されている。{yin=B{yin=B{yin=A}}, yang=B{yin=B{yin=B{yin=A}}}}。

8. (write-char #\*), {yin=B{yin=A}, yang=B{yin=B{yin=B{yin=A}}}}

9. (write-char #\*), {yin=A, yang=B{yin=B{yin=B{yin=A}}}}

10. (newline), {yin=B{yin=B{yin=B{yin=A}}}}

11. (write-char #\*), {yin=B{yin=B{yin=B{yin=A}}}, yang=B{yin=B{yin=B{yin=B{yin=A}}}}}

12. (write-char #\*), {yin=B{yin=B{yin=A}}, yang=B{yin=B{yin=B{yin=B{yin=A}}}}}

13. ...

Rubyで書き直すと次の通り。