陰陽パズルはスキームで書かれています。あなたがSchemeの基本的な構文を知っていることを前提としています。
でも知らないと思うのでlet*
、call-with-current-continuation
この2つのキーワードについて説明します。
説明let*
すでに知っている場合は、スキップできますExplain call-with-current-continuation
let*
のように見える は のようlet
に動作しlet
ますが、定義された変数 ((yin ...)
と(yang ...)
) を 1 つずつ熱心に評価します。つまり、最初に を評価しyin
、 を評価しyang
ます。
ここで詳細を読むことができます:
Using Let in Scheme
説明call-with-current-continuation
すでにわかっている場合は、 にスキップできますYin-Yang puzzle
。
説明するのは少し難しいcall-with-current-continuation
です。そこで、比喩を使って説明します。
魔法を知っている魔法使いをイメージしてcall-with-current-continuation
ください。彼が呪文を唱えると、彼は新しい宇宙を作成し、自分自身をそこに送ります. しかし、彼は新しい宇宙で何もできず、誰かが彼の名前を呼ぶのを待っていました. 呼ばれた魔法使いは、可哀想な「誰か」を手に元の宇宙に戻り、魔法使いの人生を歩むことになる。呼び出されない場合、新しいユニバースが終了すると、ウィザードも元のユニバースに戻ります。
よし、もっとテクニカルになろう。
call-with-current-continuation
関数をパラメーターとして受け入れる関数です。call-with-current-continuation
関数で呼び出すと、呼び出されF
た現在実行中の環境がcurrent-continuation
パラメーターとしてパックされ、C
関数に送信されF
、実行されF
ます。したがって、プログラム全体は になり(F C)
ます。またはより JavaScript: F(C);
. C
関数のように動作します。C
で が呼び出されていない場合はF
、通常のプログラムであり、 がF
戻ると、の戻りcall-with-current-continuation
値として値を持ちF
ます。しかしC
、 をパラメータV
で呼び出すと、プログラム全体が再び変更されます。プログラムは呼び出されたときの状態に戻ります。call-with-current-continuation
しかし、今call-with-current-continuation
では値が得られます。V
. そして番組は続く。
例を見てみましょう。
(define (f return)
(return 2)
3)
(display (f whatever)) ;; 3
(display (call-with-current-continuation f)) ;; 2
(display (call-with-current-continuation (lambda (x) 4))) ;; 4
原因の最初のdisplay
出力。3
しかし、2番目のdisplay
出力2
. なんで?
それに飛び込みましょう。
評価するとき(display (call-with-current-continuation f))
は、最初に評価し(call-with-current-continuation f)
ます。プログラム全体が次のように変更されることがわかっています。
(f C)
の定義を考えると、f
があり(return 2)
ます。を評価する必要があり(C 2)
ます。そんな時、continuation
呼ばれる存在。したがって、プログラム全体を元に戻します
(display (call-with-current-continuation f))
しかし今、call-with-current-continuation
価値があります2
。したがって、プログラムは次のようになります。
(display 2)
陰陽パズル
パズルを見てみましょう。
(let* ((yin
((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))
(yang
((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))))
(yin yang))
もっと読みやすくしましょう。
(define (id c) c)
(define (f cc) (display #\@) cc)
(define (g cc) (display #\*) cc)
(let* ((yin
(f (call-with-current-continuation id)))
(yang
(g (call-with-current-continuation id))))
(yin yang))
プログラムを脳内で実行してみましょう。
ラウンド 0
let*
yin
最初に評価させてください。yin
は
(f (call-with-current-continuation id))
ですから、まず評価し(call-with-current-continuation id)
ます。それは現在の環境をパックC_0
し、タイムラインの他の継続と区別するためにそれを呼び出し、まったく新しい宇宙に入ります: id
. しかし、id
ただ返しますC_0
。
何であるかを覚えておく必要C_0
があります。C_0
は次のようなプログラムです。
(let* ((yin
(f ###))
(yang
(g (call-with-current-continuation id))))
(yin yang))
###
はプレースホルダーであり、将来、取り戻される値によって埋められますC_0
。
しかし、id
ただ返しますC_0
。を呼び出しませんC_0
。呼ばれたら、C_0
の宇宙に入ります。しかし、そうではなかったので、評価を続けyin
ます。
(f C_0) ;; yields C_0
f
のような関数ですがid
、出力するという副作用があり@
ます。
したがって、プログラムの出力@
と letyin
は になりますC_0
。これでプログラムは次のようになります
(let* ((yin C_0)
(yang
(g (call-with-current-continuation id))))
(yin yang))
評価後yin
、評価を開始しyang
ます。yang
は
(g (call-with-current-continuation id))
call-with-current-continuation
ここで、 という名前の別の継続を作成しますC_1
。C_1
は:
(let* ((yin C_0)
(yang
(g ###)))
(yin yang))
###
はプレースホルダです。この継続では、yin
の値が決定されることに注意してください (それが何をするかlet*
です)。yin
の値がここにあると確信していC_0
ます。
(id C_1)
であるためC_1
、yang
の値は
(g C_1)
g
出力するという副作用があり*
ます。したがって、プログラムはそうします。
yang
の値は現在C_1
です。
これまでに表示した@*
したがって、次のようになります。
(let* ((yin C_0)
(yang C_1))
(yin yang))
yin
との両方yang
が解かれるので、 を評価する必要があり(yin yang)
ます。これは
(C_0 C_1)
ホーリー SH*T!
しかし、最後に、C_0
呼び出されます。だから私たちはC_0
宇宙に飛び込み、これらのたわごとをすべて忘れます。私たちは二度とこの宇宙に戻ることはありません。
ラウンド1
C_0
バックで取るC_1
。プログラムは次のようになります (何C_0
の略か忘れた場合は、戻って確認してください)。
(let* ((yin
(f C_1))
(yang
(g (call-with-current-continuation id))))
(yin yang))
yin
ああ、の値はまだ決定されていないことがわかりました。だから評価する。を評価する過程で、 asの副作用yin
を出力します。そして、私たちは今であることを知っています。@
f
yin
C_1
評価を開始し、再びyang
遭遇call-with-current-continuation
しました。私たちは実践されています。C_2
以下を表す継続を作成します。
(let* ((yin C_1)
(yang
(g ###)))
(yin yang))
そして、a*
をg
実行中として表示します。そして、私たちはここに来ます
(let* ((yin C_1)
(yang C_2))
(yin yang))
したがって、次のようになりました。
(C_1 C_2)
あなたは私たちがどこに行くのか知っています。C_1
さんの宇宙に行きます。記憶から思い出す (または Web ページからコピーして貼り付ける)。それは今です:
(let* ((yin C_0)
(yang
(g C_2)))
(yin yang))
C_1
の宇宙では、yin
の値が決定されていることがわかっています。そこで、評価を開始しyang
ます。私たちが実践しているように、それが表示され、次のようになることを直接お伝えします*
。
(C_0 C_2)
これで印刷が完了し、を使って宇宙に@*@**
行きます。C_0
C_2
ラウンド2
yin
私たちが慣れているように、「@」を表示し、 isであり、次を表すC_2
新しい continuation を作成することをお伝えします。C_3
(let* ((yin C_2)
(yang
(g ###)))
(yin yang))
を表示する*
と、yang
はC_3
になり、
(C_2 C_3)
そして、私たちは続けることができます。しかし、ここでやめておきます。陰陽パズルの最初のいくつかの出力が何であるかを示しました。
数が*
増える理由は?
今、あなたの頭は詳細でいっぱいです。私はあなたのために要約を作ります。
簡単にするために、Haskell のような構文を使用します。とcc
の略ですcall-with-current-continuation
。
#C_i#
で囲まれている場合#
は、継続がここに作成されていることを意味します。;
出力を意味します
yin = f cc id
yang = g cc id
yin yang
---
yin = f #C_0# ; @
yang = g cc id
yin yang
---
yin = C_0
yang = g #C_1# ; *
yin yang
---
C_0 C_1
---
yin = f C_1 ; @
yang = g #C_2# ; *
yin yang
---
C_1 C_2
---
yin = C_0
yang = g C_2 ; *
yin yang
---
C_0 C_2
---
yin = f C_2 ; @
yang = g #C_3#; *
yin yang
---
C_2 C_3
---
yin = C_1
yang = g C_3 ; *
yin yang
---
C_1 C_3
---
yin = C_0
yang = g C_3 ; *
yin yang
---
C_0 C_3
注意深く観察すれば、次のことは明らかです。
- 宇宙はたくさんありますが(実際には無限大です)、
C_0
によって始まった唯一の宇宙ですf
。その他は によって開始されg
ます。
C_0 C_n
常に新しい継続を行いC_max
ます。実行されていないC_0
最初の宇宙だからです。g cc id
C_0 C_n
も表示@
。C_n C_m
which n is not 0 が表示されます*
。
- 時間ごと
C_0 C_n
に、プログラムはC_0 C_n
@*@**@***...
少し数学
(n != 0) がすべての継続の中で最大の番号であると仮定し、次にC_0 C_n
呼び出されます。
仮定:C_0 C_n
が呼び出されたとき、C_n
は現在の最大番号付き継続です。
今は次のように作成されC_0 C_n
ます:
yin = f C_n ; @
yang = g #C_{n+1}#
yin yang
したがって、次のように結論付けます。
定理 I.C_0 C_n
が呼び出されると、 が である継続 が生成されyin
ますC_n
。
次に、次のステップですC_n C_{n+1}
。
yin = C_{n-1}
yang = g C_{n+1} ; *
yin yang
その理由yin
はC_{n-1}
、作成時に定理 IC_n
に従ったからです。
そして、C_{n-1} C_{n+1}
が呼び出され、 がいつ作成されるかがわかり、定理 IC_{n-1}
にも従いました。だから私たちは持っています。C_{n-2} C_{n+1}
C_{n+1}
インバリエーションです。したがって、2 番目の定理があります。
定理 II。とC_n C_m
を呼ぶn < m
とn > 0
となりC_{n-1} C_m
ます。
そして、手動でチェックしC_0
C_1
C_2
C_3
ました。彼らは仮定とすべての定理に従います。そして、私たちは最初@
にどのよう*
に作成されるかを知っています。
したがって、以下にパターンを記述できます。
C_0 C_1 ; @ *
C_[1-0] C_2 ; @ * *
C_[2-0] C_3 ; @ * * *
...
厳密ではありませんが、次のように書きたいと思います。
QED