

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]

なぜでしょうか。


CC BY-SA 3.0
2015-01-27T17:01:38.250
5458
2020-05-16T22:58:09.880
2020-05-16T22:58:09.880
19110
938
question
elisp
fletとcl-fletの差異


[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`と同じ感覚で利用できるようです。


CC BY-SA 3.0
2015-01-27T17:01:38.250
5459
2015-01-28T02:24:11.397
2015-01-28T02:24:11.397
938
938
5458
answer


Jisami Zonu Tsugioさんの回答の通りcl.elと、cl-lib.elとの互換性の無さが原因だそうですが、  
<https://ja.stackoverflow.com/a/5459/3510>  
fletをマクロ展開すると、cl-letfが出てきますので、これを使えば、cl-lib.el に収録されている範囲で書き換えが可能ではないでしょうか。

[code]

    (require 'cl-lib)
    
    (defun foo () (get-buffer "hoge"))
    (cl-letf (((symbol-function 'get-buffer)
               (lambda (buf-or-name)
                 (car-safe
                  (member buf-or-name
                          '("hoge" "fuga"))))))
      (or (get-buffer "hoge")
          (error "A"))
      (or (foo)
          (error "B")))
    ;=> "hoge"
    
[/code]

参考: fletのマクロ展開

[code]

    (flet ((get-buffer (buf-or-name)
              (car-safe (member buf-or-name '("hoge" "fuga")))))
       (or (get-buffer "hoge") (error "A"))
      (or (foo)))
    ===>
    (letf
        (((symbol-function 'get-buffer)
           (cl-function
            (lambda
                (buf-or-name)
              (cl-block get-buffer
                (car-safe
                 (member buf-or-name
                         '("hoge" "fuga"))))))))
      (or
       (get-buffer "hoge")
       (error "A"))
      (or
       (foo)))
    
[/code]


CC BY-SA 3.0
2015-01-28T07:29:21.430
5505
2015-01-28T07:29:21.430
2017-04-13T12:52:39.113
-1
3510
5458
answer