Scheme手習い(10) 高階関数とカリー化
第8章「究極のlambda」では高階関数を学びます
まずは一番わかりやすい、引数として関数を渡すパターン
いつものremberで、比較のための関数を指定できるようにします
(define rember-f
(lambda (test? a l)
(cond ((null? l) (quote ()))
((test? (car l) a) (cdr l))
(else (cons (car l) (rember-f test? a (cdr l)))))))
動かします
> (rember-f = 5 '(6 2 5 3))
(6 2 3)
> (rember-f eq? 'jelly '(jelly beans are good))
(beans are good)
> (rember-f equal? '(pop corn) '(lemonade (pop corn) and (cake)))
(lemonade and (cake))
この本はeq?で数字の比較はできないという姿勢を貫いてます
次は、関数を返すパターン
では、
(lambda (a) (lambda (x) (eq? x a)))
は何ですか。
ラムダラムダって何
引数としてaを渡されると関数
(lambda (x)
(eq? x a))を返します。ここでaは渡された引数です。
具体例で考えてみます
(lambda (a)
(lambda (x)
(eq? x a)))
に引数1を与える、つまり以下の関数適用は
((lambda (a)
(lambda (x)
(eq? x a))) 1)
以下の関数、つまりxと1が等しいかどうかを返す関数を返します
(lambda (x)
(eq? x 1))
ってことはつまりアレか
ラムダラムダにびっくりせずいつもどおりのやり方でいいんだ
((lambda (a) (lambda (x) (eq? x a))) 1)
(lambda (x) (eq? x 1))
この関数にもうひとつ引数を与えてやれば、その引数が1と等しいかどうかが返されるわけだ
> (((lambda (a)
(lambda (x)
(eq? x a))) 1) 1)
#t
> (((lambda (a)
(lambda (x)
(eq? x a))) 1) 2)
#f
つまり、1引数の関数を入れ子にして使うことで
> ((lambda (x a)
(eq? x a)) 1 1)
#t
> ((lambda (x a)
(eq? x a)) 1 2)
#f
こういう2引数の関数と同じ働きをすることが可能ってわけね
これは「カリー化」(Curry-ing)と呼ばれています。ありがとう、Schönfinkel(1889〜1942)。
Schönfinkelさんとは誰だろう
Wikipediaより
カリー化 - Wikipedia
カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。クリストファー・ストレイチーにより論理学者ハスケル・カリーに因んで名付けられたが、実際に考案したのはMoses Schönfinkelとゴットロープ・フレーゲである。
だそうで
知りませんでした
名前をつけてもOK
(define eq?-c
(lambda (a)
(lambda (x)
(eq? x a))))
(define eq?-salad (eq?-c 'salad))
> (eq?-salad 'salad)
#t
> ((eq?-c 'salad) 'salad)
#t
普通のSchemeの本ならここで何かを説明するはずなんだけど内緒だ
気にしなければどうということもない
関数が関数を返し、そのまま関数として使われるあたりとても関数型っぽい
3引数でやったらどうなるかな
理屈は同じはず
練習してみる
(define eq3?
(lambda (c)
(lambda (b)
(lambda (a)
(and (eq? a b) (eq? b c))))))
(define eq3?-1 (eq3? 1))
(define eq3?-2 (eq3?-1 1))
> (((eq3? 1) 1) 1)
#t
> ((eq3?-1 1) 1)
#t
> (eq3?-2 1)
#t
次はさっき書いたrember-fのカリー化
(define rember-f (lambda (test?) (lambda (a l) (cond ((null? l) (quote ())) ((test? (car l) a) (cdr l)) (else (cons (car l) ((rember-f test?) a (cdr l))))))))
動かす
> ((rember-f =) 5 '(6 2 5 3))
(6 2 3)
こう書き換えたから何がいいの、ってことはこの本は書いてくれないんで
挙げられてる例から読み取っていくしかないんですけど
最初に出てきたrember-fだと上に出てきたような呼び出し方はできなくて
(define rember= (lambda (a l) (rember-f = a l)))
などとして定義してから(rember= 5 '(6 2 5 3))として呼び出してやるしかありません
同じようにdefineしてから使うにしても、カリー化したrember-fであれば
(define rember= (rember-f =))
と書けてずいぶん身軽になった感じがします
これってポイントフリーとかいうやつでしょうか?違うかな
呼び出しは(rember= 5 '(6 2 5 3))でまったく同じ
同じ書き方で呼び出せて同じ値を返すってことは、まったく同じ関数とみなせますね