2012-03-19 33 views
9

Đi này (giản thể) ví dụ:Làm thế nào để tránh macro ẩn trong Clojure?

(defmacro make [v & body] 
    `(let [~'nv ~(some-calc v)] 
    ~(map #(if (= % :value) 'nv %) body))) 

Ngay bây giờ biểu tượng nv là cứng. Có cách nào để bằng cách nào đó gensym nv và vẫn có thể sử dụng nó trong chức năng bản đồ?

Nhân tiện, đây thực sự là một macro ẩn dụ?

+2

Không chắc chắn nếu bạn có thể gọi nó là ẩn dụ: để được như vậy, 'nv' nên tham khảo một số phần của biểu mẫu yêu cầu macro. Nếu bạn tính ra 'some-calc' từ cơ thể macro thì bạn sẽ có một lời gọi ẩn dụ thích hợp hơn:' (make (some-calc x) (do-something-to nv)) 'trong đó' nv' chỉ đến ' (một số-calc x) '. Trong trường hợp của bạn, bạn sẽ có '(make x (do-some-thing-to nv))', nhưng sau đó 'nv' sẽ không trực tiếp tham chiếu đến bất cứ thứ gì trong biểu mẫu yêu cầu macro. – skuro

+0

@skuro: Tôi hiểu, vì vậy về cơ bản đây là điều tồi tệ nhất của cả hai thế giới - giới thiệu một biểu tượng mới, tuy nhiên sẽ vẫn ẩn cho đến khi ai đó vô tình định nghĩa lại nó. Hf theo dõi lỗi đó :) May mắn thay, một 'gensym' rõ ràng theo đề xuất của amalloy giải quyết vấn đề này. –

Trả lời

4

Câu trả lời được chứa trong câu hỏi: chỉ cần sử dụng gensym như bạn sẽ làm nếu Clojure không có tự động-gensyms.

(defmacro make [v & body] 
    (let [value-sym (gensym)] 
    `(let [~value-sym ~(some-calc v)] 
     [email protected](replace {:value value-sym} body)))) 

Lưu ý rằng tôi không chắc chắn cho dù bạn thực sự muốn ~ hoặc [email protected] ở đây - nó phụ thuộc nếu body được coi là một chuỗi các biểu thức để thực hiện trong let, hoặc một chuỗi các đối số cho hàm độc thân gọi điện. Nhưng [email protected] sẽ trực quan hơn/bình thường hơn, vì vậy đó là những gì tôi sẽ đoán.

Cho dù macro này là ẩn dụ là một chút nghi ngờ: chắc chắn giới thiệu nv vào phạm vi gọi là, nhưng về cơ bản không chủ ý nên tôi sẽ nói không. Trong phiên bản sửa đổi của tôi, chúng tôi không còn giới thiệu nv hoặc bất kỳ thứ gì giống như nó, nhưng chúng tôi "kỳ diệu" thay thế :value bằng v. Tuy nhiên, chúng tôi chỉ làm điều đó ở cấp cao nhất của cơ thể, vì vậy nó không giống như giới thiệu một phạm vi thực sự - tôi muốn nói nó giống như làm cho mã của khách hàng bất ngờ phá vỡ trong các trường hợp góc.

Ví dụ về cách hành vi lừa đảo này có thể xuất hiện, hãy tưởng tượng rằng một trong các thành phần của body(inc :value). Nó sẽ không được thay thế bằng macro và sẽ mở rộng thành (inc :value), không bao giờ thành công. Vì vậy, thay vào đó, tôi khuyên bạn nên sử dụng macro ẩn dụ thực tế, giới thiệu một phạm vi thực sự cho một biểu tượng.Một cái gì đó như

