2012-02-23 34 views
9

Ở mức khái niệm, macro trong LISP (và phương ngữ) lấy một đoạn mã (dưới dạng danh sách) và trả về một đoạn mã khác (một lần nữa dưới dạng danh sách).Sự khác biệt giữa việc sử dụng danh sách và đánh dấu trở lại trong macro

Dựa trên nguyên tắc trên một macro đơn giản có thể là:

(defmacro zz [a] (list print a)) 
;macroexpand says : (#<core$print [email protected]> "Hello") 

Nhưng trong clojure này cũng có thể được viết như sau:

(defmacro zz [a] `(print ~a)) 
;macroexpand says : (clojure.core/print "Hello") 

Tôi không chắc chắn chính xác về sự khác biệt ở đây và đó nên là cách ưa thích. Việc đầu tiên trông đơn giản như chúng ta có nghĩa vụ phải trả lại danh sách và tránh sử dụng các ký tự lạ như đánh dấu trở lại.

Trả lời

8

Không ai đã chỉ ra điều này chưa ... sự khác biệt giữa 2 macro của bạn là thế này: bạn hình thức thứ hai (sử dụng backtick)

(defmacro zz [a] `(print ~a)) 

tương đương với:

(defmacro zz [a] (list 'print a)) 

Đó là khác biệt so với ví dụ đầu tiên của bạn:

(defmacro zz [a] (list print a)) 

Lưu ý câu trích dẫn còn thiếu - đó là lý do macro macro của bạn khác nhau. Tôi đồng ý với những người khác đăng bài: sử dụng backquote là thông thường hơn nếu macro của bạn có hình dạng khá đơn giản. Nếu bạn phải thực hiện mã đi bộ hoặc xây dựng động (tức là một macro phức tạp), thì việc sử dụng danh sách và xây dựng danh sách thường là những gì được thực hiện.

Tôi hy vọng giải thích này có ý nghĩa.

+1

Nó thực sự tương đương với '(danh sách \ 'in a)', hơi khác: '' in' sẽ chỉ hoạt động nếu người gọi có' print' tham chiếu đến 'clojure.core/print' chứ không phải là cục bộ, hoặc không có ràng buộc nào cả (nói rằng họ đã loại trừ nó khỏi ': refer-clojure' của không gian tên của chúng). '\' print', mặt khác, mở rộng trực tiếp đến 'clojure.core/print' để nó không rõ ràng và chính xác trong bất kỳ ngữ cảnh nào. – amalloy

+0

@amalloy Cảm ơn bạn đã trả lời, dựa trên sugestion của bạn, tôi đã thử thực hiện macroexpand (thông qua slime) trên '' '(print ~ a)' và tôi đã trở lại: '(seq (concat (list 'print) (liệt kê a))) '- ah, nhưng bạn đang nói rằng dạng tiền được biến đổi là giống như việc có' '' in' phải không? –

+0

Tôi không thể đọc phản hồi của bạn rất tốt vì SO đang ăn một số ký tự backtick, nhưng: Slime đang ẩn các không gian tên từ bạn (và macroexpand là không cần thiết). Nếu bạn chỉ trích dẫn biểu thức, bằng cách gõ ''\' (print ~ a) 'trong repl, bạn sẽ thấy nó tương đương với' (clojure.core/seq (clojure.core/concat (clojure.core/list (quote) clojure.core/print)) (clojure.core/list a))) '. 'Clojure.core/print' là sự khác biệt quan trọng mà tôi đã tạo ra. – amalloy

1

Theo kinh nghiệm của tôi, chúng tương đương nhau. Mặc dù có thể có một số trường hợp cạnh tôi không biết.

dụ @islon 's tương đương có thể được viết như sau:

Để đối chiếu, các mã trên tương đương có thể được viết như sau:

(defmacro unless [condition & body] 
    (list 'if (list 'not condition) 
      (list* 'do body))) 
5

Có sự khác biệt phong cách giữa chúng. Ví dụ của bạn rất đơn giản nhưng trong các macro phức tạp, sự khác biệt sẽ lớn hơn.

Ví dụ trừ khi vĩ mô như đã định nghĩa trong "The Joy of Clojure" cuốn sách:

(defmacro unless [condition & body] 
    `(if (not ~condition) 
     (do [email protected]))) 

Từ cuốn sách:

Cú pháp-quote cho phép if-mẫu sau để hoạt động như một loại mẫu cho biểu thức rằng bất kỳ việc sử dụng macro nào sẽ trở thành khi được mở rộng.

Khi tạo macro luôn chọn kiểu có thể đọc được và thành ngữ nhất.

Để đối chiếu, các mã trên tương đương có thể được viết như sau:

(defmacro unless [condition & body] 
    (list 'if (list 'not condition) 
      (list* 'do body))) 
6

Xây dựng danh sách rõ ràng là "đơn giản nhất", theo cách nào đó, vì có vài khái niệm cốt lõi bạn cần biết: chỉ chấp nhận danh sách và thay đổi nó cho đến khi bạn có danh sách mới. Backtick là một phím tắt thuận tiện cho các khối "templating"; có thể viết bất kỳ macro nào mà không có nó, nhưng đối với bất kỳ macro lớn nào thì nó nhanh chóng trở nên rất khó chịu. Ví dụ, hãy xem xét hai cách viết let như một vĩ mô đối với fn:

(defmacro let [bindings & body] 
    (let [names (take-nth 2 bindings) 
     vals (take-nth 2 (rest bindings))] 
    `((fn [[email protected]] 
     (do [email protected])) 
     [email protected]))) 

(defmacro let [bindings & body] 
    (let [names (take-nth 2 bindings) 
     vals (take-nth 2 (rest bindings))] 
    (cons (list `fn (vec names) (cons `do body)) 
      vals))) 

Trong trường hợp đầu tiên, sử dụng backtick làm cho nó khá rõ ràng rằng bạn đang viết một hàm trong những cái tên có chứa cơ thể, và sau đó gọi nó với các giá trị - mã macro được "định hình" giống như mã mở rộng, vì vậy bạn có thể tưởng tượng nó sẽ trông như thế nào.

Trong trường hợp thứ hai, chỉ với conslist tất cả các nơi, đó là một cơn đau đầu thực sự để làm việc ra những gì sẽ mở rộng như thế nào. Điều này không phải luôn luôn như vậy, tất nhiên: đôi khi nó có thể rõ ràng hơn để viết một cái gì đó mà không có một backtick.

Một điểm rất quan trọng khác được thực hiện bởi Kyle Burton: print không giống như 'print! Việc mở rộng macro của bạn phải chứa biểu tượng print, không phải giá trị của nó (là một hàm). Các đối tượng nhúng (chẳng hạn như các hàm) trong mã rất dễ vỡ và chỉ hoạt động một cách tình cờ. Vì vậy, hãy đảm bảo macro của bạn mở rộng sang mã bạn thực sự có thể tự viết và để hệ thống đánh giá thực hiện công việc khó khăn - bạn có thể nhập vào biểu tượng print, nhưng bạn không thể nhập con trỏ vào giá trị hiện tại của hàm print .

Các vấn đề liên quan