2017-07-04 77 views
2

當定義一個默認參數時,我有一對Clojure宏的問題。當一個宏調用另一個宏時Clojure宏和默認參數

在下列情況下使用2個宏,其中MM02調用MM01:

(defmacro mm01 
    [ & [ { :keys [ f1 ] :or { f1 long } :as opts } ]] 
    `(let [] 
    (println "(2) ~f1" ~f1))) 

(defmacro mm02 
    [ & [ { :keys [ f1 ] :as opts } ]] 
    `(let [] 
    (println "(1) ~f1" ~f1) 
    (mm01 [email protected]))) 

的評價:

(mm02 { :f1 byte }) 

打印出:

(1) ~f1 #function[clojure.core/byte] 
(2) ~f1 #function[clojure.core/long] 

不過,我想有預計:

(1) ~f1 #function[clojure.core/byte] 
(2) ~f1 #function[clojure.core/byte] 

我做錯了什麼或者我錯過了什麼嗎?

順便說一下的評價:

(mm01 { :f1 byte }) 

打印出:

(2) ~f1 #function[clojure.core/byte] 

非常感謝你。

回答

5

[email protected]在語法引用上下文中將一系列事物展開成幾個單獨的事物。你的opts綁定是一張地圖,它在概念上是一系列的map-entries。您可以通過在repl中使用宏將生成的表達式來查看這個動作:與通過宏觀試驗和錯誤調試相比,這通常是查看宏的中間步驟的有用方法整個。

user=> (let [opts {:f1 'long}] 
    #_=> `(foo [email protected])) 
(user/foo [:f1 long]) 

查看方括號:f1 long?這就是問題所在:你的其他宏需要用地圖來調用,而不是向量。結果,解構無法找到你正在尋找的關鍵。要解決這個問題,只需刪除@並使用普通的不引號,而不是拼接引號。

user=> (let [opts {:f1 'long}] 
    #_=> `(foo ~opts)) 
(user/foo {:f1 long}) 

作爲一個附加的改進,就應該更換,只需[{...}]的分心[& [{...}]]參數節。它們的行爲相同,只是前者允許調用者傳遞零參數(用nils填充)或任何數量的額外參數,這些參數都被忽略。如果他們的意味着省略參數並獲得默認值,那麼您的版本對於調用者來說非常方便,但是如果他們遺漏了一個參數或者意外地提供了太多的參數,那麼它們不可避免地會導致調試的麻煩。

+0

非常明確的解釋和有用的意見,謝謝!我發現可選參數可能會導致一些模糊的錯誤。我認爲Stuart Sierra在[https://stuartsierra.com/2015/06/01/clojure-donts-optional-arguments-with-varargs]中評論了與此相關的一些方面, –