partial関数書いてみた

●partial関数

Common Lisp

*1

(defun partial (f &rest args)
  (lambda (&rest rest-args)
    (apply f (append args rest-args))))
(funcall (partial #'* 2) 10)         ;=> 20
(funcall (partial #'list 1 2) 3 4 5) ;=> (1 2 3 4 5)

一応できてますね。左から順にしか部分適用できませんけれど。

Emacs Lisp

elisp でも partial の定義は同じなので省略。動作確認してみると...

Debugger entered--Lisp error: (void-variable f)
  (apply (function funcall) f (append args rest-args))
  (lambda (&rest rest-args) (apply (function funcall) f (append args rest-args)))(10)
  funcall((lambda (&rest rest-args) (apply (function funcall) f (append args rest-args))) 10)
  eval((funcall (partial (function *) 2) 10))
  eval-last-sexp-1(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp nil nil)

嘘つきました。Emacs lisp では静的レキシカルスコープを明示しないとダメ。

(require 'cl)
(defun partial (f &rest args)
  (lexical-let ((f f) (args args))
    (lambda (&rest rest-args)
      (apply f (append args rest-args)))))
(funcall (partial '* 2) 10)         ;=> 20
(funcall (partial 'list 1 2) 3 4 5) ;=> (1 2 3 4 5)

●mapcar と flip で

Common Lisp
(defun flip (f a b)
  (funcall f b a))

(mapcar (partial #'flip #'expt 2) '(1 2 3 4 5))
;;=> (1 4 9 16 25)

高階関数を適用する場合、funcall が不要なのでいい感じ。 #' は慣れれば気にならなくなります。むしろシンボルが関数だと明確になるので好ましいとすら思えてくる。

Emacs Lisp

Emacs Lisp には expt(べき乗)関数ないみたいですね。

(defun expt (n m)
  (cond ((> m 0) (apply '* (make-list m n)))
        ((zerop m) 1)
        (t (/ 1.0 (expt n (- m))))))

(defun flip (f a b)
  (funcall f b a))

(mapcar (partial 'flip 'expt 2) '(1 2 3 4 5))
;;=> (1 4 9 16 25)

●引数の rotate

flip だけだと今イチ使い勝手が悪いので引数のローテーションをさせてみる。
以下、両 lisp 共用

(defun list-rotate-left (n lis)
  (let ((m (mod n (length lis))))
    (append (nthcdr m lis) (subseq lis 0 m))))

(defun args-rotate-left (n f &rest args)
  (if (or (zerop n) (null args))
      (apply f args)
      (apply f (list-rotate-left n args))))

(defun args-rotate-right (n f &rest args)
  (apply #'args-rotate-left (- n) f args))
;; Common Lisp
(args-rotate-left 1 #'list 1 2 3 4 5)  ;=> (2 3 4 5 1)
(args-rotate-right 1 #'list 1 2 3 4 5) ;=> (5 1 2 3 4)
(mapcar (partial #'args-rotate-right 1 #'make-list :initial-element 'X) '(1 2 3 4 5))
;;=> ((X) (X X) (X X X) (X X X X) (X X X X X))

elisp は make-list の仕様が若干ちがう。

;; Emacs Lisp
(args-rotate-left  1 'list 1 2 3 4 5) ;=> (2 3 4 5 1)
(args-rotate-right 1 'list 1 2 3 4 5) ;=> (5 1 2 3 4)
(mapcar (partial 'args-rotate-right 1 'make-list 'X) '(1 2 3 4 5))
;;=> ((X) (X X) (X X X) (X X X X) (X X X X X))

ネーミングセンスなくて短い関数名が思い付かなかった...

●partial をマクロにしてみる (2011/12/31 追記)

マクロの欠点は高階関数を適用できないところ。関数を引数とする関数にマクロは渡せない。だから取りあえず partial は関数にしたんだけど、上の partial の使われかたを見るとマクロでも問題は無さそう。*2書いてみた。

(defmacro partial (f &rest args)
  (let ((rest-args (gensym)))
    `(lambda (&rest ,rest-args)
       (apply ,f ,@args ,rest-args))))

args を,@ で展開できるおかげで rest-args に append しなくてよくなった。その分だけ実行時コストの軽減にはなってるだろう。

*1:最初 apply #'funcall f ... としていましたが apply するなら funcall は不要でした。

*2:mapcar の例では partial そのものに mapcar を適用しているのではなく、partial の作るラムダ関数に mapcar を適用している。それならばマクロであっても大丈夫。「関数を作るマクロ」を作ればいい。