fletとcl-fletの差異


Emacs Lisp で `(require 'cl)` を避けるために、等価な代替として cl-lib
を使ってコードを書いたところ、予想に反する結果を得ました。

[code]

    (defun foo () (get-buffer "hoge"))
    (cl-flet ((get-buffer (buf-or-name)
                          (car-safe (member buf-or-name '("hoge" "fuga")))))
      (or (get-buffer "hoge") (error "A"))
      (or (foo)               (error "B")))
    
[/code]

エラーなしに `"hoge"` が返ることを期待しましたが、実際には `(error "B")` に到達しました。 `(error
"A")`ではないので、最初の `(get-buffer)` は期待通りに動作してるようです。

cl-libではなく、clパッケージの`flet`を利用すれば問題はありませんでした。

[code]

    (require 'cl)
    (defun foo () (get-buffer "hoge"))
    (flet ((get-buffer (buf-or-name)
                       (car-safe (member buf-or-name '("hoge" "fuga")))))
      (or (get-buffer "hoge") (error "A"))
      (or (foo)               (error "B")))
    
[/code]

なぜでしょうか。




[cl-lib](http://elpa.gnu.org/packages/cl-lib.html)は Common Lisp 風の機能を提供するための
cl パッケージに代るライブラリですが、全ての機能で既存のclパッケージとの互換性があるわけではありません。

`cl.el`の`flet`のdoc stringには以下のようにあります。

> This is an analogue of a dynamically scoped `let' that operates on the
> function  
>  cell of FUNCs rather than their value cell.  
>  If you want the Common-Lisp style of `flet', you should use `cl-flet'.  
>  The FORMs are evaluated with the specified function definitions in place,  
>  then the definitions are undone (the FUNCs go back to their previous  
>  definitions, or lack thereof).
>
> _Emacs 24.4.1に含まれる`cl.el.gz`より引用_  
>  _Copyright (C) 2012-2014 Free Software Foundation, Inc._

Emacs Lisp
はデフォルトの変数スコープがダイナミックスコープで、`let`で束縛された変数は`let`内から呼ばれたいかなる場所からもその値を指すようになります。`cl.el`で定義された`flet`も、そのような振舞をします。

ところがそれは、 Common Lisp の `flet` とは異なるようで、cl-libパッケージの `cl-flet` は Common Lisp
に近い挙動をするように改められました。

つまり、外側で定義された函数から呼び出される函数を置換する目的では `cl-flet` を `flet`の代替として利用することができません。

解決策は以下のいづれかです。

  1. `(eval-when-compile (require 'cl))` で、コンパイル時のみclパッケージを読み込む
  2. 「お作法」を気にせずに、 `(require 'cl)` で実行時にclパッケージを読み込む
  3. [nicferrier/emacs-noflet](https://github.com/nicferrier/emacs-noflet)を導入し、`noflet`マクロを利用する

自分の`.emacs`など影響範囲の狭いところでなら、2でも問題ありませんが、私は3の`noflet`を利用しました。`flet`と同じ感覚で利用できるようです。

