Hatena::Groupcadr

kozima の日記

2010-05-18

外側を書き換えるマクロ

| 22:35

特に深い考えもないのですが。

普通のマクロは,呼び出しより下にある構文木を書き換えます。*1

;; 特に意味はない例
(defmacro my-macro (arg) `(print ',arg))

(if (listen) (my-macro 42))
;; == (if (listen) (print '42))

じゃあなんか呼び出しの上のほうを書き換える機構があってもいいんじゃないかみたいな。

(defmacro* my-macro* (context) (context '42))

(if (listen) (print my-macro*))
;; == (if (listen) (print '42))
;; context に (lambda (x) `(if (listen) (print ,x))) のようなものが渡される気分
;; (macrolet ((m (x) `(if (listen) (print ,x)))) (m '42)) と同じ?

何に使えるのかよく分かりませんが,きっとその手の思考力がある人なら変態的なことができそうなので,そういう能力があって暇な人は使い道を見つけると楽しいんじゃないでしょうか。

*1:構文木を書き換えるというより,生成すると言ったほうが適切?

2009-09-25

自作制御構造のインデント

| 20:45

マクロ定義の引数リストに &body があったら勝手にインデントをそれっぽくするようにするとどうだろうと思って、とりあえず xyzzy で適当にやってみました。

(shadow 'defmacro "user")

(lisp::defmacro defmacro (name (&rest lambda-list) &body body)
  (let* ((p1 (position '&body lambda-list))
         (pos (and p1
                   (count-if-not (lambda (x)
                                   (member x lambda-list-keywords))
                                 lambda-list
                                 :end p1))))
    ` (progn
        ,(when pos `(setf (get ',name 'lisp-indent-hook) ,pos))
        (lisp::defmacro ,name ,lambda-list ,@body))))

(setf (get 'defmacro 'lisp-indent-hook) 'defun)

(defmacro awhen (test &body body)
  `(let ((it ,test))
     (when it ,@body)))

(awhen 'hoge
  (print it))

emacs でも似たようなことはできると思いますが、もともと declaration の部分で指定できたと思うので必要ないかもしれません。

2009-07-24

defmacro ってマクロじゃん

| 23:26

後で気付いたのを思い出したので g:cadr:id:lkozima:20090722:1248276046 に自分で突っ込む。

また、もしかしたら展開中のフォームの中に defmacro があったりして、そこで定義されているマクロを考慮して展開する必要もあるかもしれません。マクロ定義を生成するマクロの中には (progn (defmacro ...) ...) のようなコードに展開されるものも存在するので。

defmacroマクロなので一般にはこれでは無理ですね。defmacroマクロだということは defmacro を直接には経由しないでマクロを定義してしまうことができるということですから。elisp の場合は special form だからそこは問題なかったんですね。

ここまで考えて、だんだん macroexpand-all を本気で実装しようとすると面倒なことになりそうだという気がしてきました。

2009-07-22

macroexpand-all

| 00:20

なんだか流行りのようなので思いつくままに作り方を。*1 xyzzy で作ろうとしたことがあるような。たぶん作りかけで不完全なままになってます。

基本

基本的にはこんな感じになると思うんですが。

(defun macroexpand-all (x)
  (let ((y (macroexpand x)))
    (if (consp y)
        (cons (car y) (mapcar #'macroexpand-all (cdr y)))
      y)))

これだけだと special form のことが考えられてないので、意地悪なコードを展開すると

;; (and or) は変数の宣言であってマクロ呼び出しではない
(macroexpand-all '(let (and or) and))
;=> (let or and)

ということになります。あと

(macroexpand-all '(lambda (x) x))

が無限ループしたりもします。

もう少し真面目に

special form を考慮するなら、その部分だけ別関数にするとか。例えば

(defun macroexpand-all (x)
  (let ((y (macroexpand x)))
    (cond ((atom y) y)
          ((special-form-p (car y))
           (macroexpand-in-special-form (car y) (cdr y)))
          (t
           ;; function call
           (cons (car y) (mapcar #'macroexpand-all (cdr y)))))))

とでもしておいて、macroexpand-in-special-form のところは処理系で定義されている special form すべてについて個別に定義することになるでしょう。

もっと真面目にやるなら

本当は flet などの局所関数でグローバルなマクロ定義が隠されていた場合には展開しないようにする必要があります。これは、隠されている名前を &optional ででも受け取って適当に増やしたりするようにすればいいでしょう。

また、もしかしたら展開中のフォームの中に defmacro があったりして、そこで定義されているマクロを考慮して展開する必要もあるかもしれません。マクロ定義を生成するマクロの中には (progn (defmacro ...) ...) のようなコードに展開されるものも存在するので。

このあたりまで対応すると、簡単なインタプリタを作るのと同じくらいの難易度か。

参考

Emacs の macroexpand-all は macroexp.el で定義されています。見てみると面白かったような記憶があります。

*1:内容的にはわりと既出かも