Skip to content

ライブラリと設定の関係の明示

2012/06/22

設定ファイルが成長するに連れて

emacs 設定ファイルは日々成長していくものですよね?
新しい拡張機能やライブラリ(以下ライブラリで統一します)、便利なコード片など、気に入ったものを見つけたら自分の環境にインストールして、それに対する設定コードを .emacs (や .emacs.d/init.el)に書き足していく事と思います。
ライブラリが増えて設定コードが大きくなるに連れて、ある設定コードが必要としているライブラリがそのコード周辺を見ただけでは正確にわからなくなる時が多々ありました。
簡単な例としてはこんな感じです。

;; ac-slimeの設定
(add-hook 'slime-mode-hook 'set-up-slime-ac)
(add-hook 'slime-repl-mode-hook 'set-up-slime-ac)

これは ac-slime の設定の例です。
このコードは少なくとも auto-complete, slime, ac-slime を必要としています。
ですが、今の自分がそのことを知っていても、将来の自分が覚えているかどうかは怪しいところです。
僕は、ライブラリがロードされることを前提としたコードが、自分の設定ファイルに多くあることを不安に思っていました。
そこで今回は、コードとライブラリの関係を明示的にするために、僕が用意したマクロを紹介します。

用意したマクロ

今回紹介するマクロ(とその補助関数)は以下になります。

(eval-when-compile
  (require 'cl))

(defun* gen-lib-loader (liblist &optional (noerr t))
  (mapcar
   (lambda (x)
     (if (symbolp (car x))
         (destructuring-bind
             (feature &optional filename (noerror noerr)) x
           `(require ',feature ,filename ,noerror))
       (destructuring-bind
           (file &optional (noerror noerr) &rest rest) x
         `(load ,file ,noerror ,@rest))))
   (mapcar (lambda (x)
             (if (listp x) x (list x)))
           liblist)))

(defmacro with-library (liblist &rest form)
  "指定したライブラリが全て読み込み済みの時のみ`form'を実行する。"
  (declare (indent 1))
  `(when
       (and
        ,@(mapcar
           (lambda (x) `(featurep ',x))
           liblist))
     ,@form))

