Câu hỏi setq và defvar trong Lisp


Tôi thấy rằng Thực tế thường gặp Lisp sử dụng (defvar *db* nil) để thiết lập biến toàn cầu. Nó có ổn không khi sử dụng setq cho cùng một mục đích?

Ưu điểm / nhược điểm của việc sử dụng defvar so với setq?


41
2017-10-04 13:52


gốc




Các câu trả lời:


Có một số cách để giới thiệu các biến.

DEFVAR và DEFPARAMETER giới thiệu toàn cầu biến động. DEFVAR tùy chọn đặt nó thành một số giá trị, trừ khi nó đã được xác định. DEFPARAMETER đặt nó luôn luôn là giá trị được cung cấp. SETQ không giới thiệu một biến.

(defparameter *number-of-processes* 10)

(defvar *world* (make-world))     ; the world is made only once.

Lưu ý rằng bạn có thể không bao giờ muốn DEFVAR các biến có tên như x, y, stream, limit, ... Tại sao? Bởi vì các biến này sau đó sẽ được khai báo đặc biệt và khó có thể hoàn tác điều đó. Tuyên bố đặc biệt là toàn cầu và tất cả các sử dụng tiếp theo của biến sẽ sử dụng ràng buộc động.

XẤU:

(defvar x 10)     ; global special variable X, naming convention violated
(defvar y 20)     ; global special variable Y, naming convention violated

(defun foo ()
  (+ x y))        ; refers to special variables X and y

(defun bar (x y)  ; OOPS!! X and Y are special variables
                  ; even though they are parameters of a function!
  (+ (foo) x y))

(bar 5 7)         ; ->   24

TỐT HƠN: Luôn đánh dấu các biến đặc biệt với * trong tên của họ!

(defvar *x* 10)     ; global special variable *X*
(defvar *y* 20)     ; global special variable *Y*

(defun foo ()
  (+ *x* *y*))      ; refers to special variables X and y

(defun bar (x y)    ; Yep! X and Y are lexical variables
  (+ (foo) x y))

(bar 5 7)           ;  ->   42

Các biến cục bộ được giới thiệu với DEFUN, LAMBDA, ĐỂ CHO, MULTIPLE-VALUE-BIND và nhiều thứ khác.

(defun foo (i-am-a-local-variable)
   (print i-am-a-local-variable))

