便利そうなマクロを集める旅。途中経過

旅をしています。途中経過はgist

はじめに

elispは便利な機能がデフォルトでつかえないことが多いです。cl由来の便利ライブラリが使えません。(require 'cl)をする必要があります。それ自体はeval-when-compileで囲めば良いので問題ないです。elispは名前の衝突を避ける仕組み(e.g. 名前空間)が用意されていません。そんなわけで、自前の拡張を作成するときに各自で重複するようなマクロが定義されてます。onlisp由来のマクロなどを良く見かけます(とくにaifを良く見かける)。

今回の目的は2つあります。

  • 各自が個別に定義してきた便利マクロを集めてみるとどの程度の規模になるのか知りたい。
  • 自分用の便利なマクロを集めたライブラリが欲しい。

とりあえず、便利だなと思うマクロを追加してみることにしました。util-macro.elというファイルがそれです。

追加したマクロ

schemeとcl(onlisp)から取ってきました*1

  • onlisp
    • alambda, aif, aand, with-gensyms
  • scheme
  • dynamic-scope -> lexical-scope
    • with-lexical-bindings
defmacro -> define-utilmacro

ちなみにmacroの定義にはdefmacroではなく、新たに定義したdefine-utilmacroを使っています。

(defmacro define-utilmacro (name args &rest body)
  `(progn 
     (defmacro ,name ,args
       ,@body)
     (add-to-list 'util-macro-alist
                  (cons (replace-regexp-in-string "\\*" "\\\\*"
                                                  (symbol-name ',name))
                        ',name))
     ',name))

このマクロは、defmacroに少しだけ機能を追加してます。

  • マクロ定義(defmacro相当)
  • macro-util-alistに定義したマクロの名前を追加(( . ) ...)

後者の機能は、定義すると同時にfont-lock-keywords-faceを利用して色を付けるようにするために使っています。

定義毎にadd-font-lock-keywordsを呼び出すのは良くないと感じたのでhookを作成して、ファイルの末尾で一気に登録してます。マクロの定義はadd-hookとrun-hook-with-argsの間で行っています。これで定義したマクロに色がつくようになります。(ただし、一度読み込んだ後に新しいマクロMを定義し、C-x C-eなどで評価した場合はhookが実行されないのでMは色が付きません。)

(defvar util-macro-alist nil)
(defvar util-macro-call-from-end-of-file-hook nil)
(add-hook 'util-macro-call-from-end-of-file-hook
          (lambda ()
            (let* ((names (mapcar 'car util-macro-alist))
                   (rx (mapconcat 'identity  names "\\|")))
              (font-lock-add-keywords 
               'emacs-lisp-mode
               `((,(format "(\\(%s\\)\\>" rx) 1 font-lock-keyword-face append))))))

;; .... この間でdefine-utilmacroを使ってmacroを定義

(run-hook-with-args 'util-macro-call-from-end-of-file-hook)

macroのインストール

新たに拡張を作成する時に定義したマクロを直接使うのは怖いです。なので、prefixを付けた定義を生成する機能を加えてみました。大本の定義があればdefaliasを使うことで別名を定義することはできますが、作成する拡張がこのライブラリに依存しない方が望ましいと思います。なので、関数の本体を挿入するようにしてみました。こちらはutil-macro-install.elです。

symbol->definitionでシンボルから別名を持った関数定義を生成できます。

(insert (symbol->definition 'aif "foo-"))
;; (defmacro foo-aif (test-form then-form &rest else-forms)
;;   "Anaphoric if. Temporary variable `it' is the result of test-form."
;;   (\` (let ((it (\, test-form))) (if it (\, then-form) (\,@ else-forms)))))

(insert (symbol->definition 'aand "foo-"))
;; (defmacro foo-aand (&rest args)
;;   "Anaphoric and. anaphorar is `it'"
;;   (cond ((null args)
;;          t)
;;         ((null (cdr args))
;;          (car args))
;;         (t (\` (foo-aif (\, (car args)) (foo-aand (\,@ (cdr args))))))))

全てのマクロにprefixを付けて挿入したい場合にはutil-macro-installを使います。

(util-macro-install "foo-") ;; C-x C-eすると定義が挿入される。

今のところ一律全て挿入しますが、登録しているマクロの量が増えてきたら選択できるようにするかもしれません。

途中経過

現在の途中経過です。まだまだ変わる可能性が高いです。

*1:と言っても、blogを見て回ったという方が近いですけど