読者です 読者をやめる 読者になる 読者になる

kb84tkhrのブログ

何を書こうか考え中です

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))でまったく同じ
同じ書き方で呼び出せて同じ値を返すってことは、まったく同じ関数とみなせますね