curryは飲み物
概要
LISPでカリー化された関数を定義するマクロを作ってみた.カリー化された関数は,必要な数の引数が渡されればただちに評価されて結果を返すが,引数の数が足りなければ部分適用された関数となる.それもまたカリー化されている.
動機
昨日も一日中椅子に座ってJava書いて……今日も一日中椅子に座ってJava書いて……きっと来週も再来週も一日中椅子に座ってJava書いて……毎日毎日同じ事の繰り返しで,生きてる気がしないんだYO!
簡単なカリー化
定義通りカリー化するだけなら,こんなふうにすればいい.
(defun currying (arg-list &rest body) (let* ((args (reverse arg-list)) (last-arg (car args)) (other-args (cdr args))) (reduce #'(lambda (acc elem) `(lambda (,elem) ,acc)) other-args :initial-value `(lambda (,last-arg) ,@body)))) (currying '(a b c) '(+ a b c)) ; => (LAMBDA (A) (LAMBDA (B) (LAMBDA (C) (+ A B C)))) (defmacro defun/curry% (name arg-list &body body) (let ((first-arg (car arg-list))) `(defun ,name (,first-arg) ,(apply #'currying (cdr arg-list) body)))) (macroexpand-1 '(defun/curry% sample-fun (a b c) (+ a b c))) ; => (DEFUN SAMPLE-FUN (A) (LAMBDA (B) (LAMBDA (C) (+ A B C))))
ちょっとかっこいいカリー化
簡単なカリー化の問題
呼ぶのが超めんどくさい.
(funcall (funcall (sample-fun 1) 2) 3)
やっぱLISPカッコ多いっすねwwwwwなどと言われてしまう.本来はこのように呼びたい.
(sample-fun 1 2) ; => #<部分適用された関数.あと1つの引数で評価される> (funcall (sample-fun 1 2) 3) ; => 6 (sample-fun 1 2 3) ;; => 6
部分適用された関数を表すオブジェクトを作る
部分適用された関数は状態を持つ関数であると考える.それは次の状態を持つ.
- これまで与えられた引数
- あとどれだけ引数が足りないか
- 引数が足りた時に呼ばれる関数
構造体で表すとこう.
(defstruct curried args-luck args fun ) ;; 3引数の関数.与えた引数はなし. (defvar *curried-fun* (make-curried :args-luck 3 :args NIL :fun #'(lambda (a b c) (+ 1 2 3))))
部分適用された関数オブジェクトを呼ぶ
この部分適用された関数オブジェクトはただの構造体なので,関数のようには呼べない.呼ぶための関数は次のように書ける.
(defun call-curried (curried-fun args) "部分適用された関数と,引数のリストを取る関数" (cond ;; 引数がなければ,任意の引数を取って関数を呼ぶ関数を返す ((null args) (lambda (&rest args) (call-curried curried-fun args))) ;; 足りない引数が残り1つなら,関数本体を呼び出す ((= 1 (curried-args-luck curried-fun)) (apply (curried-fun curried-fun) (nreverse (cons (car args) (curried-args curried-fun))))) ;; それ以外なら,引数リストに値を一つプッシュして,残り引数のカウントを減らす (t (call-curried (make-curried :args-luck (- (curried-args-luck curried-fun) 1) :args (cons (car args) (curried-args curried-fun)) :fun (curried-fun curried-fun)) (cdr args)))))
マクロでラップする
いちいち構造体を定義するのは難儀だし,引数の数をいちいち数えるのもかったるい.定義時に引数の数はわかるので,マクロでラップできる.
(defmacro defun/currying (name arg-list &body body) (let ((args (gensym)) (curf (gensym))) `(defun ,name (&rest ,args) (let ((,curf (make-curried :args-luck ,(length arg-list) :args NIL :fun #'(lambda ,arg-list ,@body)))) (call-curried ,curf ,args)))))
呼び出すときはこう.
(defun/currying add3 (a b c) (+ a b c)) (add3 1 2) ; => #<CLOSURE (LAMBDA (&REST ARGS) :IN CALL-CURRIED) {25B69F85}> (funcall (add3 1 2) 3) ; => 6 (add3 1 2 3) ; => 6
まとめ
curryは飲み物