陰陽パズルはスキームで書かれています。あなたが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を出力します。そして、私たちは今であることを知っています。@fyinC_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_0C_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_mwhich 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