Scheme修行(6) 第15章 大人と子供の違い・・・・・・
代入です
set!
はdefine
された変数に値を代入します
と書くと先生に怒られるかもしれません
「名前xはaを参照しています」という言い方はここが初めてかな?
こちらが正しい表現なんでしょう
さっきまでaを参照していたxに、今度はbを参照させるというのがset!
と
いうことになります
set!
とdefine
set!
はすでにdefine
された名前についてのみ使用可能です
実際に、定義されてない名前をset!
しようとするとエラーになります
逆に、define
済みの名前でもう一度define
しようとするのもエラーかと思いきや成功
> (define x
(cons (quote chicago)
(cons (quote pizza)
(quote ()))))
> x
'(chicago pizza)
> (set! x (quote gone))
> x
'gone
> (set! y (quote yet))
set!: assignment disallowed;
cannot set variable before its definition
variable: y
> (define x (quote again))
> x
'again
しかしこういうファイルを作って...
(define x
(cons (quote chicago)
(cons (quote pizza)
(quote ()))))
x
(set! x (quote gone))
x
(define x (quote again))
x
実行するとエラーになりました
module: duplicate definition for identifier in: x
REPL上では動作が違うってこと?
REPL上でdefine
しなおすことができないと試行錯誤できないからそれでいいのかな
define
で値を変更できるようにすればset!
はいらなくなりそうですが、
あえて好き勝手できないようになってるんでしょうね
未定義の変数にset!
できないのも同様なんでしょう
値
(define ...)
と(set! ...)
は値を持ちません
右側の欄がときどき空白になってて
初めはなんだろうこれと思いましたが、値がないということです
しつこいほど聞かれます
値がないということがそれほど重要なんでしょうか
define
やset
が、定義/代入された値を返すという仕様にするのもアリな気もしますが
値を返さないことにした、という選択が大事なんでしょうね
なぜでしょう
安全だから?
章の冒頭で
しかし、これからは時として定義式の値についても触れる必要があります。
と言ってますしね
触れても値はない、って繰り返すだけですが
何かが読み取れてない気がします
変わる
こう定義して
(define x (quote skins))
(define gourmet
(lambda (food)
(cons food
(cons x (quote ())))))
(gourmet (quote onion))
を評価すると当然(onion skins)
ですが
(set! x (quote rings))
した後もう一度(gourmet (quote onion))
を評価すると
なんと!
(onion rings)
になります
いやそれも当然っぽいんですけど
ただ、手習いのインタプリタで作ったクロージャだと
クロージャができた後は値の変わりようがない気がするので
当然ともいいきれないなあと
状態
現時点での変数の値(正確に言うと、「名前が参照する値」?)を覚えておかないといけないので
本が読みづらくなりました
やっぱり状態を持つのはよくないですね!(頭がメモリ不足
隠す
最後に食べたものを覚えておけるこんな関数を作ります
(define gourmand
(lambda (food)
(set! x food)
(cons food
(cons x (quote ())))))
もうひとつ作ります
(define dinerR
(lambda (food)
(set! x food)
(cons (quote milkshake)
(cons food (quote ())))))
両方でx
という名前を使っているために他方の関数を呼ぶと
x
の値が変更されてしまいます
他の関数からの影響を受けないようにするため、こんな風にしてx
を隠します
(define omnivore
(let ((x (quote minestrone)))
(lambda (food)
(set! x food)
(cons food
(cons x (quote ()))))))
(omnivore (quote (bouillabaisse))
を評価しても
関数の外側で定義したx
は影響を受けません
別の関数がx
という名前を使っても影響を受けることはありません
staticなローカル変数といった感じです
しかし残念ながら、x
が隠されてしまっているので最後に食べたものを
確認することはできなくなってしまいました
関数内部で前回の値を使うような例になっていればまだ役に立っているように見えるのですが
この辺りはきっと後で解決されるのでしょう
ominivore
の値は何ですか、という問が繰り返され、結局関数です、ということになります
lambda
なんだから関数なのはわかっているんですが
let
の中に入ってても同じか、と聞いているのかなあ
詳しく言うと、lambda
で定義した関数とlet
で定義したx
を含むクロージャ、と
なると思うんですがそう言わないということは?
手習いを読んでいればその説明で飲み込めないことはないと思うんですが
第16の戒律
(let ...)
で定義された名前に対してのみ(set! ...)
を使うべし。
let
で定義するのはいいとして、minestrone
みたいな捨てられるだけの値を書くのは
シャクに触るので書かずにすませたいところですが・・・
間違い
これはうまくいきません。
(define nibbler
(lambda (food)
(let ((x (quote donut)))
(set! x food)
(cons food
(cons x (quote ()))))))
nibbler
を評価するたびにx
が新しく定義されるので
前回の値を覚えておく役にたっていません
といっても評価した値は変わらないので、x
覚えてないよね、というのは
脳内で確かめるしかありませんが
第17の戒律(予備版)
(let ((x ...)) ...)
に 対して(set! x)
を用いる際には、それらの間に少なくとも1つの(lambda ...
を置くべし。
自分的にはlambda
の外側にlet
を置け、の方がピンときますが気分的なものでしょう
swap
代入を使って値を入れ替える関数を作ります
(define chez-nous
(lambda ()
(set! food x)
(set! x food)))
失敗です
わざとらしいですね
こうです
(define chez-nous2
(lambda ()
(let ((a food))
(set! food x)
(set! x a))))
第18の戒律
(set! x ...)
はx
が参照する値がもはや必要ないときにのみ使うべし。
そりゃそうですね
まとめ
大人と子供の違いって?