(defmacro make [v & body] 
    `(let [~'the-value ~(some-calc v)] 
    [email protected])) 

Và sau đó người gọi có thể chỉ cần sử dụng the-value trong mã của họ, và nó hoạt động giống như một thực tế, thường xuyên ở địa phương ràng buộc: macro của bạn giới thiệu nó bằng ma thuật, nhưng nó không có bất kỳ thủ thuật đặc biệt khác .

2

Trong ví dụ của bạn, cả hai some-calcmap xảy ra trong khi mở rộng macro, không phải lúc chạy, do đó, nv không cần phải là let. Bản thân macro không được viết đúng cách, không phân biệt bất cứ điều gì cần làm với việc nắm bắt biểu tượng.

+0

Như tôi đã nói, đây là một ví dụ đơn giản; vĩ mô thực sự trả về một 'fn' mà sẽ được gọi là tương đối thường xuyên, do đó các" bộ nhớ đệm "trong let. –

+0

Tôi không thực sự mua cái này, Alex - nó khá rõ ràng rằng bản đồ 'của anh ta 'được dự định xảy ra trong quá trình mở rộng macro, và hoàn toàn chính đáng rằng' một số calc cũng có thể là tốt. Ví dụ, có lẽ '(some-calc 'x)' trả về '' (inc (doto x prn))' - hoàn toàn phù hợp để sử dụng trong ngữ cảnh đó, và cần phải là 'let' để tránh lặp lại tác dụng phụ của việc in ấn. – amalloy

+0

Chắc chắn gọi 'map' và' some-calc' trong khi mở rộng có thể chính xác. Quan điểm của tôi là 'let' của 'nv' không thực sự làm bất cứ điều gì, do đó bản thân macro không có ý nghĩa gì đối với câu hỏi về' nv'. –

3

Nó không thực sự là một macro ẩn dụ khi tôi hiểu nó.

Một tương đương anaphoric sẽ phải cung cấp cho bạn một cú pháp như sau:

(make foo 1 2 3 4 it 6 7 it 8 9) 

ví dụ: biểu tượng it đã được xác định để nó có thể được sử dụng bên trong cơ thể của make vĩ mô.

Tôi không chắc chắn chính xác nếu điều này là những gì bạn muốn bởi vì tôi không có đủ bối cảnh về cách vĩ mô này sẽ được sử dụng, nhưng bạn có thể thực hiện ở trên như:

(defmacro make [v & body] 
    `(let [~'it ~v] 
     (list [email protected]))) 

(make (* 10 10) 1 2 3 4 it 6 7 it 8 9) 
=> (1 2 3 4 100 6 7 100 8 9) 

Ngoài , nếu bạn không thực sự cố gắng để tạo ra cú pháp mới và chỉ muốn thay thế :value trong một số bộ sưu tập, sau đó bạn không thực sự cần một macro: nó sẽ tốt hơn nếu chỉ sử dụng replace:

(replace {:value (* 10 10)} [1 2 :value 3 4]) 
=> [1 2 100 3 4] 
+0

Mặc dù tôi chấp nhận câu trả lời của Amalloy, điều này rất hữu ích, cảm ơn! –

2

Một cách tiếp cận là sử dụng liên kết động.

(declare ^:dynamic *nv*) 

(defmacro make [v & body] 
    `(binding [*nv* ~(some-calc v)] 
    ~(map #(if (= % :value) *nv* %) body))) 

Trong thực tế, các biến động hút khi phạm vi của họ là quá rộng (đó là khó khăn hơn để kiểm tra và các chương trình gỡ lỗi vv), nhưng trong trường hợp như thế này, nơi phạm vi được giới hạn trong bối cảnh cuộc gọi nội vùng nơi một anaphor là cần thiết, chúng có thể khá tiện dụng.

Một khía cạnh thú vị của việc sử dụng này là nó là loại nghịch đảo của một thành ngữ phổ biến của việc sử dụng một macro để ẩn một ràng buộc động (nhiều trong phong cách with- *). Trong thành ngữ này (mà theo như tôi biết không phải là tất cả những gì phổ biến) ràng buộc được sử dụng để lộ một cái gì đó ẩn bởi vĩ mô.

+1

Yea, gần đây tôi đã bắt đầu (ab) sử dụng các liên kết động và tôi vẫn đang khám phá các trường hợp mà chúng có thể hữu ích. Cảm ơn ý tưởng! –

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