partial関数(マクロ)の続き
前回書いた partial マクロ利用例
(mapcar (partial #'args-rotate-right 1 #'make-list :initial-element 'X) '(1 2 3 4 5))
mapcar の第一引数をもう少し理解しやすい形にしたい。
そこで部分適用を段階的にしてみる。args-rotate-right 1 を部分適用した後、その部分適用した関数を使ってさらに make-list を部分適用。
(let ((fn (partial (partial #'args-rotate-right 1) #'make-list :initial-element 'X))) (mapcar fn '(1 2 3 4 5)))
これを変形する。ネストした部分適用を逐次的な記述へ。
(let* ((r1 (partial #'args-rotate-right 1)) (fn (partial r1 #'make-list :initial-element 'X))) (mapcar fn '(1 2 3 4 5)))
この形をみてふと clojure の -> が使いたくなった。
以前 elisp 用に書いたものを移植。というかそのまんま CL でも使える。
(defmacro -> (&rest exprs) (when exprs (reduce #'(lambda (acc expr) (if (listp expr) (cons (car expr) (cons acc (cdr expr))) (list expr acc))) exprs)))
これを使うと
(-> (partial #'args-rotate-right 1) (partial #'make-list :initial-element 'X) (mapcar '(1 2 3 4 5)))
だいぶスッキリ読みやすくなった。マクロ展開してみると
(MAPCAR (LAMBDA (&REST #:G0) (APPLY (LAMBDA (&REST #:G1) (APPLY #'ARGS-ROTATE-RIGHT 1 #:G1)) #'MAKE-LIST :INITIAL-ELEMENT 'X #:G0)) '(1 2 3 4 5))
部分適用する毎に lambda のネストが作られる。
一方このエントリの最初に書いたコードを展開すると
(MAPCAR (LAMBDA (&REST #:G0) (APPLY #'ARGS-ROTATE-RIGHT 1 #'MAKE-LIST :INITIAL-ELEMENT 'X #:G0)) '(1 2 3 4 5))
lambda が一つ少ない。
読みやすさと処理コストのトレードオフか...