Khi nào sử dụng '(hoặc trích dẫn) trong Lisp?


114

Sau khi xem qua các phần chính của cuốn sách Lisp giới thiệu, tôi vẫn không thể hiểu hàm toán tử đặc biệt (quote)(hoặc tương đương ') làm gì, nhưng điều này đã nằm trong toàn bộ mã Lisp mà tôi đã thấy.

Nó làm gì?

Câu trả lời:


178

Câu trả lời ngắn Bỏ qua các quy tắc đánh giá mặc định và không đánh giá biểu thức (biểu tượng hoặc s-exp), chuyển nó cùng với hàm chính xác như đã nhập.

Câu trả lời dài: Quy tắc đánh giá mặc định

Khi một hàm thông thường (tôi sẽ nói đến điều đó sau) được gọi, tất cả các đối số được chuyển cho nó sẽ được đánh giá. Điều này có nghĩa là bạn có thể viết như sau:

(* (+ a 2)
   3)

Lần lượt đánh giá (+ a 2), bằng cách đánh giá avà 2. Giá trị của biểu tượng ađược tra cứu trong tập ràng buộc biến hiện tại, và sau đó được thay thế. Say ahiện bị ràng buộc với giá trị 3:

(let ((a 3))
  (* (+ a 2)
     3))

Chúng ta sẽ nhận được (+ 3 2), + sau đó được gọi trên 3 và 2 cho kết quả là 5. Dạng ban đầu của chúng ta bây giờ là (* 5 3)15.

Giải thích quoterồi!

Ổn thỏa. Như đã thấy ở trên, tất cả các đối số của một hàm đều được đánh giá, vì vậy nếu bạn muốn chuyển ký hiệu a chứ không phải giá trị của nó, bạn không muốn đánh giá nó. Các ký hiệu Lisp có thể nhân đôi cả hai giá trị của chúng và các điểm đánh dấu mà bạn trong các ngôn ngữ khác sẽ sử dụng các chuỗi, chẳng hạn như khóa của bảng băm.

Đây là nơi quotexuất hiện. Giả sử bạn muốn vẽ biểu đồ phân bổ tài nguyên từ một ứng dụng Python, nhưng thay vì vẽ biểu đồ trong Lisp. Yêu cầu ứng dụng Python của bạn làm điều gì đó như sau:

print("'(")
while allocating:
    if random.random() > 0.5:
        print(f"(allocate {random.randint(0, 20)})")
    else:
        print(f"(free {random.randint(0, 20)})")
    ...
print(")")

Cung cấp cho bạn đầu ra trông như thế này (hơi đẹp):

'((allocate 3)
  (allocate 7)
  (free 14)
  (allocate 19)
  ...)

Hãy nhớ những gì tôi đã nói về quote("đánh dấu") khiến quy tắc mặc định không được áp dụng? Tốt. Điều gì khác sẽ xảy ra là các giá trị của allocatefreeđược tra cứu, và chúng tôi không muốn điều đó. Trong Lisp của chúng tôi, chúng tôi muốn làm:

(dolist (entry allocation-log)
  (case (first entry)
    (allocate (plot-allocation (second entry)))
    (free (plot-free (second entry)))))

Đối với dữ liệu được đưa ra ở trên, chuỗi lệnh gọi hàm sau đây sẽ được thực hiện:

(plot-allocation 3)
(plot-allocation 7)
(plot-free 14)
(plot-allocation 19)

Nhưng Điều gì về list?

Vâng, đôi khi bạn làm muốn đánh giá các đối số. Giả sử bạn có một hàm tiện lợi thao tác một số và một chuỗi và trả về một danh sách các ... kết quả. Hãy bắt đầu sai:

(defun mess-with (number string)
  '(value-of-number (1+ number) something-with-string (length string)))

Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER (1+ NUMBER) SOMETHING-WITH-STRING (LENGTH STRING))

