kb84tkhrのブログ

何を書こうか考え中です

Scheme修行(13) 第20章 店には何がある?

ついにScheme修行も最終章
あいかわらずよく意味の分からないタイトルで締めてくれます

第10章とおなじく、ここではschemeインタプリタを作ります
今回はdefineが(letccも)実装されますのでそのままセルフで自分自身を実行することができるはず

今回もなぜかテーブルの定義から入ります 好きですね
今回はリストではなく関数(というかクロージャ)でテーブルを作るそうです
関数でテーブルを作る利点は何でしょう?

まずは空っぽのテーブルを作ります

(define the-empty-table
  (lambda (name)
    (car (quote ()))))

↓のように書いてありますのでとりあえず版のようです

Scheme手習い」では次のようにしました。
(car (quote ()))

手習いの時はなんだろうこれと思ってたものですが
今回すっきり納得できるでしょうか
期待です

検索と追加です

(define lookup
  (lambda (table name)
    (table name)))

(define extend
  (lambda (name1 value table)
    (lambda (name2)
      (cond ((eq? name2 name1) value)
            (else (table name2))))))

面白いというか不思議というか新鮮な書き方ですが
なるほどなんか動きそうですね
使い方はこんな感じになるでしょうか

> (define test-table the-empty-table)
> (set! test-table (extend 'name 'taro test-table))
> (set! test-table (extend 'nationality 'japan test-table))
> (lookup test-table 'name)
'taro
> (lookup test-table 'nationality)
'japan
> (lookup test-table 'gender)
car: contract violation
  expected: pair?
  given: '()

このとき、test-tableはこんな風に育っています
変数名がかぶるのでちょっとわかりづらいですが

(lambda (name)
  (car (quote ())))
    ↓
(lambda (name2)
  (cond ((eq? name2 'name) 'taro)
        (else ((lambda (name)
                 (car (quote ()))) name2))))
    ↓
(lambda (name2)
  (cond ((eq? name2 'nationality) 'japan)
        (else ((lambda (name2)
                 (cond ((eq? name2 'name) 'taro)
                       (else ((lambda (name)
                                (car (quote ()))) name2)))) name2))))

確かに名前を与えると値を返す関数になっています

ところで(lookup table e)が(table e)と同じ意味なんであればlookupて必要なんですかね
何かと形がそろってる必要があるのかな?
必要はないけどそろえたい?
あとで何か気付きがあるでしょうか

テーブル作ったと思ったら今度は最上位のvalueを定義します ただし仮
ボトムアップだったりトップダウンだったり

(define value
  (lambda (e)
    (cond ((define? e) (*define e))
          (else (the-meaning e)))))

早速目玉機能であるdefineが現れました
完全に特別扱いです
the-meaningとやらの中では扱えないのかな?

defineです
どんな高尚なことをするのかと思ったら

(define *define
  (lambda (e)
    (set! global-table
          (extend (name-of e)
                  (box (the-meaning (right-side-of e)))
                  global-table))))

覚えておくだけかよ!
なぜ特別扱いなんだろう
the-meaningを呼んでいるとはいえthe-meaningの中で扱えないことはない気がしますが

覚えておく先はglobal-table決め打ちなんですね
ブロック構造なんかはこれでも実現できるんでしょうか
それとも実装しない?

それよりboxってなんでしょうか
入れものっぽい雰囲気は醸しだしてますが

*defineが名前と値でテーブルを拡張すると、その名前はいつも「同じ」値を
表すようになりますか。

いいえ。前に何回か見たように、名前が表すものは(set! ...)を使って変更できます。

*defineがテーブルを拡張する前に値をboxに入れるのは、それが理由ですか。

それが理由らしいです
set!するためのしくみのようです

boxを作り、値を設定し、値を取り出す関数です

(define box
  (lambda (it)
    (lambda (sel)
      (sel it (lambda (new) (set! it new))))))

(define setbox
  (lambda (box new)
    (box (lambda (it set) (set new)))))

(define unbox
  (lambda (box)
    (box (lambda (it set) it))))

短いですけど複雑ですね
まずは使ってみましょうか

> (define testbox (box 'a))
> (unbox testbox)
'a
> (setbox testbox 'b)
> (unbox testbox)
'b

いつものように追いかけてみましょう
defineあたりはてきとうにごまかしつつ

(define textbox (box 'a))
(define testbox (lambda (sel) (sel 'a (lambda (new) (set!

あれ?
(set! it new)のitってどうなるの?
今までそこはただ名前が書いてあるものだと思ってたけど、変数だったらどうなるの?
(set! 'a new)じゃ変だし・・・

そこはあくまでもitっていう名前で、変数じゃないってことかな?

> (let ((name 'aaa)) (define name 'bbb) name)
'bbb

ほんとに確かめになってるかどうか微妙ですけど
そんな感じなのでとりあえずそういうことにしておいて進みます

(define textbox (box 'a))
(define testbox (lambda (sel) (sel 'a (lambda (new) (set! it new)))))

(unbox testbox)
(unbox (lambda (sel) (sel 'a (lambda (new) (set! it new)))))
((lambda (sel) (sel 'a (lambda (new) (set! it new)))) (lambda (it set) it))
((lambda (it set) it) 'a (lambda (new) (set! it new)))
'a

ふむふむよい感じ
続いてsetbox

(setbox testbox 'b)
(setbox (lambda (sel) (sel 'a (lambda (new) (set! it new)))) 'b)
((lambda (sel) (sel 'a (lambda (new) (set! it new)))) (lambda (it set) (set 'b)))
((lambda (it set) (set 'b)) 'a (lambda (new) (set! it new)))
((lambda (new) (set! it new)) 'b)
(set! it 'b)

(unbox testbox)
(unbox (lambda (sel) (sel 'a 

・・・
ここの'a'aのままじゃ何にもなってませんね
ということはどうなんでしょう
結局itはitのままということでしょうか

仮引数のitと、boxに入ったitを区別するためにbox内のitは<it>と書くことにして
最初からやってみます

(define textbox (box 'a)) ; <it>は'aに
(define testbox (lambda (sel) (sel <it> (lambda (new) (set! <it> new)))))

(unbox testbox)
((lambda (sel) (sel <it> (lambda (new) (set! <it> new)))) (lambda (it set) it))
((lambda (it set) it) <it> (lambda (new) (set! <it> new)))
<it>
'a

(setbox testbox 'b)
((lambda (sel) (sel <it> (lambda (new) (set! <it> new)))) (lambda (it set) (set 'b)))
((lambda (it set) (set 'b)) <it> (lambda (new) (set! <it> new)))
((lambda (new) (set! <it> new)) 'b)
(set! <it> 'b)

(unbox testbox)
((lambda (sel) (sel <it> (lambda (new) (set! <it> new)))) (lambda (it set) it))
((lambda (it set) it) <it> (lambda (new) (set! <it> new)))
<it>
'b

いちおう動作を追うことができた感じです
itを単純に'aに置き換えるやり方はもはや無理ってかんじですね
の中身を覚えておく、というのはつまりクロージャを覚えておくってことなので
クロージャを思い浮かべながら追っていく必要がありそうです

ところでわざわざboxなんてものを使わなくても、直接set!してはいけないんでしょうか
上の例のようなことをするだけなら直接set!で十分です

> (define it 'a)
> it
'a
> (set! it 'b)
> it
'b

・・・

でも例えば(define it '(a b))としたときの'bのところ「だけ」を'cに置き換えたい、
ってときに困るか

まるごと(set! it '(a c))とはできても
(set! (cadr it) 'c)みたいなことはできないし書きようがありませんね
そういえばCommon Lispではそういうことができるsetfってのがあるらしいです
関係あるかな

boxを使えばそういうことができます

> (define it (cons (box 'a) (cons (box 'b) (quote ()))))
> (unbox (cadr it))
'b
> (setbox (cadr it) 'c)
> (unbox (cadr it))
'c

そういうことでしょう