13

Emacs 24 では、ローカル変数のオプションのレキシカル バインディングが追加されました。XEmacs および以前の Emacs バージョンとの互換性を維持しながら、この機能をモジュールで使用したいと考えています。

Emacs 24 より前では、クロージャーを取得する最も簡単な方法は、 でlexical-let定義されたフォームを使用することでした。このフォームはcl-macs、巧妙なマクロトリックでレキシカル スコープをエミュレートしていました。これは elisp プログラマーの間であまり人気がありませんでしたが、lexical-let次の疑似コードのように でラップすることを覚えていれば、実際に効果的なクロージャーを作成して機能しました。

(defun foo-open-tag (tag data)
  "Maybe open TAG and return a function that closes it." 
  ... do the work here, initializing state ...
  ;; return a closure that explicitly captures internal state
  (lexical-let ((var1 var1) (var2 var2) ...)
    (lambda ()
      ... use the captured vars without exposing them to the caller ...
      )))

問題は、Emacs 23 と XEmacs のサポートを維持しながら、新しいレキシカル バインディングを使用する最善の方法は何かということです。現在、バインドされているかどうかに応じlexical-letて通常に展開または通常に展開するパッケージ固有のマクロを定義することで解決しました。letlexical-binding

(defmacro foo-lexlet (&rest letforms)
  (if (and (boundp 'lexical-binding)
           lexical-binding)
      `(let ,@letforms)
    `(lexical-let ,@letforms)))
(put 'foo-lexlet 'lisp-indent-function 1)

... at the end of file, turn on lexical binding if available:
;; Local Variables:
;; lexical-binding: t
;; End:

この解決策は機能しますが、新しい特別なフォームが非標準であり、適切に強調表示されず、 の下edebugにステップインできず、通常はそれ自体に注意を引くため、ぎこちなく感じます。より良い方法はありますか?


編集

コードが引き続き標準フォームを使用してクロージャーを作成できるようにする、よりスマートな (必ずしも良いとは限らない) ソリューションのアイデアの 2 つの例を次に示します。

  • アドバイスまたはコンパイラ マクロを使用して、レキシカルスコープのシンボルへの割り当てのみを下にlexical-let展開します。このアドバイスは、 のバイトコンパイル中に一時的にのみアクティブになるため、残りの Emacs ではの意味は変更されません。letlexical-bindingslexical-letfoo.ellexical-let

  • マクロ/コード ウォーカー機能を使用して、letプレフィックスのないシンボルをlexical-let古い Emacsen にコンパイルします。これも、 のバイトコンパイル中にのみ適用されますfoo.el

これらのアイデアがオーバーエンジニアリングのにおいがしても心配しないでください。私はそれらをそのまま使用することを提案していません。上記のマクロに代わるものに興味があります。パッケージは、ローディング/コンパイルの複雑さを犠牲にして、クロージャーの移植性の高い使用の利点を得ることができます。


編集2

モジュールが残りの Emacs でモジュールを使用し続ける、letまたはそれらを壊すことなく使用できるようにするソリューションを誰も提供していないため、上記のマクロlexical-letそれを行う方法であると述べている Stefan の回答を受け入れます。それに加えて、edebug と lisp-indent のエレガントな宣言を使用および追加することで、コードが改善されます。bound-and-true-p

誰かがこの互換性レイヤーの代替提案、または上記のアイデアの洗練された実装を持っている場合は、回答することをお勧めします.

4

3 に答える 3

7

lexical-letと lexical-bindingletはまったく同じことをしないので (より具体的にはlexical-let常に字句バインディングをlet使用しますが、var が 'd であるかどうかに応じて動的バインディングまたは字句バインディングを使用しますdefvar)、私はあなたのアプローチはそれが得るのと同じくらい良いと思います. Edebugを簡単にステップインさせることができます:

(defmacro foo-lexlet (&rest letforms)
  (declare (indent 1) (debug let))
  (if (bound-and-true-p lexical-binding)
      `(let ,@letforms)
    `(lexical-let ,@letforms)))

に依存したくない場合はdeclare、 を使用できます(put 'foo-lexlet 'edebug-form-spec 'let)

于 2012-09-18T03:22:25.240 に答える
2

defadvice考えられる解決策の1つは、フックlexical-let拡張を使用することです。私は次のアドバイスを書きました、そしてそれはうまくいくようです。これもbyte-compile認識しています。

(defadvice lexical-let (around use-let-if-possible (bindings &rest body) activate)
  (if (and (>= emacs-major-version 24)
           (boundp 'lexical-binding)
           lexical-binding)
      (setq ad-return-value `(let ,bindings . ,body))
    ad-do-it))
于 2012-09-17T22:54:01.763 に答える
-1

Lexical-letはletと同じarglist形式を持っているように見えるので、次のようなものはどうでしょうか。

(if (older-emacs-p)
  (setf (macro-function 'let) (macro-function 'lexical-let))
  (setf (macro-function 'lexical-let) (macro-function 'let)))

このシムは、新しいEmacsが古いコードの字句レット部分を読み取れるようにする必要があります(古いEmacsが新しいコードのlet部分を読み取れるようにします)。

それはCommonLispです。誰かがそれをEmacsに翻訳したいと思っていますか?

また、lexical-let / letが(マクロではなく)特別な形式として実装されている場合、問題が発生する可能性があります。

また、letが古いEmacsで定義されている場合、これは上位互換性のケースを完全に壊す可能性があります。それは...ですか?(私はEmacsについてほとんど知りません;それは私の選んだエディターではありません)。ただし、下位互換性のあるケースの方が重要な場合があります。

于 2012-09-21T23:50:49.993 に答える