テーブルから名前の検索

久しぶりにThe Little Schemer。
9章までは終わってたから、最終章の10章をば。


いろんな人が書いたレビューなどによると、この章ではSchemeを用いてScheme処理系を実装するのだそうだ。
The Little Schemerって、「この章の目標は○○が出来るようになる事ですよ」的なことがぜんぜん書いてないから、途中で「...ふーん。で、何が言いたいん?」って気持ちになることが多い。


さて、本題。


まず、2個のリストからなるリストのうち、最初のリストがセットで、かつ2つのリストの要素数が等しいものを「エントリー」と定義する。
セットに関しては111ページを参照のこと。
つまり、エントリーを例示すると

((One Two Three) (1 2 3))

((foo bar baz) (hoge hoge hoge))

((foo bar) ((this is) (a sample entry)))

など。

nameというセットとvalueというリストからエントリーを作成するには、以前作成したbuild関数が利用できる。
build関数の定義は119ページを参照。


さて、

(define name (quote bar))
(define entry (quote ((foo bar baz) (hoge fuga piyo))))

としたとき、

(lookup-in-entry name entry)

の値が

fuga

になるようなlookup-in-entry関数について考えてみようか、と。
つまり、セットからnameを探し出して、見つかったらリストの同じ位置にあるS式を返す関数ね。

もし、ここでnameの定義が

(define name (quote qux))

だったら、セットの中には見当たらない。こんなときに

(lookup-in-entry name entry)

が何に評価されるかはプログラマに任せたい、とのことだ。

んで、それをどうやって実現できるか?
答えは、引数を1個増やして、そいつで「見つからなかった場合に評価される関数」を渡してやる、という方法。
ちなみに、その「見つからなかった場合に評価される関数」は1つの引数をとる関数だそうな。
引数名がnameとか言ってるから、見つからなかったときのエラーメッセージかなんかに利用するのかな?


ここでlookup-in-entry関数の定義が示される。

(define lookup-in-entry
  (lambda (name entry entry-f)
    (lookup-in-entry-help name (first entry) (second entry) entry-f)))

first関数とsecond関数の定義は119ページ。
しかし、lookup-in-entry-helpは未定義。
lookup-in-entry-helpを実装しろ、と。

答えは

(define lookup-in-entry-help
  (lambda (name names vals entry-f)
    (cond
     ((null? names) (entry-f name))
     ((eq? name (car names)) (car vals))
     (else (lookup-in-entry-help name (cdr names) (cdr vals) entry-f)))))

だと。
lookup-in-entry-helpというヘルパをかますことで、何が嬉しいのだろうか。
ただ、ヘルパが無いとちょっと書きにくいな、ってことは分かる。


さて、ここでエントリーのリストを「テーブル」と定義する。
テーブルの例は

()

(((foo bar baz)
  (hoge fuga piyo))
 ((foo qux)
  ((this is) (a sample of table))))

など。

テーブルを拡大するextend-table関数は

(define extend-table cons)

と定義できる。

さっきはエントリーの中から名前を探して、それに対応するS式を取り出すlookup-in-entryを作ったけど、
今度はテーブルの中から探す関数を作ろう、と。
つまり、

(define name (quote entree))
(define table
  (quote (((entree dessert)
           (spaghetti spumoni))
          ((appetizer entree beverage)
           (food tastes good)))))

のとき、

(lookup-in-table name table table-f)

の値が

spaghetti

になるようなlookup-in-table関数。
この例だと、entreeは2箇所見つかるけど、最初に見つかった方を採用するから答えはspaghettiになる、と。

さて、定義は

(define lookup-in-table
  (lambda (name table table-f)
    (cond
     ((null? table) (table-f name))
     (else
       (lookup-in-entry
         name
         (car table)
         (lambda (name)
           (lookup-in-table name (cdr table) table-f)))))))

となるそうな。
lookup-in-entryの時と同じように、「テーブルに見つからなかったときに何をするか」を引数で渡している。

lookup-in-table関数で自分が難しいと感じた箇所は、

(lambda (name)
  (lookup-in-table name (cdr table) table-f))

の部分。
entry-fとしてこの無名関数を渡していて、この部分はいつ評価されるかというと、今見ているエントリーにnameが見つからなかった場合ね。
んで何をやっているか具体的にいうと、次のエントリーにlookup-in-entryを適用しているわけね。(もちろんnameとtable-fは不変)


今日はここまで。


次にやるべきことを引数で渡してやる、ってのは継続という考え方らしい。
僕の場合、継続と再帰が絡むと特に難しく感じる。