(let ((i-am-also-a-local-variable 'hehe))
  (print i-am-also-a-local-variable))

Bây giờ, theo mặc định, các biến cục bộ trong hai biểu mẫu trên là từ vựng, trừ khi chúng được khai báo ĐẶC BIỆT. Sau đó, chúng sẽ là các biến động.

Tiếp theo, cũng có một số biểu mẫu để đặt biến thành giá trị mới.  BỘ, SETQ, SETF và những người khác. SETQ và SETF có thể đặt cả hai biến từ vựng và đặc biệt (động).

Nó là cần thiết cho mã di động mà một bộ biến đã được khai báo. Hiệu quả chính xác của việc thiết lập một biến không được khai báo là không được xác định bởi tiêu chuẩn.

Vì vậy, nếu bạn biết những gì thực hiện Common Lisp của bạn, bạn có thể sử dụng

(setq world (make-new-world))

bên trong Đọc-Đánh giá-In-Loop ở phía trên. Nhưng đừng sử dụng nó trong mã của bạn, vì hiệu ứng không phải là di động. Thông thường SETQ sẽ đặt biến. Nhưng một số triển khai cũng có thể khai báo biến ĐẶC BIỆT khi nó không biết nó (CMU Common Lisp làm điều đó theo mặc định). Đó hầu như không phải là thứ bạn muốn. Sử dụng nó để sử dụng bình thường nếu bạn biết những gì bạn làm, nhưng không phải cho mã.

Tương tự ở đây:

(defun make-shiny-new-world ()
  (setq world (make-world 'shiny)))

Đầu tiên, các biến như vậy sẽ được viết là *world*  (với xung quanh * ký tự), để làm rõ rằng nó là một biến đặc biệt toàn cầu. Thứ hai, nó phải được khai báo với DEFVAR hoặc là DEFPARAMETER trước.

Một trình biên dịch Lisp điển hình sẽ phàn nàn rằng biến trên không khai báo. Vì các biến từ vựng toàn cầu không tồn tại trong Common Lisp, trình biên dịch phải tạo mã để tra cứu động. Một số trình biên dịch sau đó nói, được rồi, chúng tôi giả định rằng đây là một tra cứu động, hãy khai báo nó đặc biệt - vì đó là những gì chúng tôi giả định.


40
2017-10-04 16:45



Tôi ngạc nhiên rằng một khi bạn đã khai báo biến toàn cầu với defvar, không có cách nào bạn có thể tạo một biến cục bộ có cùng tên, vì vậy (let ((x 1)) x) có thể tạo ra các kết quả bất ngờ nếu x là tuyên bố bởi defvar. - Ian
@ian Đó là một lý do tại sao nhiều người sử dụng "bịt tai" (nghĩa là, họ không bao giờ sử dụng (defvar x foo), họ dùng (defvar *x* foo), theo cách đó, bạn ít có khả năng phạm sai lầm hơn. - Vatine


defvar giới thiệu một biến động trong khi setq được sử dụng để gán giá trị cho biến động hoặc từ vựng. Giá trị của một biến động được tìm kiếm trong môi trường gọi hàm, trong khi giá trị của một biến lexical được tìm kiếm trong môi trường mà hàm được định nghĩa. Ví dụ sau sẽ làm cho sự khác biệt rõ ràng:

;; dynamic variable sample
> (defvar *x* 100)
*X*
> (defun fx () *x*)
FX
> (fx)
100
> (let ((*x* 500)) (fx)) ;; gets the value of *x* from the dynamic scope.
500
> (fx) ;; *x* now refers to the global binding.
100

;; example of using a lexical variable
> (let ((y 200))
   (let ((fy (lambda () (format t "~a~%" y))))
     (funcall fy) ;; => 200
     (let ((y 500))
       (funcall fy) ;; => 200, the value of lexically bound y
       (setq y 500) ;; => y in the current environment is modified
       (funcall fy)) ;; => 200, the value of lexically bound y, which was 
                     ;; unaffected by setq
     (setq y 500) => ;; value of the original y is modified.
     (funcall fy))) ;; => 500, the new value of y in fy's defining environment.

Các biến động hữu ích khi truyền xung quanh giá trị mặc định. Ví dụ, chúng ta có thể ràng buộc biến động *out* với đầu ra tiêu chuẩn, để nó trở thành đầu ra mặc định của tất cả các hàm io. Để ghi đè hành vi này, chúng tôi chỉ giới thiệu một ràng buộc cục bộ:

> (defun my-print (s)
        (format *out* "~a~%" s))
MY-PRINT
> (my-print "hello")
hello
> (let ((*out* some-stream))
    (my-print " cruel ")) ;; goes to some-stream
> (my-print " world.")
world

Việc sử dụng phổ biến các biến từ vựng là xác định các bao đóng, để mô phỏng các đối tượng với trạng thái. Trong ví dụ đầu tiên, biến y trong môi trường ràng buộc của fy đã trở thành trạng thái riêng của hàm đó.

defvar sẽ chỉ gán một giá trị cho một biến nếu nó chưa được gán. Vì vậy, định nghĩa lại sau đây của *x* sẽ không thay đổi ràng buộc ban đầu:

> (defvar *x* 400)
*X*
> *x*
100

Chúng ta có thể gán một giá trị mới cho *x* bằng cách sử dụng setq:

> (setq *x* 400)
400
> *x*
400
> (fx)
400
> (let ((*x* 500)) (fx)) ;; setq changed the binding of *x*, but 
                         ;; its dynamic property still remains.
500
> (fx)
400

17
2017-10-04 14:34



Thật không may điều này là sai. Hiệu ứng chính xác của một (setq y 200) trên một biến không được khai báo / xác định là không xác định. Lisp chung cũng không có các biến từ vựng toàn cầu. SETQ đặt một biến. Chỉ có bấy nhiêu thôi. Biến động hoặc biến từ vựng, tùy thuộc vào biến được cung cấp. LET ràng buộc. SETQ bộ. - Rainer Joswig
Ngoài ra người ta không thể định nghĩa một hàm CL: PRINT, vì tên đó đã được thực hiện bởi hàm chuẩn. FORMAT in thành luồng chứ không phải tệp. - Rainer Joswig
@Rainer Cảm ơn bạn đã chỉ ra những điểm không chính xác. Tôi đã cập nhật câu trả lời. - Vijay Mathew


DEFVAR thiết lập một biến mới. SETQ gán cho một biến.

Hầu hết các triển khai Lisp mà tôi đã sử dụng sẽ đưa ra cảnh báo nếu bạn SETQ thành một biến chưa tồn tại.


7
2017-10-04 14:03





defvar và defparameter cả hai giới thiệu các biến toàn cầu. Như Ken lưu ý, setq gán cho một biến.

Ngoài ra, defvar sẽ không che giấu điều gì đó trước đây defvar-ed. Seibel nói sau này trong cuốn sách (Chương 6): "Thực tế, bạn nên sử dụng DEFVAR để xác định các biến chứa dữ liệu bạn muốn giữ ngay cả khi bạn đã thực hiện thay đổi mã nguồn sử dụng biến".

http://www.gigamonkeys.com/book/variables.html

Ví dụ: nếu bạn có toàn cầu *db* cho cơ sở dữ liệu trong chương Cơ sở dữ liệu đơn giản:

(defvar *db* nil)

... và bạn bắt đầu chơi với nó tại REPL - thêm, xóa mọi thứ, v.v. - nhưng sau đó bạn thực hiện thay đổi đối với tệp nguồn có chứa định dạng defvar đó, việc tải lại tệp đó sẽ không quét sạch *db* và tất cả những thay đổi bạn có thể đã thực hiện ... Tôi tin rằng setq sẽ, như sẽ defparameter. Một Lisper có kinh nghiệm hơn xin vui lòng sửa tôi nếu tôi là sai mặc dù.


7
2017-10-04 14:03