プログラミング再入門

プログラミングをもう一度ちゃんと勉強する読書ノート

Scheme修行 第15章 大人と子供の違い……

この章ではset!を習います。また分かりにくい形でlambdaが作るクロージャに変数を取り込む方法も習います。これを用いると特定の関数からのみ参照出来る、C言語で言う所の関数内static変数に相当する変数が作れます。

ノート:

set!

特に明記されていないが、最初にdefineで名前(変数)を定義する必要がある。set!はあくまで既に定義された変数の値を変更するのに使われる。
一連のやり取りの動作確認をする。

> (define x (cons (quote chicago) (cons (quote pizza) (quote ()))))
> x
(chicago pizza)
> (set! x 'gone)
> x
gone
> (set! x 'skins)
> x
skins
> (define gourmet
    (lambda (food)
      (cons food
            (cons x (quote ())))))
> (cons x (quote ()))
(skins)
> (gourmet (quote onion))
(onion skins)
> (set! x (quote rings))
> (let ((y 'onion))
    (gourmet y))
(onion rings)
> (define gourmand
    (lambda (food)
      (set! x food)
      (cons food
            (cons x
                  (quote ())))))
> (gourmand (quote potato))
(potato potato)
> x
potato
> (let ((w 'rice))
    (gourmand w))
(rice rice)
> x
rice
> (dinerR (quote onion))
(milkshake onion)
> x
onion
> (dinerR (quote pecanpie))
(milkshake pecanpie)
> x
pecanpie
> (gourmand (quote onion))
(onion onion)
> x
onion
> 
> (define omnivore
    (let ((x (quote minestrone)))
      (lambda (food)
        (set! x food)
        (cons food
              (cons x
                    (quote ()))))))
> (let ((z 'bouillabaisse))
    (omnivore z))
(bouillabaisse bouillabaisse)
> (omnivore 'minestrone)
(minestrone minestrone)
> x
onion
> 

確かにグローバルのxには影響していない。

x1は何を参照していますか。(What does x1 refer to?』『minestroneを表しています。(It stands for minestrone.)』は良いとして『では、x1の値は何ですか。(So, what's x1's value?)』『答えはありません。それは架空の名前です。(No answer; it is imaginary.)』の部分は何を表しているのか解釈出来ない。

本の例では分かりにくいが、変数xはlambdaが呼ばれる段階でクロージャの環境として保存され、omnivoreを評価し終わった後でも存在している。

(let …)の後に(lambda …)と構成すると、(define x1 …)、(define omnivore (lambda …))と書き換えている部分でこの事を示そうとしているのかもしれない。

この事を以下の例で示す。

> (define test
    (let ((x 0))
      (lambda (y)
        (if (> y x)
            (set! x y)
            (set! x 0))
        x)))
> (test 1)
1
> (test 2)
2
> (test 1)
0
> 

関数testはxよりも与えられたyが大きい場合にはxにyを再代入、そうで場合はxを0にリセット。何れにしても最後に値としてxを返す。
testを定義した時点ではxは0。(test 1)でxには1が再代入される。(test 2)でxは2になる。そこで(test 1)を評価する。

誤った解釈:

  1. testの定義に従い、letから始める。
  2. xには0が代入され
  3. 関数が作られる

こう考えると(test 1)の結果は1となる筈。

正しい解釈:

  1. testは既に0に初期化された変数xが存在している環境で定義されたある関数に結びつけられている。
  2. 最初に(test 1)、次に(test 2)が評価される事で、変数xは値2を参照している。
  3. この状態で(test 1)を評価すると、定義に従いxには0が再代入される。

globberは、関数定義内で作られる変数xは、別の関数内で作られる変数xとは独立である事を示している。

次の定義ではlambdaで関数を定義する段階では環境として変数xは含まず、関数が評価される度毎に変数xが作られ、0で初期化される。

> (define test2
    (lambda (y)
      (let ((x 0))
        (if (> y x)
            (set! x y)
            (set! x 0))
        x)))
> (test2 1)
1
> (test2 2)
2
> (test2 1)
1
> 

nibblerはlambdaの中にletを作っているので関数内の一時変数を作っている事を示している。
関数の評価が終わると変数xは廃棄される。この事は『nibblerはまだcheeroについて知っていますか。』『いいえ!』の部分で表現されている。

『それで、(let …)は役に立ちましたか。(So, what was the use of (let …)?)』『まったく。(let …)と(set! …)は、その間に(lambda …を入れないで使っても、何かを覚える役には立ちません。(None. If (let …) and (set! …) are used without a (lambda … between them, they don't help us to remember things.)』どことなく訳が変だが、(let …)と(set! …)の間に(lambda …を入れなかった場合には、何も覚えておいてくれない、と解釈する。つまりlambdaを呼ぶ前に変数を作っておかないとクロージャの一部として変数は残らない。

第16の戒律はグローバルな変数は使うなと言う意味で、第17の戒律はその代わりにクロージャに取り込まれる変数を使えと言っている模様。ただし、クロージャに取り込まれる変数はある種オブジェクトのアトリビュートやメンバ変数的ではある物の、外部から明示的に参照出来ない点で分かりにくいバグを作りそうな危険な香りがする。

> x
onion
> (define food (quote none))
> (define glutton
    (lambda (x)
      (set! food x)
      (cons (quote mote)
            (cons x
                  (cons (quote more)
                        (cons x
                              (quote ())))))))
> (glutton (quote garlic))
(mote garlic more garlic)
> food
garlic
> x
onion
> 
chez-nous

chez-nousは所謂swap(のつもり)。

> (define chez-nous
    (lambda ()
      (set! food x)
      (set! x food)))
> food
garlic
> x
onion
> (chez-nous)
> food
onion
> x
onion
> 

当然の結果。

一時変数を使う定義では:

> (define chez-nous
    (lambda ()
      (let ((a food))
        (set! food x)
        (set! x a))))
> (glutton (quote garlic))
(mote garlic more garlic)
> food
garlic
> (gourmand (quote potato))
(potato potato)
> x
potato
> (chez-nous)
> food
potato
> x
garlic
>