関数合成とか部分適用とかリードマクロとか

マクロの復習というか練習というかメモというか。

メモ:マクロを呼び出してる関数をコンパイルするとどうなるの

予想通りといえばの予想通りの動作。
式がインライン展開されてると考えてよろしいのでしょうか。

CL-USER> (defmacro foo! (x)
           `(setq ,x (+ 2 ,x)) )
FOO!
CL-USER> (defun bar! (x)
           (foo! x)
           (print x))
BAR!
CL-USER> (compile 'bar!)
BAR!
NIL
NIL
CL-USER> (bar! 98)
100 
100

;; マクロを再定義しても
CL-USER> (defmacro foo! (x)
           `(setq ,x (* 2 ,x)) )
FOO!
;; コンパイル済み関数は眉一つ動かさない
CL-USER> (bar! 50)
52 
52

関数合成マクロ(<<<, >>>)

レキシカル環境を参照するマクロの練習をするべく2つのマクロを定義してみる。

CL-USER> (defun define-composer ()
           (labels ((composer-base (fnsyms)
                      (if (car fnsyms)
                          (let* ((fns (get-funcs fnsyms))
                                 (f (car (last fns))))
                            #'(lambda (&rest args)
                                (reduce #'funcall (butlast fns)
                                        :from-end t
                                        :initial-value (apply f args) )))
                          #'identity) )
                    (get-funcs (fnsyms)
                      (if fnsyms
                          (mapcar #'symbol-function fnsyms) )))
             
             (defmacro <<< (&rest fns)
               `(composer-base ',fns))
             (defmacro >>> (&rest fns)
               `(composer-base ',(reverse fns)))))
DEFINE-COMPOSER

CL-USER> (define-composer)
>>>
CL-USER> (funcall (<<< 1- expt) 2 8)
255
CL-USER> (funcall (>>> + evenp) 1 1 1 1)
T

マクロにすると#'(シャープクォート、関数の実体を得るために必要)が記述不要になるので楽ですね。
そうでないと (<<< #'1- #'expt) と書かねばならない。


この2つのマクロはcomposer-baseという閉じ込められた関数を参照できる。
しかしマクロがこのようにしてレキシカル環境を連れるようになったのはCLtL2からのことで、CLtL1では空のレキシカル環境で動作していたため、参照できなかったらしいです。(ということでxyzzyでは動作しません)


get-funcs内のsymbol-functionを何故かsymbol-valueと書いててややしばらく悩んでた。
仮眠したら一瞬で殺虫できました。
睡眠不足がひどい状態でプログラミングしてるとたまに自分が何書いてるか分からなくなる。笑


リードマクロを定義するマクロ

リードマクロの定義をいちいち覚えるの面倒だな委員会からの要請により(まあただの練習)、単純なリードマクロを定義するマクロを書いてみました。

CL-USER> (defmacro add-form (start-char end-char f)
           `(progn
             (set-macro-character ,end-char (get-macro-character #\) ))
             (set-macro-character ,start-char
              #'(lambda (stream c)
                  (let ((exp (read-delimited-list ,end-char stream t)))
                    (funcall ,f exp) )))))
ADD-FORM

マクロにする必要あったのかな?まあマクロの練習が目的なので・・・笑
以下のように使います。(選んだ記号は適当)

;; 部分適用(curry化)
CL-USER> (add-form #\[
                   #\]
                   #'(lambda (exp)
                       (let ((fn (car exp))
                             (args (rest exp)))
                         `(lambda (&rest other)
                           (apply #',fn (append ',args other)) ))))
T

;; 関数合成(さっきのマクロを使用)
CL-USER> (add-form #\|
                   #\|
                   #'(lambda (exp) `(<<< ,@exp)) )
T


CL-USER> (funcall [expt 2] 10)
1024

CL-USER> (funcall
          |plusp 1+ -| 5 2 3)
T

部分適用の方は以前のやつ(d:id:Nobuhisa:20081023:1224702900)そのままですが・・・。
以前トラックバックもらいましたがやっぱりxyzzyでは動作しません。



以上のぶちゃんのヒトリ練習場でした。
マクロに少しだけ慣れてきたけどまだまだ先は長いのでしょう・・・。お先まっくろ
ココこうした方がいいよ!とかコメントいただけるとううううううれしいいいい!