なぜdefvarスコープはinitvalueなしでは動作が異なるのですか?


10

次のファイルという名前のファイルがあるとしますelisp-defvar-test.el

;;; elisp-defvar-test.el ---  -*- lexical-binding: t -*- 

(defvar my-dynamic-var)

(defun f1 (x)
  "Should return X."
  (let ((my-dynamic-var x))
    (f2)))

(defun f2 ()
  "Returns the current value of `my-dynamic-var'."
  my-dynamic-var)

(provide 'elisp-dynamic-test)

;;; elisp-defvar-test.el ends here

このファイルをロードしてから、スクラッチバッファーに移動して実行します。

(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
  (f2))

(f1 5)期待どおり5を返します。これは、の本体が動的スコープの変数としてf1扱わmy-dynamic-varれていることを示しています。ただし、最後の形式では、にvoid変数エラーが発生しmy-dynamic-var、この変数に字句スコープが使用されていることを示しています。これは、のドキュメントと矛盾しているようですdefvar

また、defvarフォームは変数を「特別」として宣言するため、たとえlexical-bindingtであっても常に動的にバインドされます。

defvarテストファイルのフォームを変更して初期値を指定すると、ドキュメントに記載されているように、変数は常に動的として扱われます。変数のスコープがdefvarその変数を宣言するときに初期値が提供されたかどうかによって決定される理由を誰かが説明できますか?

重要な場合のエラーバックトレースは次のとおりです。

Debugger entered--Lisp error: (void-variable my-dynamic-var)
  f2()
  (let ((my-dynamic-var 5)) (f2))
  (progn (let ((my-dynamic-var 5)) (f2)))
  eval((progn (let ((my-dynamic-var 5)) (f2))) t)
  elisp--eval-last-sexp(t)
  eval-last-sexp(t)
  eval-print-last-sexp(nil)
  funcall-interactively(eval-print-last-sexp nil)
  call-interactively(eval-print-last-sexp nil nil)
  command-execute(eval-print-last-sexp)

4
バグ#18059の議論は関連があると思います。
バジル

すばらしい質問です。そうです、バグ#18059の説明をご覧ください。
ドリュー

それはドキュメントがEmacsの26でこれに対処するために更新されるように見えるので、私は、見る
ライアンC.トンプソン

回答:


8

2つが異なる方法で処理される理由は、ほとんどが「それが私たちが必要とするものだから」です。より具体的には、の単一引数形式はdefvarずっと前に登場しましたが、他のものより遅れており、基本的にはコンパイラの警告を抑制するための「ハック」でした。実行時にはまったく影響がなかったため、「事故」のようにのサイレンシング動作は(defvar FOO)現在のファイルにのみ適用されること(コンパイラーがそのようなdefvarが他のファイルで実行されたことを知る方法がないため)。

ときlexical-bindingにEmacs-24に導入された、私たちはすることを決定した再利用この(defvar FOO)フォームを、それはそれは今ことを意味しない効果を持っています。

以前の「現在のファイルのみに影響する」動作を一部維持するが、toto他のライブラリtotoがレキシカルスコープの変数として使用するのを妨げることなく、ライブラリを動的スコープの変数として使用できるようにする(通常、パッケージ接頭辞の命名規則により、競合しますが、悲しいことにすべての場所で使用されているわけではありません)。の新しい動作は(defvar FOO)、現在のファイルにのみ適用されるように定義されており、現在のスコープにのみ適用されるように調整されています(たとえば、関数内に表示される場合は、その関数内の変数)。

基本的に、(defvar FOO VAL)そして(defvar FOO)ちょうど2つの「完全に異なる」ものです。彼らは歴史的な理由でたまたま同じキーワードを使用しています。


1
答えは+1。しかし、Common Lispのアプローチはより明確で優れています。
ドリュー

@Drew:私はほぼ同意しますが、を再利用する(defvar FOO)と、新しいモードが古いコードとはるかに互換性を持つようになります。また、CommonLispのソリューションのIIRCの問題は、Elispのような純粋なインタープリターにとってかなりコストがかかることです(たとえば、評価するたびに、varの一部に影響letするaがdeclareある場合は、その内部を調べる必要があります)。
ステファン

両方の点で合意。
ドリュー

4

実験に基づいて、問題は(defvar VAR)init値がないと、それが表示されるライブラリにのみ影響するということだと思います。

バッファに追加(defvar my-dynamic-var)する*scratch*と、エラーは発生しなくなりました。

私はもともと、これはのためにと思った評価そのフォームを、私は、単純にすることを第一に気づい訪問そのフォームの存在でファイルが十分でした。さらに、そのフォームをバッファーに追加(または削除)するだけで、評価せず(let ((my-dynamic-var 5)) (f2))に、同じバッファー内をで評価するときに何が起こったかを変更するだけで十分eval-last-sexpです。

(私はここで何が起こっているのかを実際に理解していません。驚くべき動作に気づきましたが、この機能の実装方法の詳細に精通していません。)

この形式defvar(初期値なし)は、バイトコンパイラが、コンパイルされているelispファイルで外部定義された動的変数の使用について不平を言うのを防ぎますが、それ自体で、その変数がになることはありませんboundp。変数を厳密に定義しているわけではありません。(変数 その場合、boundpこの問題はまったく発生しないことに注意してください。)

実際に私はあなたがすることを提供し、これはOK出て仕事に行くされているとします含める(defvar my-dynamic-var)あなたの使用して任意の字句結合ライブラリにmy-dynamic-var(おそらく他の場所で実際の定義を持っているでしょう)変数を。


編集:

コメントの@npostavsからのポインタに感謝します:

両方eval-last-sexpとをeval-defun使用eval-sexp-add-defvarsして:

EXPのdefvar前に、バッファ内のすべてのを付加します。

具体的にdefvardefconst、すべての、、およびdefcustomインスタンスを検索します。(コメントアウトされていても気付く。)

これは呼び出し時にバッファを検索するため、これらのフォームが評価されていなくてもバッファにどのように影響するかを説明し、フォームが同じelispファイルに(そして評価されるコードよりも前に)出現する必要があることを確認します。 。


2
IIUC、バグ#18059は実験を確認します。
バジル

2
eval-sexp-add-defvarsバッファテキストのdefvars をチェックするようです。
npostavs

1
+1。明らかに、この機能は明確ではないか、ユーザーに明確に提示されていません。バグ#18059のドキュメント修正は役立ちますが、これはユーザーにとって脆弱ではないにしても、未だ不可解なものです。
ドリュー

0

私はこれをまったく再現できません。後者のスニペットの評価はここでうまく機能し、期待どおりに5を返します。my-dynamic-var自分で評価していないと確信していますか?変数が無効であるため、エラーがスローされます。変数は値に設定されておらず、動的にバインドした場合にのみ値があります。


1
lexical-bindingフォームを評価する前にnon-nil を設定しましたか?あなたがlexical-bindingnilで記述した動作を取得しますが、それをnon-nilに設定すると、void変数エラーが発生します。
ライアンC.トンプソン

はい、これを別のファイルに保存し、元に戻してチェックし、lexical-binding設定され、フォームを順番に評価しました。
wasamasa

@wasamasa再現my-dynamic-varします。現在のセッションで誤ってトップレベルの動的な値を指定した可能性がありますか?私はそれを永久に特別なものとすることができると思います。
npostavs
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.