第10回 Schemeの総合練習(6月19日)

今日の課題

■ 破壊的代入

Scheme は、不純な関数型言語であると言われている。その理由の一つとして、変数に破壊的代入が許されているからである。破壊的代入の方法は次のとおりである。
(define x 10)
(set! x (+ x 1))
x
⇒ 11

2 行目の set! により、破壊的代入が行われている。なお、2 行目を、「(define x (+ x 1))」に置き換えても、Interactions 上では予想通りに動作する。しかし、別の状況では、予想通りに動作しないので、破壊的代入を行うときは、set! を使おう。

■ クロージャ

Scheme では、関数を評価したときの状態が保存されている。たとえば、lambda 式で作られた関数において、引数の変数の値や、内部で定義した変数の値が、関数評価時にはもちろん存在するのだが、評価が終ったあとでも残されているのである。

クロージャとは、「関数の定義と、その計算に必要な変数や関数のまとまりが保持されている関数」のことである。

直観的には、C言語の static 変数を思い浮かべるかもしれないが、Scheme で異なる点は、関数評価のたびに、別物として変数が保存されているところである。たとえば、以下のプログラムを見てみよう。
int counter() {
  static int c = 10;
  c = c + 1;
  return c;
}

C 言語では、counter() を呼出した後も変数 c の値が保存されている。しかし、counter() の実体はただ1つである。

これに対して、Scheme では、counter() の実体が幾つも作ることができる。それは、関数を返す関数により作ることができる。具体例を次に示す。
(define counter
  (lambda ()
    (define c 10)
    (lambda ()
      (set! c (+ c 1))
      c)))

Interactions 上では次のように動作確認をする。
(define x (counter))
(x)
⇒ 11
(x)
⇒ 12
(define y (counter))
(y)
⇒ 11
(x)
⇒ 13
(y)
⇒ 12

x に対応する関数内の変数 c と y に対応する関数内の変数 c は、互いに関係を持っていないことが分る。
話についていっていない人へ
  • (define x (counter)) の意味は理解できていますか?
    まず、counter は関数を返す関数です。 counter を評価すると、関数を返す関数であるという値が返されます。 (counter) を評価すると、何かしら c の値を返す関数であるという値が返されます。 (define x (counter)) は、後者が、x になります。
  • (x) の意味は理解できていますか?
    上記のように x を定義しているので、x は関数です。 (x) を評価すると、コンビネーションを評価しているので、 その関数を計算することになります。 従って、何かしら c の値が返り値となり、11 や 12 のように表示されます。

■ オブジェクト指向プログラミングへの接近

上記の counter の使い方を見て、オブジェクトが見えてきませんか?

counter がクラス、c がインスタンス変数、(define x (counter)) が、x = counter new という関係が見えてくるだろう。また、(define y (counter)) が y = counter new と対応すると見れば、x と y の内部の変数が独立していることは自然に納得できるかもしれない。

物足りないのは、オブジェクトにメッセージを送っていないところである。

そこで、メッセージを受け付けるように変更しよう。受け付けるメッセージは、内部の変数 c の値を 0 にする「'reset」、c の値を 1 つ増やす「'up」の 2 種類を作成しよう。
(define counter
  (lambda ()
    (define c 10)
    (lambda (msg)
      (cond ((eq? msg 'reset) (set! c 0) c)
            ((eq? msg 'up) (set! c (+ c 1)) c) ))))

実行方法は次のとおりである。
> (define a (counter))
> (a 'up)
⇒ 11
> (a 'reset)
⇒ 0
> (a 'up)
⇒ 1

□ 練習問題

問1

上記の counter の実行方法を見ると、何かしら c の値が返されていること、メッセージに引数が無いこと、という特徴が見られる。そこで、次のように改良しよう。

  1. 関数を返す関数にすること
  2. 引数付きのメッセージが扱えること

1 の結果、実行方法を次のように変更しよう。
> (define a (counter))
> ((a 'up))
⇒ 11
> ((a 'reset))
⇒ 0
> ((a 'up))
⇒ 1

ヒント:'reset のときの処理は、(set! c 0) と c の 2 つの評価を行なうことだったが、この 2 つを評価する lambda 式に変更する。

2 については、メッセージとして「set: 数」に相当するものを作りたい。set: により、内部の変数 c を直接的に指定するのである。実行方法は次のようにしよう。
> (define b (counter))
> ((b 'set:) 100)
⇒ 100

問2

問1 の続きである。メッセージとして「step: 数」に相当するものを作ろう。step: により、'up の際に増える値を指定できるようにしよう。
> (define c (counter))
> ((c 'set:) 100)
⇒ 100
> ((c 'step:) 5)
> ((c 'up))
⇒ 105

問3

Collection に相当する関数 collection-class を作ろう。この関数は次のように実行できるものとする。
> (define aBag (collection-class))
> ((aBag 'show))
⇒ (list )
> ((aBag 'empty?))
⇒ #t
> ((aBag 'add:) 1)
> ((aBag 'add:) 2)
> ((aBag 'add:) 3)
> ((aBag 'show))
⇒ (list 3 2 1)  .... 順序は不問
> ((aBag 'get-one))
⇒ 3             .... どの要素でも良いので取り出す
> ((aBag 'show))
⇒ (list 2 1)    .... get-one で取り出した後の残りが表示される

問4

collection-class に、次の機能のメッセージを追加しよう。


(c) 2008.6.17 by tokuhisa