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 が一つ少ない。
読みやすさと処理コストのトレードオフか...