“The Seasoned Schemer”メモ
こちらもメモを取っていく。
Guy L. Steel Jr. の {Fore,After}word が見られる。これ Lisp っぽいなと思う。
以下、ネタバレ注意!
この格言って起源がはっきりしてないのですね。ちょっと検索しただけなのだけれども、よくわからない。
If you give someone a fish, he can eat for a day.
If you teach someone to fish, he can eat for a lifetime.
他にも思い付くかしらん?今のところこれで快適だよ。というようなお互い軽い感じのやり取りだと思う。(答えなさいみたいに堅い感じがない) ‘old-shoe’ 履き慣れた靴や、気楽につきあえる人という意味があるらしい。
Can you think of a better name for U
This should be an old shoe by now.
ちょっと休憩しなよ、ということなのだろう、食べ物ブレイクじゃないのは何でだろう、とどうでも良い疑問も湧く。それと、 Duane さんは挿し絵の作者です。
きっちり理解できたかを確認して欲しいタイミングだよ、というようなことなんだろうなと思う、話も切り替わるし。
(This would be a good time to count Duane’s elephants.)
"The only difference between men and boys is the cost of their toys."
set! という新しいおもちゃの紹介、というのが自分なりの解釈。プロペラ機というおもちゃ。
15. The Difference Between Men and Boys…
食べる人・もの的な単語がぽんぽん出てきます。
最後に (glutton 'garlic) を評価していて、その上で、もうガーリックはいらないよ、といっているのに、わざわざskordaliaというやつをオススメしてくる。そして、最後にしれっとレシピを引用してくるあたり、この本ならではのらしさが発揮されていると思います。
(define gourmet …) ;;グルメ、美食家
(define gourmand …) ;;大食漢
(define omnivore …) ;;雑食動物
(define gobbler …) ;;ガツガツ食べるもの
(define nibbler …) ;;かじるもの
(define glutton …) ;;大食家、とにかく熱中する人
この歌。
私の適当な解釈だけれども、以下のような筋なんだろうと。
象を欲しい貧しい人がいて、王様は、象を与えた。象は日がな一日食べ続ける…もっと干し草を。と、その人は泣き叫ぶ。
羨望の眼差しを受けて象に乗って町を歩きたかったのだけれども、象を食わせなきゃあならない。稼ぎはエサにどんどん費やされていく…
ある日、王様に、象を引き取ってくれるなら奴隷にでもなる、と訴えて、ようやく象を持っていることから逃れることができた。
さて、この悲しい歌の教訓はなんだろうか?お金が無いなりに充足しておけ?違う。これは君にも関係あることだよ。例えパイが飛んで来たとしても、冷静に対処しよう。食べちゃいけないものかもしれないし。
言い訳抜きに、幸運はつかみ取るんだ。言い訳抜きに、運命を受け入れるんだ。かの不幸な人のようになってはならない。
最後の言いまわし、 take と comply は、単語だけを見るとどちらも、苦労無く取ること従うこと、と解釈できるものの、後に続いている without 〜 を受けて、言い訳できないくらいにやること。自分の方から進んで徹底的にやる、というニュアンスが感じられた。
パイの件りは、 take a pie when pie is passed 、幸運をつかめ、というやつだろうと思う、パイ=幸運の比喩。 And a steady diet Even of pie, Pall on one’s pal-ate may! というのは、隣りの芝が青く見えたとしても、それは普通なんだよ、ということかな。ただここは、 〜Even of pie だけで見ることもできて、例えパイが飛んできたとしても普通に対処しようよ、と。で更に続けて、たとえそれが他の人に渡っても気にすることはない、というようにも解釈できると思います。
せっかく象をもらったのに、というのと、とにかく象をもらった・象のせいにして、という両方を悪い方に働かせてしまって、身を持ち崩してしまった、かの人のようになってはならない。というようなこと、というのが私の解釈。(象をもらう、というのを宝くじに当たる、として読み替えられるかな)
この歌がここに出てくる理由は、せっかく Y! を知ったのだから、 Y との違いを知ることで、更に両方の理解が深まる、というようなこと、と解釈しました。ちゃんと自分のものにすれば、当然どちらも使いこなせるようになるだろう。ここで自分のモノにしなかったとしたらば、両方とも使いこなせなくなっちゃうかもよと言っている、と。
あと歌詞の中に this sad song って出てくるのが、自分を参照する、という意味の共通点があるのも印象的でした。
For that elephant ate all night,
And that elephant ate all day;
Do what he could to furnish him food,
The cry was still more hay.
— John Cheever Goodwin
この章でやっている cons の評価回数を調べようとしたりで、コストは知ろうとしているよ、ということを言いたいのかなと思う、逆説的に。で、この一ページで、 Rs に保持したり、それを検索したりしていることにもコストがかかっていることを読者に認識させようとしている、というのが私の解釈。
But we know the value of food!
it が何を指すのか?ピザはもう沢山だから麺類にしよう、ということだと思う…。
Find a good restaurant that specializes in it and dine there tonight.
これは Scheme 言語を指していて、ここでは特に静的スコープ、のおかげでここで挙げた same? で説明できるってことになるのかな。 bons の中で作っている kdr を操作しているわけで、静的スコープのおかげ、というのを表しているというのが私の解釈。
Thank you, Gerald J. Sussman and Guy L. Steele Jr.
この qk と sl 、まさかとは思ったのだけれども、 quick と slow じゃないかな。というのも kdr をそれぞれ二回・一回だけ、と評価するから。
(qk (lambda (x) (kdr (kdr x)))) (sl (lambda (x) (kdr x))) )
この Wow! から以降の問答の意味わからなかったので、私なりの解釈や補足を入れながら問答の流れを追ってみたいと思います。
Last time: (get-next (quote go))
Wow!
What is so bad about that?
If we had done all of what we intended to do, we would be back where we originally asked what the value of (start-it2 l) would be where
l was …
ちゃんと手順通りにやることをやったとしたら、 (start-it2 l) の値を尋ねた所に戻るはずだったよね。→ じゃあそれから? → 神のみぞ知る。
ここで、"Perhaps 〜"の、何が多分良かったのか、がはっきりわからない。これは、問答する中で動きを確認することに対して、実行はしていなかったのがかえって良かったのかもしれない、と言っていると考えればいいのかな。確認というか、実行したとしたらどうなっただろうかを考えてみましょう、という問だったので良かった、と。(ここで何が良かったのかというのも疑問なのだけれども。)
私なりに解釈したところは、 (start-it2 l) から返っているのか、それとも、 (get-next 'go) から返っているのかは、逐一 REPL から実行していたとしたら違いがわかりずらかったから、というもの。その上で、問答の流れで、それまで fill を呼び出すことで、意図した通りの動きをし続けただろうものが、最後に一転、 (start-it2 l) の所に返ってしまっていただろうことになったのを受けてのことなのかなと考えます。
“The Twentieth Commandment”の、 Then, when you use it, remember to forget. のことがこの問答の言わんとしていることで、 "forget" ってどういうことなのかを説明している、と考えられます。 forget しなかったとしたら、ここで言う (start-it2 l) の方へ返ってしまうことになって、その後は神のみぞ知る状態になっちゃうよ、と。
神のみぞ知るなどと有耶無耶にしてしまうのも納得が行かないので、次のように自分なりの考えを捕足しながら読みました。
six-layers とか four-layers とか、それまでの問答の流れを見るに、関数から値が返ることを、値が定まった後、と、その値を待ちかまえてその後に続くやらなければならないことに渡すということ、に分離しようとしているのではないかな、と考えることができます。待ち構えている継続へ定まった値を渡すということですね。以下のような問答もあることですし。
What would be the value of leave
It would be a function that does whatever is left to do after the value of (start-it2 l) is determined.
(start-it2 l) を呼び出すと (start-it2 l) を待っている継続が leave に保存されます。(この時点で、 leave を呼ぶことと、 (start-it2 l) から返ること、とが等価です。)
その後、 (get-next 'go) を呼び出すと今度は (get-next 'go) を待っている継続が leave に上書きされます。(今度は、 leave を呼ぶことと、 (get-next 'go) から返ること、とが等価です。)
これまでも、継続を呼び出すことはforgetすることと表現している一方で、継続を呼び出さなかった時、言い替えるとforgetしなかった場合、どの継続へ処理が移っていくのかということを、この問答で気付いてもらおうというのもねらいなのかな、というのが私なりの解釈です。
というわけで、 (get-next 'go) を呼んだ後で leave を呼ばないという、問答と同様なことをホントにやったとしたら実際に何が起こるのかを自分なりに考えてみたいと思います。
まず、 start-it2 を呼ぶ。
gosh> (start-it2 '(foo)) foo gosh>
ここで、 fill には、 waddle を呼んで (start-it2 '(foo)) を待っている以下のような継続が保存されていると考えられます。
(lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (_v) (waddle '())) x)))
REPL の print から次のループへという意味で PRINT-THEN-REPL にしました。
gosh> (cons 'discard (get-next 'go)) () gosh>
これは the final value is () ということを言っていることなのだと考えます。( (start-it2 '(foo)) がもともと作っていた継続に処理が移っていく。つまり、 get という名に値しない、ということを言っていると考えられます)。
何が起きているのか自分なりに納得するために、説明を加えてみます。 REPL へ入力した (cons 'discard (get-next 'go)) の、 (get-next 'go) の箇所での継続は以下のようなものだと考えられます。
(lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (v) (cons 'discard v)) x)))
ここに (get-next 'go) の結果が渡されるので、わざとらしくそのまま埋め込んだとすると以下のようになります。
((lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (v) (cons 'discard v)) x))) (get-next 'go)) ;; (get-next 'go) は fill の呼び出しなのでその内容を埋め込むと↓ ((lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (v) (cons 'discard v)) x))) ((lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (_v) (waddle '())) x))) 'go)) ;; さらに、これ↑はよく見ると、関数呼び出しの連なりの↓と等価 ((lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (v) (cons 'discard v)) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (_v) (waddle '())) x))))) 'go)
順に、 waddle → PRINT-THEN-REPL 呼び出されています。 (cons discard v) は呼び出されていないので、 "()" が表示されて REPL のプロンプトが表示される、ということになります。
さて試しに、最初に REPL から gosh> (cons 'start-it2 (start-it2 '(foo))) と入力した場合には、以下のような結果となります。
gosh> (cons 'start-it2 (start-it2 '(foo))) ;; (1) (start-it2 . foo) gosh> (cons 'discard (get-next 'go)) (start-it2) gosh>
ここでも REPL から gosh> (start-it2 '(foo)) と入力したのと同様、 fill には、 waddle を呼んで (start-it2 '(foo)) を待っている以下のような継続が保存されていると考えられます。違いは、 (PRINT-THEN-REPL v) の前に (cons 'start-it2 v) がある点です。
(lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (v) (cons 'start-it2 v)) ;;<- (1) の時点で、 (start-it2 '(foo)) ((lambda (_v) (waddle '())) ;; を待ち構えている継続と同じ。 x))))
(1)の時点で (start-it2 '(foo)) を待ち構えている継続は以下。
(lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (v) (cons 'start-it2 v)) x)))
上の(1)からの REPL での実行例ではどちらも最終的に、この(1)の時点で (start-it2 '(foo)) を待ち構えている継続が呼び出されている、ということがわかります。
the final value is () と言っているように、最終的にどの PRINT-THEN-REPL が値を印字して次の入力を評価しようとしているか、を探っていくことで、一見、不可解に思えるような挙動も自分なりに納得できるようになりました。
さて、ここで問答は、 (get-next 'go) を待っている継続の方( get-next の呼び出しにより、 leave に保存される継続)を呼び出したいとすると、 (leave '()) をどこかで呼ぶ必要があるということになる。そこで、 get-first を以下のように定義してこれを使えば良い。という問答が続いていきます。
(define (get-first l) (let/cc here (set! leave here) (waddle l) (leave '())))
先程の start-it2 で見たのと同様に、 fill に保存される継続を考えてみます。
gosh> (get-first '(foo)) foo gosh>
この時点で、 fill には、 leave の呼び出しを含む継続が保存されていると考えられます。
(lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (_v) (leave '())) ((lambda (_v) (waddle '())) x))))
(waddle '()) の結果を捨てて、 (leave '()) を呼んでトップレべル( (get-first '(foo)) を待っていたもともとの継続)へ、というような継続が保存されている。この中の leave は get-next を呼び出した時点で、今度は get-next の結果を待ちかまえる継続に書き換えられる。こんな風に。
gosh> (cons 'ok (get-next 'go)) (ok) gosh>
leave には次のような継続が保存されていると考えられます。
(lambda (x) ((lambda (v) (PRINT-THEN-REPL v)) ((lambda (v) (cons 'ok v)) x)))
直接 leave を評価してみるとこのことを確認することができます。
gosh> (leave '()) (ok) gosh> (leave 'cdr) (ok . cdr)
継続の呼び出しを使って、所謂、 "get-next から返る" といういかにも get の名にふさわしい挙動をさせようとすると、掟20の to forget が重要となります。この挙動に限らずなのだけれども、どの継続に処理を移して行くことになるのか特に注意を払う必要がある、ということもこの問答の言わんとしていることなのかな、というのが私なりの解釈です。
let を消す、二回目。一回目は、 beglis の所で出てきて、その時はやり方はもう知っているから、だったけれども、今回は、 Thank you, John Reynolds 。
ここで注記に、これなら Scheme で動くよって書いてあって、なぜあえて書いてあるのかはっきりわからない。一回目との対比で言うと、ここで言う Scheme は現に今実装しようとしている Scheme でも動く、ということを言っているのかな。
Do we need ((lambda (val) ...) ...) here too?
Yes,¹ here and in beglis.
Thank you, john Reynolds.
----
¹ S: So that our definitions always work in Scheme.
最後。これはピザを模した奴のとは異なり形は関係ありません、といったとこかしらん?
†No, you don’t have to eat parentheses.