dot.emacs

[exec-if-bound, defun-add-hook, load-safe] [eval-safe] [autoload-if-found] [references]

exec-if-bound, defun-add-hook, load-safe

普通のまっとうな環境でまっとうな Emacs を使っているだけなら、まっとうに M-x customize を使うべきだと思います。しかし現在棲んでいる環境が、Solaris 2.5.1/2.6 の SPARC 版 / Intel 版, RedHat Linux 6.2/7.0 あとよく分からない Alpha マシンとかとにかくごちゃごちゃ混在の上にすべて $HOME が NFS 共有されているという状態で、当然 Emacs/Mule も下は 19.28 から上は 20.7 に至るまで、しかも elisp ライブラリだって全然統一されていない。おまけに、頻繁にマシンが追加されたり消えたりする。こんな状態では、M-x customize は全く無力です。(そんなことないかな。)

こんな場合 .emacs を手書きすることになるのですが、1. .emacs-foobar で分ける、2. (system-name) で分ける、3. さらに string< + emacs-version, (featurep 'xemacs) (だっけ?)を使って分ける、といった手段が考えられ、Web をつらつらと眺めた限りではこれがスタンダードな方法のようです。しかし、ホスト名やらをどうしてもハードコーディングしなければならず、気持ちよくない。そこでいろいろ考えてみました。

ところが、このような微妙な環境でも M-x customize を使いこなす方法があります。customize の結果が書き込まれる file を、(setq custom-file "~/.emacs-foobar")のようにして変えることができます。これは、おおむらゆうさんに教えていただきました。ありがとうございます。

まず条件(希望)は、

  1. .emacs ファイルは1つにまとめたい。
  2. ホスト名やらをハードコーディングするのは美しくない。
  3. 「使えない機能が自動的に無効となるだけ」にしたい。すなわち、.emacs の解釈が途中で止まってしまうと困る。

特に3番目に注目して .emacs を眺めてみたところ、分かったのは

  1. 条件文 (if, cond) や関数定義など、言語寄りの話をなくせば、 .emacs に書かれているのはだいたい以下の5つしかない。
    1. setq
    2. add-hook
    3. load (単純な (load "library") の形)
    4. autoload
    5. それ以外の、一般的な関数呼び出し (set-language-environment, set-wnn-host-name など)
  2. このうち、setqadd-hook, autoload は決して失敗しない。(ここで「失敗」とは、 .emacs の解釈がそこで止まってしまうことを指す。変な autoload ができるのはやっぱり気持ちよくないですが、気にしないことにします。)
  3. 残るは関数呼び出しと load。関数呼び出しについては fboundp がある。load は、省略可能な第2引数 (missing-ok) に non-nil を与えることで、読み込み失敗しても異常終了とならないようにできる。

あとこれはおまけですが、add-hook もちょっと不便です。何が不便かというと、hook では引数付きの関数呼び出しは(引数固定でも)できないので、 lambda で囲むということが定石としてやられます。ところで、関数をクオートするには、単純なシングルクオートではなく function を使うべしとされています。以上に厳密に従い、かつこれを標準 Emacs-Lisp モードでインデントすると、

(add-hook 'foobar-mode-hook
	  (function (lambda ()
 		      ; 22カラム目!!
		      (foo 1)
		      (bar 2)
		      )))

というかなり感じ悪いことになります。そこで、これも何とかすることにします。

というわけで、都合3つの関数およびマクロを書きました。

(defmacro exec-if-bound (sexplist)
  "関数が存在する時だけ実行する。(car の fboundp を調べるだけ)"
  `(if (fboundp (car ',sexplist))
       ,sexplist))
(defmacro defun-add-hook (hookname &rest sexplist)
  "add-hook のエイリアス。引数を関数にパックして hook に追加する。"
  `(add-hook ,hookname
             (function (lambda () ,@sexplist))))
(defun load-safe (loadlib)
  "安全な load。読み込みに失敗してもそこで止まらない。"
  ;; missing-ok で読んでみて、ダメならこっそり message でも出しておく
  (let ((load-status (load loadlib t)))
    (or load-status
        (message (format "[load-safe] failed %s" loadlib)))
    load-status))

使い方はこんな感じです。

;; 例えば Mule2.3@19.34 と Emacs20.x (MULE/4.0) では、バッファの出力漢
;; 字コードをセットする関数の名前と引数が微妙に違います。しかし、ここ
;; で (emacs-version) で判別、などする必要はありません。両方並べて書け
;; ば、実行できる方だけが実行されます。
(exec-if-bound (set-file-coding-system '*euc-japan*))
(exec-if-bound (set-buffer-file-coding-system 'euc-japan))

;; もしかしたら、この Emacs は Canna あるいは tamago v4 ベースで、 
;; its/han-kata がないかも知れません。しかし、気にする必要はありません。
(load-safe "its/han-kata")

;; 名前を defun で始めたのは、標準状態の Emacs-Lisp モードだと def? く
;; らいで始まるものは関数定義のスタイルでインデントされるからです。つ
;; まり、こんな感じ(何と2カラム目!)
(defun-add-hook 'perl-mode-hook
  (exec-if-bound (set-buffer-file-coding-system 'euc-japan-unix)))

お楽しみいただけましたか?

eval-safe

以上でだいたいの項目には対処できます。が、さらに複雑な .emacs を記述しようとすると、これでは困ることも出てきます。例えば require した library のなかでさらに require が行われており、その先の library だけがインストールされていない場合などです。この場合は、対症療法でエラーが出た先の library を locate-library でひとつひとつチェックしてもよいのですが、ここではもっとインテリジェントな機構を導入します。 condition-case です。鈴木圭一さんに教わりました(ありがとうございます)。 C++ や Java の try / catch に似た例外機構です。

(condition-case err
    (progn
      (some-error-function))
  (error (message "failed: %s" err)))

こんな感じで使います。err は変数です。progn の中で何らかのエラーが起こったときに、即座に error 節へジャンプし、その時に変数 err に error message が入っています。それを message で表示しています。

使い方はもちろん exec-if-bound, load-safe の置き換えで、これなら本当に何も考えず、設定項目単位ごとにこれで囲えば済んでしまいます。さらに重要なことに、progn 内は途中で失敗したらその先は実行されないので、依存関係にある設定項目を自然に表現できます。例えば、

(condition-case err
    (progn
      (assert (locate-library "foobar"))
      (autoload 'foo "foobar" "Foo Bar." t nil))
  (error (message "failed: %s" err)))

こんな風に autoload の前に locate-library を置いておけば、library が存在するときのみ autoload が set されるようになる、というようなことです。(この辺りも含めて、教えていただいたことです。ありがとうございます)

これを eval-safe として定義しておきます。

(defmacro eval-safe (&rest body)
  "安全な評価。評価に失敗してもそこで止まらない。"
  `(condition-case err
       (progn ,@body)
     (error (message "[eval-safe] %s" err))))

使い方は、

(eval-safe (some-suspicious-code))
;; nesting もできます。
(eval-safe
 (insert "1")
 (eval-safe
  (insert "2")
  (no-such-function))
 (insert "3")
 (no-such-function))

autoload-if-found

本当に小物ですが重宝しているので紹介します。 locate-libraryして autoload、を1発で書ける関数です。

(defun autoload-if-found (function file &optional docstring interactive type)
  "set autoload iff. FILE has found."
  (and (locate-library file)
       (autoload function file docstring interactive type)))
;; 使い方
;; 引数は autoload と全く同じです。-if-found を付けるだけ
(when (autoload-if-found 'bs-show "bs" "buffer selection" t)
  ;; autoload は成功した場合のみ non-nil を返すので、
  ;; when の条件部に置くことで、依存関係にある設定項目を自然に表現できます。
  (global-set-key [(control x) (control b)] 'bs-show)
  (setq bs-max-window-height 10))

references

Kondara Projectの細野さんに間違いのご指摘と訂正を頂きました。ありがとうございました。

* この文書は無保証です。 [XHTML 1.0 Strict]