読者です 読者をやめる 読者になる 読者になる

kb84tkhrのブログ

何を書こうか考え中です

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! ...)は値を持ちません
右側の欄がときどき空白になってて
初めはなんだろうこれと思いましたが、値がないということです

しつこいほど聞かれます
値がないということがそれほど重要なんでしょうか

definesetが、定義/代入された値を返すという仕様にするのもアリな気もしますが
値を返さないことにした、という選択が大事なんでしょうね
なぜでしょう
安全だから?

章の冒頭で

しかし、これからは時として定義式の値についても触れる必要があります。

と言ってますしね
触れても値はない、って繰り返すだけですが

何かが読み取れてない気がします

変わる

こう定義して

(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が参照する値がもはや必要ないときにのみ使うべし。

そりゃそうですね

まとめ

大人と子供の違いって?