Tại sao phạm vi defvar hoạt động khác nhau mà không có một giá trị?


10

Giả sử tôi có một tệp có tên 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

Tôi tải tập tin này và sau đó đi vào bộ đệm đầu và chạy:

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

(f1 5)trả về 5 như mong đợi, chỉ ra rằng cơ thể f1đang được coi my-dynamic-varlà một biến có phạm vi động, như mong đợi. Tuy nhiên, biểu mẫu cuối cùng đưa ra lỗi biến void cho my-dynamic-var, cho biết rằng nó đang sử dụng phạm vi từ vựng cho biến này. Điều này có vẻ mâu thuẫn với tài liệu cho defvar, trong đó nói:

Biểu defvarmẫu cũng khai báo biến là "đặc biệt", để nó luôn bị ràng buộc động ngay cả khi lexical-bindinglà t.

Nếu tôi thay đổi defvarbiểu mẫu trong tệp thử nghiệm để cung cấp giá trị ban đầu, thì biến luôn được coi là động, như tài liệu nói. Bất cứ ai cũng có thể giải thích tại sao phạm vi của một biến được xác định bởi việc có defvarđược cung cấp giá trị ban đầu hay không khi khai báo biến đó?

Đây là lỗi quay lại, trong trường hợp có vấn đề:

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
Tôi nghĩ rằng cuộc thảo luận trong lỗi # 18059 có liên quan.
Basil

Câu hỏi tuyệt vời, và vâng, xin vui lòng xem cuộc thảo luận về lỗi # 18059.
Drew

Tôi hiểu rồi, nên có vẻ như tài liệu sẽ được cập nhật để giải quyết vấn đề này trong Emacs 26.
Ryan C. Thompson

Câu trả lời:


8

Tại sao hai người được đối xử khác nhau chủ yếu là "bởi vì đó là những gì chúng ta cần". Cụ thể hơn, hình thức một đối số đã defvarxuất hiện từ lâu, nhưng muộn hơn so với hình thức khác và về cơ bản là một "hack" để im lặng cảnh báo trình biên dịch: tại thời điểm thực thi, nó không có tác dụng gì cả, vì vậy nó có nghĩa là "tai nạn" rằng hành vi im lặng (defvar FOO)chỉ áp dụng cho tệp hiện tại (vì trình biên dịch không có cách nào để biết rằng một defvar như vậy đã được thực thi trong một số tệp khác).

Khi lexical-bindingđược giới thiệu trong Emacs-24, chúng tôi đã quyết định sử dụng lại (defvar FOO)hình thức này , nhưng điều đó ngụ ý rằng bây giờ có hiệu lực.

Một phần để duy trì hành vi "chỉ ảnh hưởng đến tệp hiện tại" trước đó, nhưng quan trọng hơn là cho phép thư viện sử dụng totonhư một var có phạm vi động mà không ngăn các thư viện khác sử dụng totonhư một var có phạm vi từ vựng (thường là quy ước đặt tên tiền tố gói tránh xung đột, nhưng nó không được sử dụng ở mọi nơi một cách đáng buồn), hành vi mới (defvar FOO)được xác định là chỉ áp dụng cho tệp hiện tại và thậm chí đã được tinh chỉnh để nó chỉ áp dụng cho phạm vi hiện tại (ví dụ: nếu nó xuất hiện trong một chức năng, nó chỉ ảnh hưởng đến việc sử dụng var đó trong hàm đó).

Về cơ bản, (defvar FOO VAL)(defvar FOO)chỉ là hai điều "hoàn toàn khác nhau". Họ chỉ tình cờ sử dụng cùng một từ khóa vì lý do lịch sử.


1
+1 cho câu trả lời. Nhưng phương pháp của Common Lisp rõ ràng và tốt hơn, IMHO.
Drew

@Drew: Tôi hầu hết đồng ý, nhưng việc sử dụng lại (defvar FOO)làm cho chế độ mới tương thích hơn nhiều với mã cũ. Ngoài ra, IIRC một vấn đề với giải pháp của CommonLisp là khá tốn kém cho một người phiên dịch thuần túy như Elisp (ví dụ, mỗi khi bạn đánh giá letbạn phải nhìn vào bên trong cơ thể trong trường hợp có declareảnh hưởng đến một số vars).
Stefan

Đồng ý cả hai tính.
Drew

4

