Khi nào tôi sẽ yêu cầu Macro thay vì hàm?


7

Tôi chưa quen với Clojure, tôi mới biết về Macros và tôi không có nền tảng trước về Lisp. Tôi đã tiếp tục tạo trường hợp chuyển đổi của riêng mình như biểu mẫu và kết thúc bằng việc này:

(defmacro switch-case [v cases default] (if (cases v) (cases v) default )) 

và sau đó đã thử tạo một chức năng và kết thúc với điều này:

(defn fn-switch-case [v cases default] (if (cases v) (cases v) default ))

Cả hai

(switch-case 5 {6 "six" 7 "seven"} "not there")

(fn-switch-case 5 {6 "six" 7 "seven"} "not there")

Làm việc tốt

Điều gì có thể là kịch bản mà tôi sẽ cần một macro và một chức năng sẽ không hoạt động? Có một nhược điểm trong chức năng hoặc triển khai vĩ mô của tôi không?


2
Tôi không có ví dụ cụ thể cho bạn, nhưng câu trả lời đơn giản là macro cần thiết để tạo cú pháp mới (như trong Ngôn ngữ dành riêng cho tên miền). Nhưng, như ví dụ của bạn minh họa, các hàm Lisp hạng nhất thông thường đã đủ mạnh để thực hiện công việc mà không yêu cầu macro, trong nhiều trường hợp. Bạn nên ưu tiên các chức năng thông thường trên các macro; chỉ sử dụng macro khi một chức năng thông thường sẽ không làm.
Robert Harvey

Câu trả lời:


6

Một lợi ích của macro là chúng không tuân theo các quy tắc đánh giá giống như các hàm, vì chúng chỉ thực hiện các phép biến đổi trên mã.

Một ví dụ về cấu trúc bạn không thể tạo chỉ bằng cách sử dụng các hàm là macro luồng của Clojure .

Nó cho phép bạn chèn một biểu thức làm đối số đầu tiên trong một chuỗi các biểu mẫu:

(-> 5
  (+ 3)
  (* 4))

tương đương với

(* (+ 5 3) 4)

Bạn sẽ không thể tạo loại cấu trúc này chỉ bằng các hàm, vì trước khi ->biểu thức được ước tính, bên trong (+ 3)(* 4)sẽ có nghĩa là ->sẽ nhận được

(-> 5
    3
    4)

và nó cần phải xem các chức năng thực tế đang được sử dụng để làm việc.

Trong ví dụ của bạn, hãy xem xét nếu kết quả trong một số trường hợp là có tác dụng phụ hoặc gọi một số chức năng khác. Một phiên bản chức năng sẽ không thể ngăn mã đó được chạy trong tình huống mà kết quả đó không được chọn, trong khi macro có thể ngăn mã đó được đánh giá trừ khi đó là tùy chọn được thực hiện.


1
Tôi hiểu rồi. Vì tâm trí tôi, tôi không thể nghĩ xa hơn một chức năng. tôi luôn luôn tạo ra một cấu trúc trông giống như một hàm và có thể được thực hiện với một hàm. bởi vì tôi đã quen viết mã không tạo ngôn ngữ và cấu trúc hoặc DSls.
Amogh Talpallikar

2

Một khía cạnh thú vị của macro là chúng cung cấp cho bạn khả năng mở rộng cú pháp của lisp và thêm các tính năng cú pháp mới vào nó, và điều này chỉ xảy ra vì các đối số được truyền tới macro sẽ chỉ được đánh giá trong thời gian chạy chứ không phải tại thời điểm biên dịch macro của bạn. Như một ví dụ, các macro luồng đầu tiên / cuối cùng trong clojure -> ->>không đánh giá các đối số biểu thức của chúng, nếu không, các kết quả được đánh giá này không thể chấp nhận bất cứ điều gì nữa (nói (+ 3) => 33không phải là một hàm có thể chấp nhận đối số chính đầu tiên của bạn trong một cái gì đó như (-> 4 (+ 3))).

