lambdaを生成するリーダマクロ

arcのマクロ(gaucheにもあった?)

 (fn (_) (if (odd _) (+ _ 10))

とかを

 [if (odd _) (+ _ 10)]

でかけるやつ。
がちょっと便利そうだったので、CommonLispで作ってみた。

(defun deep-find (item seq &key (test #'eql))
  (if (not (null seq))
      (or (if (funcall test item (car seq))
              item
              (if (not (atom (car seq)))
                  (deep-find item (car seq) :test test)))
          (deep-find item (cdr seq) :test test))))

(defmacro >str (&body body)
  `(write-to-string ,@body))
(defmacro >sym (str &optional package)
  `(intern ,str ,package))
(defmacro str+ (&rest b)
  `(concatenate 'string ,@b))

(set-macro-character #\] (get-macro-character #\)))
(set-dispatch-macro-character #\# #\[
  #'(lambda (stream char1 char2)
      (let ((body (read-delimited-list #\] stream t)))
        (if (deep-find '_ body)
            `(function (lambda (_) ,body))
            `(function
              (lambda
                  ,(nreverse
                    (do ((i 0 (1+ i)) (var nil))
                        ((> i 9) var)
                      (if (deep-find (>sym (str+ "_" (>str i)) *package*) body)
                          (setq var (cons (>sym (str+ "_" (>str i)) *package*) var)))))
               ,body))))))

deep-findはfindのdeep版で、あとは名前でわかるだろう。


「_(アンダーバー)」だけだと引数が二つ以上取れないので、勝手に「_0 〜 _9」まで追加した。
以下のように展開する。

 #[+ 1 2] 
  => #'(lambda () (+ 1 2))

 #[+ _ 1] 
  => #'(lambda (_) (+ _ 1))

 #[if (< _0 10) (+ _1 2) (+ (- 100 _0) _2)] 
  => #'(lambda (_0 _1 _2) (if (< _0 10) (+ _1 2) (+ (- 100 _0) _2)))

テスト

> (funcall #[+ 10 100])
; 110
> (funcall #[+ _ 100] 1)
; 101
> (mapcar #[list _0 _1 _2] '(1 2 3) '(4 5 6) '(7 8 9))
;((1 4 7) (2 5 8) (3 6 9))

うまくいっているようだ。