Dựa trên thử nghiệm, tôi tin rằng vấn đề là (defvar VAR)không có giá trị init chỉ có ảnh hưởng đến (các) thư viện mà nó xuất hiện.

Khi tôi đã thêm (defvar my-dynamic-var)vào *scratch*bộ đệm, lỗi không còn xảy ra.

Ban đầu tôi nghĩ rằng đây là tài khoản đánh giá biểu mẫu đó, nhưng sau đó tôi nhận thấy rằng chỉ cần truy cập vào tệp có biểu mẫu đó là đủ; và hơn nữa chỉ cần thêm (hoặc loại bỏ) hình thức đó trong bộ đệm, mà không đánh giá nó là đủ để thay đổi những gì đã xảy ra khi đánh giá (let ((my-dynamic-var 5)) (f2))bên trong cùng bộ đệm đó eval-last-sexp.

(Tôi không có hiểu biết thực sự về những gì đang xảy ra ở đây. Tôi thấy hành vi này đáng ngạc nhiên, nhưng tôi không quen với các chi tiết về cách thực hiện chức năng này.)

Tôi sẽ thêm rằng dạng này defvar(không có giá trị init) ngăn trình biên dịch byte phàn nàn về việc sử dụng biến động được xác định bên ngoài trong tệp elisp được biên dịch, nhưng về bản thân nó không gây ra biến đó boundp; vì vậy nó không nghiêm ngặt xác định biến. (Lưu ý rằng nếu biến boundp thì vấn đề này hoàn toàn không xảy ra.)

Trên thực tế tôi cho rằng điều này sẽ làm việc ra ok miễn là bạn làm bao gồm (defvar my-dynamic-var)trong bất kỳ thư viện từ vựng ràng buộc trong đó sử dụng của bạn my-dynamic-varbiến (mà có lẽ sẽ có một định nghĩa thực sự ở nơi khác).


Biên tập:

Nhờ con trỏ từ @npostavs trong các bình luận:

Cả hai eval-last-sexpeval-defunsử dụng eval-sexp-add-defvarsđể:

Chuẩn bị EXP với tất cả các defvars trước nó trong bộ đệm.

Cụ thể nó nằm tất cả defvar, defconstdefcustomtrường hợp. (Ngay cả khi nhận xét, tôi nhận thấy.)

Vì điều này đang tìm kiếm bộ đệm tại thời điểm gọi, nó giải thích làm thế nào các biểu mẫu này có thể có hiệu ứng trong bộ đệm ngay cả khi không được đánh giá và xác nhận rằng biểu mẫu phải xuất hiện trong cùng một tệp elisp (và cũng sớm hơn mã được đánh giá) .


2
IIUC, lỗi # 18059 xác nhận những nỗ lực của bạn.
Basil

2
Có vẻ như eval-sexp-add-defvarskiểm tra các defvars trong văn bản bộ đệm.
npostavs

1
+1. Rõ ràng tính năng này không rõ ràng, hoặc không được trình bày rõ ràng cho người dùng. Bản sửa lỗi doc cho lỗi # 18059 giúp ích, nhưng đây vẫn là một điều bí ẩn, nếu không muốn nói là dễ vỡ đối với người dùng.
Drew

0

Tôi hoàn toàn không thể tái tạo điều này, đánh giá đoạn trích sau chỉ hoạt động tốt ở đây và trả về 5 như mong đợi. Bạn có chắc chắn rằng bạn không tự đánh giá my-dynamic-var? Điều đó sẽ gây ra lỗi vì biến là void, nó chưa được đặt thành giá trị và nó sẽ chỉ có một lỗi nếu bạn tự động ràng buộc nó với một giá trị.


1
Bạn đã thiết lập lexical-bindingnon-nil trước khi đánh giá các hình thức? Tôi nhận được hành vi mà bạn mô tả bằng lexical-bindingnil, nhưng khi tôi đặt nó thành không, tôi nhận được lỗi void-biến.
Ryan C. Thompson

Vâng, tôi lưu này vào một tập tin riêng biệt, hoàn nguyên, kiểm tra rằng lexical-bindingđược thiết lập và đánh giá các hình thức tuần tự.
wasamasa

@wasamasa Tái tạo một đối với tôi, có thể bạn đã vô tình đưa my-dynamic-varmột giá trị động cấp cao nhất trong phiên hiện tại của bạn? Tôi nghĩ rằng có thể đánh dấu nó đặc biệt vĩnh viễn.
npostavs
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.