Bây giờ nếu tôi thích cú pháp này và tôi muốn thêm nó vào triển khai Lisp chung của tôi (không có nó) tôi có thể thêm nó bằng cách tự xác định một macro. Một cái gì đó như thế này:

;;; in SBCL

;;; first thread:
(defmacro -> (x &rest more)
  (loop for m in more
     for n = `(,(first m) ,x ,@(rest m)) then `(,(first m) ,n ,@(rest m))
     finally (return n)))

;;; last thread:
(defmacro ->> (x &rest more)
  (loop for m in more
     for n = `(,(first m) ,@(rest m) ,x) then `(,(first m) ,@(rest m) ,n)
     finally (return n)))

Bây giờ tôi sẽ có thể sử dụng chúng trong Common Lisp giống như trong Clojure:

(-> #'+
    (mapcar '(2 3 4) '(1 2 3))) ;; => (3 5 7)

(->> #'>
 (sort '(9 8 3 5 7 2 4))) ;; => (9 8 7 5 4 3 2)

Ngoài ra, có thể bạn muốn có một cú pháp mới cho rangechức năng của clojure với các từ khóa của riêng bạn cho cú pháp của bạn, đại loại như:

(from 0 to 10) ;=> (0 1 2 3 4 5 6 7 8 9)

(from 0 to 10 by 0.5) ;;=> (0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5)

có thể được định nghĩa như macro này:

(defmacro from
  "Just another range!"
  [x y z & more]
  `(when (= '~y '~'to)
     (if '~more (range ~x ~z (second '~more))
         (range ~x ~z))))

hoặc thêm một cái gì đó tương tự như Common Lisp (tất nhiên chỉ là vì tất cả những thứ này đã có thể được thực hiện bằng các ngôn ngữ của khóa học!):

(defmacro from (x y z &rest r)
  `(when (eql ',y 'to)
     (if (and ',r (eql (first ',r) 'by))
     (loop for i from ,x to ,z by (second ',r) collect i)
     (loop for i from ,x to ,z collect i))))

(from 0 to 10) ;=> (0 1 2 3 4 5 6 7 8 9 10)
(from 0 to 5 by 1/2) ;=> (0 1/2 1 3/2 2 5/2 3 7/2 4 9/2 5)

-2

Tôi không chắc chắn chính xác những gì bạn muốn chức năng chuyển đổi trường hợp của bạn để làm. Giá trị của:

(def x 5)
(switch-case x {5 :here}
             :not-there)

Nó có thể hữu ích nếu bạn đã xem nguồn của các hàm / macro lõi có liên quan bằng cách sử dụng thay thế

(clojure.repl/source case)

Nhưng nói chung, một lý do tại sao bạn có thể muốn một macro trái ngược với chức năng là việc sử dụng casemacro lõi

(case 6
  5 (print "hello")
  "not there")

sẽ không in xin chào. Tuy nhiên, nếu caseđược định nghĩa là một hàm, nó sẽ in "xin chào" lên màn hình và toàn bộ biểu thức sẽ đánh giá "not there". Điều này là do các hàm đánh giá các đối số của chúng trước mỗi lần gọi hàm.


3
Điều này không thực sự trả lời câu hỏi chính. Nó chỉ nitpicks trên ví dụ.
Florian Margaine

1
Phần cuối cùng trả lời câu hỏi. Nó nói một cái gì đó một macro có thể làm mà một chức năng không thể.
WuHoUnited

Bạn có thể đã rõ ràng hơn một chút về cách đánh giá đối số khác với macro.
Robert Harvey

Tôi đã học macro. Tôi muốn tạo ra một cấu trúc giả sử nó không có ở đó. Vì vậy, tôi nghĩ đến việc làm một trường hợp chuyển đổi bằng cách sử dụng if form. Tôi không thể nghĩ gì khác.
Amogh Talpallikar
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.