プログラミング再入門

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

Scheme手習い 第8章 究極のlambda(その1)

ここでは関数を引数として渡す所謂高階関数を習います。

ノート

rember-f

関数を引数に取り、元々eq?で比較していた部分を引数として渡された関数の適用に変更するのみで、全体の定義は元のremberと変わらない。
示された例を適用してみる。

> (let ((test? =) (a 5) (l '(6 2 5 3)))
    (rember-f test? a l))
(6 2 3)
> (let ((test? eq?) (a 'jelly) (l '(jelly beans are good)))
    (rember-f test? a l))
(beans are good)
> (let ((test? equal?) (a '(pop corn)) (l '(lemonade (pop corn) and (cake))))
    (rember-f test? a l))
(lemonade and (cake))
> 
カリー化

ここまで関数値としては値かリストしか扱って来なかったが、関数本体(最初のlambdaの中身)にlambdaが出てくれば関数値として関数を返す事が出来る。

ここの例では名前は与えられていないが、引数aに適用し、ひとつの引数を取りeq?を使ってaと比較をする関数と言う意味になる。

ここでカリー化の話が出て来る。WikipediaによるとChristopher StracheyがHaskell B. Curryに因んで名付けたが実際に考案したのはMoses SchönfinkelとFriedrich Ludwig Gottlob Fregeであるとの事。ここではSchönfinkelとCurryへの謝辞が書かれている。

Wikipediaの最初に出て来る解説だと複数の引数を取る関数を、最初の引数だけを取り、残りの引数を取って結果を返す関数を返す関数に書き換える事をカリー化と言うとの事。Wikipediaでのその後の解説やその他Web上での解説を読むと残りの引数を取る関数も順にカリー化して全てひとつの引数を取る一連の関数に分解する事をカリー化と称している模様。だが、この本ではあくまで最初の引数のみを分離する事を想定している。

eq?の例では、xとaの二つの引数を取る述語関数eq?を、引数aを取り、引数xを取り先のaとeq?を適用する関数を返す関数として分解する事を『カリー化する』と呼ぶ。つまりeq?に対してeq?-cを定義する事をカリー化すると言う。ここではxとaを引数に取り、それをカリー化する関数を定義している訳ではない。カリー化はあくまで人の手で行っている。

部分適用

次に部分適用の話。先にeq?をカリー化したeq?-cをsaladに適用してeq?-saladを作っている。これが部分適用。
eq?-saladの例を試してみる。

> (let ((y 'salad))
    (eq?-salad y))
#t
> (let ((y 'tuna))
    (eq?-salad y))
#f
> 

eq?-saladに名前をつける必要がありますか。

は「この関数にeq?-saladと言う名前をつける必要はありましたか?」と解釈。eq?-saladの定義をそのまま適用出来るので名前を付ける必要はない。

rember-f再び

rember-fを単に比較関数も引数に取ると言うだけではなく、比較関数のみを引数に取り従来のrember相当の関数を返す関数を定義する。ここでのカリー化はあくまで最初の引数のみを分離する。

ここではその前のrember-fと区別する為にrember-fcとして定義し、示された例を試す。

> (define rember-eq? (rember-fc eq?))
> (let ((a 'tuna)
        (l '(tuna salad is good)))
    (rember-eq? a l))
(salad is good)
> (let ((a 'tuna) (l '(shrimp salad and tuna salad)))
    ((rember-fc eq?) a l))
(shrimp salad and salad)
> (let ((a 'eq?) (l '(equal? eqan? eqlist? eqpair?)))
    ((rember-fc eq?) a l))
(equal? eqan? eqlist? eqpair?)
> 
insertL-f、insertR-f、insert-g

insertL-f、insertR-f共に比較関数eq?を引数test?で指定する様にし、更にtest?のみをカリー化する。

更に挿入部分のリストを組み立てるコードを関数化(seqL及びseqR)して更に一般化してinsert-gとする。但し比較関数はeq?に戻ってしまっている。

これらの2つの定義にどこか普通ではないことがありますか。

はい。以前でしたら、
seqがseqLのとき
(define insertL (insert-g seq))で、

defineの部分でのseqは引数ではないので「seqがseqLのとき」の意味が分からない。ここでも何らかの『定型』を持ち出している気がする。

insertL、insertRを適当な例で評価してみる。

> (insertL 'topping 'fudge '(ice cream with fudge for desert))
(ice cream with fudge topping for desert)
> (insertR 'topping 'fudge '(ice cream with fudge for desert))
(ice cream with topping fudge for desert)
> 
subst

insertL、insertRとはconsでリストを組み立てる部分のみの違いなのでseqSを使う事でinsert-gから合成可能。

> (let ((new 'topping) (old 'fudge) (lat '(ice cream with fudge for dessert)))
    (subst new old lat))
(ice cream with topping for dessert)
> 
yyy

insert-gで引数lの先頭要素がoldと等しい時に、seqremが適用される。seqremでは引数の如何に関わらずlを値としている。insert-gではseqremに(cdr l)を渡すため、lの先頭要素がoldと等しい場合にはlの先頭を除いたリストとなる。insert-gでlの先頭要素がoldと等しくない場合には結果に保存されるので、全体としてはremberと同じく最左のoldを除いたリストを意味する事になる。ここで引数newは使用されないためyyyの定義ではダミーの引数として#fを渡している。

> (let ((a 'sausage) (l '(pizza with sausage and bacon)))
    (yyy a l))
(pizza with and bacon)
>

第9の戒律

新しき関数においては共通のパターンを抽象化すべし。

共通の部分を抽象化すると言う表現が正しいのか、違う部分を抽象化すると言うのが正しいのか。

atom-to-function

整数演算を評価する関数valueの定義にあった重複を解消する。

> (let ((nexp '( 5 3)))
    (atom-to-function (operator nexp)))
#<procedure:+>
> 

ちゃんと関数+が返って来ている。

簡単な例に適用してみる。

> (value '(× ( 3 6) 6))
54
> (value '( 3 4))
81
> (value '(? 3 4))
81
> 
multirember-f

ここから少し部分適用の例。
multiremberの比較関数を引数化したmultirember-f。

> (let ((test? eq?) (a 'tuna) (lat '(shrimp salad tuna salad and tuna)))
    ((multirember-f test?) a lat))
(shrimp salad salad and)
> 

比較関数をeq?の固定化したmultirember-eq?

> (let ((a 'tuna) (lat '(shrimp salad tuna salad and tuna)))
    (multirember-eq? a lat))
(shrimp salad salad and)
> 
eq?-tuna

multirember-fにtunaに関して教える必要はありますか。

multirember-fはlatの中のすべての要素を調べるときに、常にtunaを捜します。

multirember-fは関数を返すのであってlatの中を調べる事はない。

multirember-fがlatを走査する際にtest?は変化しますか。

multirember-fはlatを走査しないが、multirember-fが返す関数がlatを走査する。その際にtest?は変化しない。

multiremberT

比較関数は既に比較対象を含んでいるので引数がひとつ少ない。特定のアトムと比較する関数test?とリストlを引数に取る。multiremberTは関数を返さず結果のリストを返す。

> (let ((test? eq?-tuna) (lat '(shrimp salad tuna salad and tuna)))
    (multiremberT test? lat))
(shrimp salad salad and)
>