Chào! Đó không phải là những gì chúng tôi muốn. Chúng tôi muốn đánh giá một cách có chọn lọc một số đối số và để những đối số khác làm biểu tượng. Hãy thử # 2!

(defun mess-with (number string)
  (list 'value-of-number (1+ number) 'something-with-string (length string)))

Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER 21 SOMETHING-WITH-STRING 3)

Không chỉ quote, nhưngbackquote

Tốt hơn nhiều! Ngẫu nhiên, mẫu này rất phổ biến trong (hầu hết) macro, đến mức có một cú pháp đặc biệt để thực hiện điều đó. Phần trích dẫn sau:

(defun mess-with (number string)
  `(value-of-number ,(1+ number) something-with-string ,(length string)))

Nó giống như sử dụng quote, nhưng với tùy chọn để đánh giá rõ ràng một số đối số bằng cách đặt trước chúng bằng dấu phẩy. Kết quả tương đương với việc sử dụng list, nhưng nếu bạn đang tạo mã từ macro, bạn thường chỉ muốn đánh giá các phần nhỏ của mã được trả về, vì vậy backquote phù hợp hơn. Đối với danh sách ngắn hơn, listcó thể dễ đọc hơn.

Này, Bạn Quên Về quote!

Vậy thứ này bỏ chúng ta ở đâu? Ồ đúng rồi, quotethực sự thì làm gì? Nó chỉ đơn giản trả về (các) đối số của nó không được đánh giá! Hãy nhớ những gì tôi đã nói ở phần đầu về các hàm thông thường? Lần lượt ra rằng một số nhà khai thác / chức năng cần không đánh giá lập luận của họ. Chẳng hạn như IF - bạn sẽ không muốn nhánh khác được đánh giá nếu nó không được thực hiện, phải không? Cái được gọi là toán tử đặc biệt , cùng với macro, hoạt động như vậy. Các toán tử đặc biệt cũng là "tiên đề" của ngôn ngữ - tập hợp các quy tắc tối thiểu - mà bạn có thể triển khai phần còn lại của Lisp bằng cách kết hợp chúng với nhau theo những cách khác nhau.

Quay lại quote, mặc dù:

Lisp> (quote spiffy-symbol)
SPIFFY-SYMBOL

Lisp> 'spiffy-symbol ; ' is just a shorthand ("reader macro"), as shown above
SPIFFY-SYMBOL

So sánh với (trên Steel-Bank Common Lisp):

Lisp> spiffy-symbol
debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD "initial thread" RUNNING   {A69F6A9}>:
  The variable SPIFFY-SYMBOL is unbound.

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-INT:SIMPLE-EVAL-IN-LEXENV SPIFFY-SYMBOL #<NULL-LEXENV>)
0] 

Bởi vì không có spiffy-symboltrong phạm vi hiện tại!

Tổng hợp

quote, backquote(với dấu phẩy), và listlà một số công cụ bạn sử dụng để tạo danh sách, không chỉ là danh sách các giá trị, mà như bạn đã thấy có thể được sử dụng như structcấu trúc dữ liệu nhẹ (không cần xác định a )!

Nếu bạn muốn tìm hiểu thêm, tôi giới thiệu cuốn sách Practical Common Lisp của Peter Seibel để có cách tiếp cận thực tế để học Lisp, nếu bạn đã học lập trình nói chung. Cuối cùng trong hành trình Lisp của mình, bạn cũng sẽ bắt đầu sử dụng các gói. Ron Garret's The Idiot's Guide to Common Lisp Packages sẽ giải thích tốt cho bạn về những điều đó.

Chúc bạn hack vui vẻ!


Trong emacs của tôi, SBCL được thiết lập và khi tôi nhập `` this '' là true `` Nó chỉ trả về cuối cùng tức là TRUE ở đầu ra. Ngay cả trong portacle Tôi nhận được cùng một đầu ra
Totoro

@Totoro Giá trị trả về của một hàm hoặc chỉ nhiều câu lệnh trong lisp là biểu thức cuối cùng, vì vậy nó thực sự trả về this, sau isđó true, nhưng bạn chỉ thấy câu cuối cùng được trả về. (điều này đúng và đúng là các tuyên bố riêng biệt)
Wezl

52

Nó nói "đừng đánh giá tôi". Ví dụ: nếu bạn muốn sử dụng một danh sách dưới dạng dữ liệu chứ không phải dưới dạng mã, bạn phải đặt một trích dẫn ở trước nó. Ví dụ,

(print '(+ 3 4))bản in "(+ 3 4)", trong khi (print (+ 3 4))bản in "7"


Làm thế nào có thể đánh giá nó sau đó chẳng hạn như có một unquotelệnh?
Lime

3
@William Lisps có một chức năng thuận tiện gọi là eval: (print (eval '(+ 3 4))). Đây là điều làm cho Lisps trở nên tuyệt vời: danh sách là mã và mã là danh sách, vì vậy một chương trình Lisp có thể tự thao tác.
darkfeline

18

Những người khác đã trả lời câu hỏi này một cách đáng ngưỡng mộ, và Matthias Benkard đưa ra một lời cảnh báo tuyệt vời.

KHÔNG SỬ DỤNG QUOTE ĐỂ TẠO DANH SÁCH MÀ BẠN SẼ SỬA ĐỔI SAU. Đặc tả cho phép trình biên dịch coi các danh sách được trích dẫn là hằng số. Thông thường, trình biên dịch sẽ tối ưu hóa các hằng số bằng cách tạo một giá trị duy nhất cho chúng trong bộ nhớ và sau đó tham chiếu đến giá trị duy nhất đó từ tất cả các vị trí mà hằng số xuất hiện. Nói cách khác, nó có thể coi hằng số như một biến toàn cục ẩn danh.

Điều này có thể gây ra các vấn đề rõ ràng. Nếu bạn sửa đổi một hằng số, nó rất có thể sửa đổi các cách sử dụng khác của cùng một hằng số trong mã hoàn toàn không liên quan. Ví dụ: bạn có thể so sánh một số biến với '(1 1) trong một số hàm, và trong một hàm hoàn toàn khác, hãy bắt đầu một danh sách bằng' (1 1) và sau đó thêm nhiều thứ vào đó. Khi chạy các hàm này, bạn có thể thấy rằng hàm đầu tiên không khớp với mọi thứ nữa, bởi vì nó hiện đang cố gắng so sánh biến với '(1 1 2 3 5 8 13), là hàm thứ hai trả về. Hai hàm này hoàn toàn không liên quan đến nhau, nhưng chúng có ảnh hưởng lẫn nhau vì việc sử dụng các hằng số. Thậm chí những hiệu ứng xấu thậm chí còn có thể xảy ra, như một danh sách hoàn toàn bình thường, đột nhiên lặp lại vòng lặp vô hạn.

Sử dụng trích dẫn khi bạn cần một danh sách không đổi, chẳng hạn như để so sánh. Sử dụng danh sách khi bạn sẽ sửa đổi kết quả.


Vì vậy, có vẻ như bạn nên sử dụng (list (+ 1 2)) hầu hết thời gian. Nếu vậy làm thế nào để bạn ngăn chặn việc đánh giá (+ 1 2)bên trong một ví dụ như vậy? Có unquotelệnh không?
Lime

1
Bạn có muốn tương đương với '((3)), hoặc tương đương với '((+ 1 2))? Nếu sau này, bạn phải sử dụng nhiều list: (list (list '+ 1 2)). Hoặc nếu bạn muốn tương đương với '(+ 1 2), chỉ (list '+ 1 2). Và hãy nhớ rằng, nếu bạn không sửa đổi danh sách, hãy sử dụng trích dẫn: không có gì sai '(+ 1 2)nếu bạn chỉ so sánh với nó hoặc một cái gì đó.
Xanthir

1
Bạn có phiền khi tham khảo danh sách được trích dẫn được coi là hằng số không?
Lime

HyperSpec clhs.lisp.se/Body/s_quote.htm cho biết hành vi là không xác định nếu đối tượng được trích dẫn bị sửa đổi triệt để . Nó ngụ ý rằng điều này là để cho phép hàm ý coi các giá trị là giá trị nguyên tử.
Xanthir

14

Một câu trả lời cho câu hỏi này nói rằng QUOTE “tạo cấu trúc dữ liệu danh sách”. Điều này không hoàn toàn đúng. QUOTE là cơ bản hơn thế này. Trên thực tế, QUOTE là một toán tử tầm thường: Mục đích của nó là ngăn chặn bất cứ điều gì xảy ra. Đặc biệt, nó không tạo ra bất cứ thứ gì.

Điều (QUOTE X) nói về cơ bản là "đừng làm gì cả, chỉ đưa tôi X". X không cần phải là một danh sách như trong (QUOTE (ABC)) hoặc một ký hiệu như trong (QUOTE FOO). Nó có thể là bất kỳ đối tượng nào. Thật vậy, kết quả đánh giá danh sách được tạo bởi (LIST 'QUOTE SOME-OBJECT) sẽ luôn trả về SOME-OBJECT, bất kể nó là gì.

Bây giờ, lý do mà (QUOTE (ABC)) có vẻ như là nó đã tạo ra một danh sách có các phần tử là A, B và C là vì một danh sách như vậy thực sự là những gì nó trả về; nhưng tại thời điểm biểu mẫu QUOTE được đánh giá, danh sách thường đã tồn tại một thời gian (như một thành phần của biểu mẫu QUOTE!), được tạo bởi trình tải hoặc trình đọc trước khi thực thi mã.

Một ngụ ý của điều này có xu hướng làm tăng người mới khá thường xuyên là việc sửa đổi danh sách được trả về bởi biểu mẫu QUOTE là rất không khôn ngoan. Dữ liệu do QUOTE trả về, đối với mọi ý định và mục đích, được coi là một phần của đang được thực thi và do đó nên được coi là chỉ đọc!


11

Trích dẫn ngăn cản việc thực thi hoặc đánh giá biểu mẫu, thay vào đó biến nó thành dữ liệu. Nói chung, bạn có thể thực thi dữ liệu bằng cách đánh giá nó.

quote tạo cấu trúc dữ liệu danh sách, ví dụ: các cấu trúc sau tương đương:

(quote a)
'a

Nó cũng có thể được sử dụng để tạo danh sách (hoặc cây):

(quote (1 2 3))
'(1 2 3)

Có lẽ tốt nhất bạn nên mua một cuốn sách giới thiệu về ngọng, chẳng hạn như Bảng liệt kê chung thực tế (có sẵn để đọc trực tuyến).


3

Trong Emacs Lisp:

Những gì có thể được trích dẫn?

Danh sách và biểu tượng.

Trích dẫn một số đánh giá chính số đó: '5giống như 5.

Điều gì xảy ra khi bạn trích dẫn danh sách?

Ví dụ:

'(one two) đánh giá

(list 'one 'two) đánh giá

(list (intern "one") (intern ("two"))).

(intern "one")tạo một biểu tượng có tên là "một" và lưu trữ nó trong một bản đồ băm "trung tâm", vì vậy bất cứ lúc nào bạn nói 'onethì biểu tượng có tên "one"sẽ được tra cứu trong bản đồ băm trung tâm đó.

Nhưng biểu tượng là gì?

Ví dụ: trong ngôn ngữ OO (Java / Javascript / Python), một biểu tượng có thể được biểu diễn dưới dạng một đối tượng có một nametrường, đó là tên của biểu tượng như "one"trên, và dữ liệu và / hoặc mã có thể được liên kết với đối tượng này.

Vì vậy, một biểu tượng trong Python có thể được triển khai như:

class Symbol:
   def __init__(self,name,code,value):
       self.name=name
       self.code=code
       self.value=value

Ví dụ, trong Emacs Lisp, một biểu tượng có thể có 1) dữ liệu được liên kết với nó VÀ (đồng thời - cho cùng một biểu tượng) 2) mã được liên kết với nó - tùy thuộc vào ngữ cảnh, dữ liệu hoặc mã được gọi.

Ví dụ, trong Elisp:

(progn
  (fset 'add '+ )
  (set 'add 2)
  (add add add)
)

đánh giá 4.

(add add add)đánh giá là:

(add add add)
(+ add add)
(+ 2 add)
(+ 2 2)
4

Vì vậy, ví dụ, bằng cách sử dụng Symbollớp mà chúng tôi đã xác định bằng Python ở trên, addELisp-Symbol này có thể được viết bằng Python Symbol("add",(lambda x,y: x+y),2).

Rất cảm ơn mọi người trên IRC #emacs đã giải thích các ký hiệu và dấu ngoặc kép cho tôi.


2

Khi chúng ta muốn truyền một đối số thay vì truyền giá trị của đối số thì chúng ta sử dụng dấu ngoặc kép. Nó chủ yếu liên quan đến thủ tục truyền trong quá trình sử dụng danh sách, cặp và nguyên tử không có trong Ngôn ngữ lập trình C (hầu hết mọi người bắt đầu lập trình bằng lập trình C, do đó chúng tôi bị nhầm lẫn) Đây là mã trong ngôn ngữ lập trình Scheme, một phương ngữ của ngọng và tôi đoán bạn có thể hiểu mã này.

(define atom?              ; defining a procedure atom?
  (lambda (x)              ; which as one argument x
(and (not (null? x)) (not(pair? x) )))) ; checks if the argument is atom or not
(atom? '(a b c)) ; since it is a list it is false #f

Dòng cuối cùng (nguyên tử? 'Abc) đang chuyển abc vì nó là thủ tục để kiểm tra xem abc có phải là nguyên tử hay không, nhưng khi bạn chuyển (nguyên tử? Abc) thì nó sẽ kiểm tra giá trị của abc và dán giá trị vào nó. Vì chúng tôi không cung cấp bất kỳ giá trị nào cho nó


1

Trích dẫn trả về đại diện bên trong của các đối số của nó. Sau khi xem qua quá nhiều lời giải thích về những điều mà câu trích dẫn không có tác dụng, đó là lúc bóng đèn bật sáng. Nếu REPL không chuyển đổi tên hàm thành UPPER-CASE khi tôi trích dẫn chúng, thì có thể nó đã không phát hiện ra với tôi.

Vì thế. Các hàm Lisp thông thường chuyển đổi các đối số của chúng thành một đại diện bên trong, đánh giá các đối số và áp dụng hàm. Trích dẫn chuyển đổi các đối số của nó thành một đại diện bên trong và chỉ trả về điều đó. Về mặt kỹ thuật, câu trích dẫn có nội dung "không đánh giá" là chính xác, nhưng khi tôi cố gắng hiểu nó đã làm gì, nói với tôi những gì nó không làm thật là bực bội. Máy nướng bánh mì của tôi cũng không đánh giá các chức năng Lisp; nhưng đó không phải là cách bạn giải thích những gì một máy nướng bánh mì làm.


1

Câu trả lời ngắn gọn của Anoter:

quotecó nghĩa là không đánh giá nó, và backquote là trích dẫn nhưng để lại cửa sau .

Một sự giới thiệu tốt:

Tài liệu tham khảo Emacs Lisp làm cho nó rất rõ ràng

9.3 Trích dẫn

Trích dẫn dạng đặc biệt trả về đối số duy nhất của nó, như đã viết, mà không đánh giá nó. Điều này cung cấp một cách để bao gồm các ký hiệu và danh sách không đổi, không phải là các đối tượng tự đánh giá, trong một chương trình. (Không cần thiết phải trích dẫn các đối tượng tự đánh giá như số, chuỗi và vectơ.)

Hình thức đặc biệt: đối tượng trích dẫn

This special form returns object, without evaluating it. 

Bởi vì trích dẫn được sử dụng rất thường xuyên trong các chương trình, Lisp cung cấp một cú pháp đọc thuận tiện cho nó. Một ký tự dấu nháy đơn ('' ') theo sau là đối tượng Lisp (trong cú pháp đọc) mở rộng thành danh sách có phần tử đầu tiên là dấu ngoặc kép và phần tử thứ hai là đối tượng. Do đó, cú pháp đọc 'x là viết tắt của (trích dẫn x).

Dưới đây là một số ví dụ về các biểu thức sử dụng dấu ngoặc kép:

(quote (+ 1 2))
      (+ 1 2)

(quote foo)
      foo

'foo
      foo

''foo
      (quote foo)

'(quote foo)
      (quote foo)

9.4 Trích dẫn ngược

Cấu trúc backquote cho phép bạn trích dẫn một danh sách, nhưng đánh giá có chọn lọc các phần tử của danh sách đó. Trong trường hợp đơn giản nhất, nó giống với trích dẫn dạng đặc biệt (được mô tả trong phần trước; xem Trích dẫn). Ví dụ: hai hình thức này mang lại kết quả giống hệt nhau:

`(a list of (+ 2 3) elements)
      (a list of (+ 2 3) elements)

'(a list of (+ 2 3) elements)
      (a list of (+ 2 3) elements)

Dấu đặc biệt ',' bên trong đối số để trích dẫn lại cho biết một giá trị không phải là hằng số. Trình đánh giá Emacs Lisp đánh giá đối số của ',' và đặt giá trị vào cấu trúc danh sách:

`(a list of ,(+ 2 3) elements)
      (a list of 5 elements)

Thay thế bằng ',' cũng được phép ở các cấp sâu hơn của cấu trúc danh sách. Ví dụ:

`(1 2 (3 ,(+ 4 5)))
      (1 2 (3 9))

Bạn cũng có thể ghép một giá trị đã đánh giá vào danh sách kết quả, sử dụng dấu đặc biệt ', @'. Các phần tử của danh sách được nối trở thành các phần tử ở cùng cấp với các phần tử khác của danh sách kết quả. Mã tương đương mà không sử dụng '' 'thường không thể đọc được. Dưới đây là một số ví dụ:

(setq some-list '(2 3))
      (2 3)

(cons 1 (append some-list '(4) some-list))
      (1 2 3 4 2 3)

`(1 ,@some-list 4 ,@some-list)
      (1 2 3 4 2 3)

1
Code is data and data is code.  There is no clear distinction between them.

Đây là một câu lệnh cổ điển mà bất kỳ lập trình viên lisp nào cũng biết.

Khi bạn trích dẫn một mã, mã đó sẽ là dữ liệu.

1 ]=> '(+ 2 3 4)
;Value: (+ 2 3 4)

1 ]=> (+ 2 3 4)
;Value: 9

Khi bạn trích dẫn một mã, kết quả sẽ là dữ liệu đại diện cho mã đó. Vì vậy, khi bạn muốn làm việc với dữ liệu đại diện cho một chương trình, bạn trích dẫn chương trình đó. Điều này cũng hợp lệ cho các biểu thức nguyên tử, không chỉ cho các danh sách:

1 ]=> 'code
;Value: code

1 ]=> '10
;Value: 10

1 ]=> '"ok"
;Value: "ok"

1 ]=> code
;Unbound variable: code

Giả sử bạn muốn tạo một ngôn ngữ lập trình được nhúng trong lisp - bạn sẽ làm việc với các chương trình được trích dẫn trong lược đồ (như '(+ 2 3)) và được hiểu là mã bằng ngôn ngữ bạn tạo, bằng cách cung cấp cho các chương trình một diễn giải ngữ nghĩa. Trong trường hợp này, bạn cần sử dụng quote để giữ dữ liệu, nếu không nó sẽ được đánh giá bằng ngôn ngữ bên ngoài.

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.