(defmacro with-load-library (liblist &rest form)
  "指定したライブラリを順に読み込み、すべてのライブラリの読み込みが成功した場合のみ`form'を実行する。"
  (declare (indent 1))
  `(when
       (and
        ,@(gen-lib-loader liblist))
     ,@form))

(defmacro with-lazy-library (liblist &rest form)
  "指定したライブラリが全て読み込まれた時点で`form'を実行する。"
  (declare (indent 1))
  (with-lazy-library-code-gen liblist `(progn ,@form)))

(defun with-lazy-library-code-gen (liblist form)
  (if (null liblist)
      form
    `(eval-after-load
      ,(format "%s" (car liblist))
      ',(with-lazy-library-code-gen (cdr liblist) form))))

このうち、目的のマクロは

  • with-library
  • with-load-library
  • with-lazy-library

の3つです。

with-library

このマクロは、指定したライブラリがずべてロード済みだった場合のみ、このマクロに包まれたコードを実行します。
(正確には、そのように動作するコードを生成するといったところでしょうか)
このマクロはあるライブラリ用の設定を用意するけど、そのライブラリをロードするかどうかは、他所に任せるといったものです。
ちなみに、ロード済みかどうかは featurep を使って判断します。

  • 書式
    書式は次のようになります。

    (with-library (lib1 lib2 ...)
      code ...)
    

  • 展開結果
    展開結果の例は次のようになります。

    ELISP> (macroexpand '(with-library (hoge huga)
                           (foo)
                           (bar baz)))
    (if
        (and
         (featurep 'hoge)
         (featurep 'huga))
        (progn
          (foo)
          (bar baz)))
    

with-load-library

with-library とは違い、このマクロは指定したライブラリの読み込みを試し、すべての読み込みに成功した場合のみ、このマクロに包まれたコードを実行します。
ライブラリの設定とともにライブラリの読み込みに関しても責任を持ちます。
ライブラリの読み込みには require と load が利用できるようにしてあります。
それぞれ、ライブラリをシンボルで指定した場合には require 、文字列で指定した場合には load が使用されます。
また、 require と load のオプショナル引数を使用したい場合には、ライブラリ名にオプショナル引数をつなげたリストを指定します。

  • 書式

    (with-load-library (lib1 "lib2" (lib3 "lib3-dev") ...)
      code ...)
    

  • 展開結果

    ELISP> (macroexpand '(with-load-library (hoge "huga" (piyo "piyopiyo"))
                           (foo)
                           (bar baz)))
    (if
        (and
         (require 'hoge nil t)
         (load "huga" t)
         (require 'piyo "piyopiyo" t))
        (progn
          (foo)
          (bar baz)))
    

with-lazy-library

最後に、このマクロは指定したライブラリが全て読み込まれたタイミングで、このマクロに包まれたコードを実行します。
つまるところ、ネストした eval-after-load に展開されます。
ライブラリの指定にはシンボルと文字列の両方が使えますが、どちらを用いても意味は変わりません。
ライブラリが、 load で読み込まれることを示す目印として僕は利用しています。
ライブラリが読み込まれるまでコードを保持しておく以外は、 with-library と同じような動作をします。

  • 書式

    (with-lazy-library (lib1 "lib2" ...)
      code ...)
    

  • 展開結果

    ELISP> (macroexpand '(with-lazy-library (hoge "huga")
                           (foo)
                           (bar baz)))
    (eval-after-load "hoge"
      '(eval-after-load "huga"
         '(progn
            (foo)
            (bar baz))))
    

使用例

用意したマクロを使って、冒頭に出てきたコードを書いてみると次のようになります。

(with-lazy-library (slime auto-complete)
  (with-load-library (ac-slime)
    (add-hook 'slime-mode-hook 'set-up-slime-ac)
    (add-hook 'slime-repl-mode-hook 'set-up-slime-ac)))

これで、 slime と auto-complete のロードに関しては責任を持たないけど、両方が読み込まれたならば、 ac-slime のロードとそれに関する設定を行う、という事がコード上で表現できるようになりました。

終わりに

今回の目的は、設定コードとライブラリの関係をコード上に表すことでした。
とりあえずは目的を果たせたのではないかと思います。
これで、マクロ呼び出し側から意味合いを残して、実装を抜き出すことができました。
同じような意味で書かれていた似たようなコードを統一できましたし、実装に不備があっても訂正箇所はごく僅かです。
車輪の再発明な気はしますが、これで少しは .emacs の整理が楽になるのではないでしょうか。

実際には(おまけ)

実際には、自分は前回用意した eval-after-load-compile を使用すると共に、バイトコンパイル時にだけライブラリをロードするようにして、今回紹介したマクロを使用しています。
なぜ上で紹介しなかったのかというと、実装からくる注意点がいくつか存在するからです。
参考までに、コードを載せておきますね。
(変更のない部分は省略してあります)

(defmacro with-load-library (liblist &rest form)
  "指定したライブラリを順に読み込み、すべてのライブラリの読み込みが成功した場合のみ`form'を実行する。"
  (declare (indent 1))
  `(progn
     (eval-when-compile
       ,@(gen-lib-loader liblist))
     (when
         (and
          ,@(gen-lib-loader liblist))
       ,@form)))

(defmacro with-lazy-library (liblist &rest form)
  "指定したライブラリが全て読み込まれた時点で`form'を実行する。"
  (declare (indent 1))
  `(progn
     (eval-when-compile
       ,@(gen-lib-loader liblist))
     ,(with-lazy-library-code-gen liblist `(progn ,@form))))

(defun with-lazy-library-code-gen (liblist form)
  (if (null liblist)
      form
    `(eval-after-load-compile
      ,(format "%s" (car liblist))
      ',(with-lazy-library-code-gen (cdr liblist) form))))

広告

From → Emacs

コメントする

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。