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되고 있음을 나타냅니다 . 그러나 마지막 양식은 my-dynamic-var이 변수에 대해 어휘 범위를 사용하고 있음을 나타내는 void-variable 오류를 나타냅니다. 이것은에 대한 문서와 상충되는 것으로 보입니다 defvar.

defvar형식은 또한 변수를 "special"으로 선언하므로 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에 대한 토론을보십시오.
Drew

Emacs 26에서이 문제를 해결하기 위해 설명서가 업데이트 될 것 같습니다.
Ryan C. Thompson

답변:


8

이 두 가지를 다르게 취급하는 이유는 대부분 "필요한 것이기 때문"입니다. 더 구체적으로 말하면, 단일 인수 형식은 defvar오래 전에 나타 났지만 다른 것보다 늦었고 기본적으로 컴파일러 경고를 침묵시키는 "해킹"이었습니다. 실행 시간에는 전혀 영향을 미치지 않았으므로 "사고" 침묵 화 동작은 (defvar FOO)현재 파일에만 적용됩니다 (컴파일러는 그러한 defvar가 다른 파일에서 실행되었다는 것을 알 수 없었기 때문에).

lexical-binding이맥스 (24)에 도입 된, 우리는하기로 결정 재사용(defvar FOO)양식을,하지만 지금 것을 의미 하지 효과가있다.

이전의 "현재 파일에만 영향을 미칩니다"동작을 보존하는 것이 중요하지만,보다 중요한 것은 toto다른 라이브러리 toto가 어휘 범위가 지정된 변수로 사용 되는 것을 막지 않고 라이브러리가 동적 범위의 변수로 사용 되도록하는 것입니다. 충돌하지만 슬프게도 아무데도 사용되지는 않음)의 새로운 동작은 (defvar FOO)현재 파일에만 적용되도록 정의되었으며 심지어 현재 범위 에만 적용되도록 수정되었습니다 (예 : 함수 내에 표시되는 경우 해당 함수 내의 해당 var).

근본적으로, (defvar FOO VAL)그리고 (defvar FOO)두 "완전히 다른"것입니다. 그들은 역사적 이유로 같은 키워드를 사용합니다.


1
답변은 +1입니다. 그러나 Common Lisp의 접근 방식 은 더 명확하고 좋습니다 (IMHO).
Drew

@Drew : 대부분 동의하지만 (defvar FOO)새 모드를 재사용하면 이전 모드와 훨씬 더 호환됩니다. 또한 IIRC의 CommonLisp 솔루션의 문제점은 Elisp와 같은 순수한 통역가가 꽤 비싸다는 것입니다 (예를 들어 평가할 때마다 일부 변수에 영향을 미치는 let경우 신체 내부를 살펴 봐야 함 declare).
Stefan

두 가지 모두에 동의했습니다.
Drew

4

실험에 따르면, 문제는 (defvar VAR)초기화 값이 없으면 라이브러리에 영향을 미치는 것입니다.

내가 추가 할 때 (defvar my-dynamic-var)받는 *scratch*버퍼 오류가 더 이상 발생하지 않았다.

원래는 이것이 해당 형식 을 평가 한 것이라고 생각 했지만, 먼저 해당 형식의 파일을 방문 하는 것으로 충분하다는 것을 알았습니다 . 또한 평가없이 버퍼에 해당 형식을 추가 (또는 제거)하는 것만으로 (let ((my-dynamic-var 5)) (f2))동일한 버퍼 내부에서 평가할 때 발생한 일을 변경 하기 에 충분했습니다 eval-last-sexp.

(여기서 무슨 일이 일어나고 있는지 전혀 알지 못합니다. 놀라운 동작을 발견했지만이 기능이 구현되는 방법에 대한 자세한 내용은 알 수 없습니다.)

defvarinit 값이없는 이 형식은 바이트 컴파일러가 elisp 파일에서 외부 정의 된 동적 변수의 사용에 대해 불평하는 것을 막지 만, 그 자체로 그 변수 가되지 않습니다boundp . 변수를 엄격하게 정의하지는 않습니다. (변수 boundp 그렇다면이 문제는 전혀 발생하지 않습니다.)

실제로 변수 를 사용하는 어휘 바인딩 라이브러리 (다른 곳에서 실제 정의가있을 수 있음)를 포함 하면 이것이 제대로 작동한다고 가정합니다 .(defvar my-dynamic-var)my-dynamic-var


편집하다:

의견에 @npostavs의 포인터 덕분에 :

둘 다 다음 eval-last-sexpeval-defun위해 사용 eval-sexp-add-defvars합니다.

defvar버퍼에서 앞에 오는 모든을 EXP 앞에 추가 하십시오.

특히이 모든 찾아 defvar, defconst그리고 defcustom인스턴스. (댓글을 달았을 때도 알아요.)

이것은 호출시 버퍼를 검색 할 때 이러한 양식이 평가되지 않아도 버퍼에 어떤 영향을 줄 수 있는지 설명하고 양식이 동일한 elisp 파일에 표시되어야 함을 확인합니다 (또한 평가되는 코드보다 빠름). .


2
IIUC, 버그 번호 18059는 귀하의 명령을 확인합니다.
바질

2
eval-sexp-add-defvars버퍼 텍스트에서 defvar 를 확인하는 것으로 보입니다 .
npostavs

1
+1. 분명히이 기능은 명확하지 않거나 사용자에게 명확하게 표시되지 않습니다. 버그 # 18059에 대한 문서 수정이 도움이되지만, 이것은 사용자에게 취약하지는 않지만 여전히 신비로운 것입니다.
Drew

0

나는 이것을 전혀 재현 할 수 없으며, 후자의 스 니펫을 평가하면 여기에서 잘 작동하고 예상대로 5를 반환합니다. my-dynamic-var자체적으로 평가하고 있지 않습니까? 변수가 void이기 때문에 오류가 발생하고 값으로 설정되지 않았으며 동적으로 하나에 바인딩하면 하나만 갖게됩니다.


1
lexical-binding양식을 평가하기 전에 0이 아닌 값을 설정 했습니까 ? lexical-bindingnil로 설명하는 동작을 얻지 만 non-nil로 설정하면 void-variable 오류가 발생합니다.
Ryan C. Thompson

예,이 파일을 별도의 파일에 저장하고 되돌리고 확인 lexical-binding하여 양식을 설정하고 순차적으로 평가했습니다.
wasamasa

@wasamasa 나를 위해 재현, 당신은 아마도 당신이 실수로 my-dynamic-var현재 세션에서 최고 수준의 동적 가치를 부여했을 까요? 나는 그것이 영구적으로 특별하다고 표시 할 수 있다고 생각합니다.
npostavs
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.