[英]Clojure macros: quoting, unquoting and evaluation
我有以下代碼:
(ns macroo)
(def primitives #{::byte ::short ::int})
(defn primitive? [type]
(contains? primitives type))
(def pp clojure.pprint/pprint)
(defn foo [buffer data schema]
(println schema))
(defmacro write-fn [buffer schema schemas]
(let [data (gensym)]
`(fn [~data]
~(cond
(primitive? schema) `(foo ~buffer ~data ~schema)
(vector? schema) (if (= ::some (first schema))
`(do (foo ~buffer (count ~data) ::short)
(map #((write-fn ~buffer ~(second schema) ~schemas) %)
~data))
`(do ~@(for [[i s] (map-indexed vector schema)]
((write-fn buffer s schemas) `(get ~data ~i)))))
:else [schema `(primitive? ~schema) (primitive? schema)])))) ; for debugging
(pp (clojure.walk/macroexpand-all '(write-fn 0 [::int ::int] 0)))
問題是,在評估最后一個表達式后,我得到了
=>
(fn*
([G__6506]
(do
[:macroo/int :macroo/int true false]
[:macroo/int :macroo/int true false])))
我會在必要時解釋代碼,但是現在我只是陳述問題(這可能只是我正在制作的新手錯誤):
`(primitive? ~schema)
和
(primitive? schema)
在:else分支中分別返回true和false,因為我在cond表達式中使用第二個版本,它在不應該的地方失敗(我更喜歡第二個版本,因為它將在編譯時評估,如果我“沒錯。”
我懷疑它可能與名稱空間限定的符號有關?
經過一些調查(見編輯),這是一個有效的Clojure替代品。 基本上,您很少需要遞歸宏。 如果你需要遞歸地構建表單,委托輔助函數並從宏調用它們(同樣, write-fn
不是一個好名字)。
(defmacro write-fn [buffer schemas fun]
;; we will evaluate "buffer" and "fun" only once
;; and we need gensym for intermediate variables.
(let [fsym (gensym)
bsym (gensym)]
;; define two mutually recursive function
;; to parse and build a map consisting of two keys
;;
;; - args is the argument list of the generated function
;; - body is a list of generated forms
;;
(letfn [(transformer [schema]
(cond
(primitive? schema)
(let [g (gensym)]
{:args g
:body `(~fsym ~schema ~bsym ~g)})
(sequential? schema)
(if (and(= (count schema) 2)
(= (first schema) ::some)
(primitive? (second schema)))
(let [g (gensym)]
{:args ['& g]
:body
`(doseq [i# ~g]
(~fsym ~(second schema) ~bsym i#))})
(reduce reducer {:args [] :body []} schema))
:else (throw (Exception. "Bad input"))))
(reducer [{:keys [args body]} schema]
(let [{arg :args code :body} (transformer schema)]
{:args (conj args arg)
:body (conj body code)}))]
(let [{:keys [args body]} (transformer schemas)]
`(let [~fsym ~fun
~bsym ~buffer]
(fn [~args] ~@body))))))
宏采用緩沖區 (無論它是什么),由您的語言定義的模式以及為生成的函數訪問的每個值調用的函數。
(pp (macroexpand
'(write-fn 0
[::int [::some ::short] [::int ::short ::int]]
(fn [& more] (apply println more)))))
...產生以下內容:
(let*
[G__1178 (fn [& more] (apply println more)) G__1179 0]
(clojure.core/fn
[[G__1180 [& G__1181] [G__1182 G__1183 G__1184]]]
(G__1178 :macroo/int G__1179 G__1180)
(clojure.core/doseq
[i__1110__auto__ G__1181]
(G__1178 :macroo/short G__1179 i__1110__auto__))
[(G__1178 :macroo/int G__1179 G__1182)
(G__1178 :macroo/short G__1179 G__1183)
(G__1178 :macroo/int G__1179 G__1184)]))
[::some x]
,接受零個或多個值作為向量,並為每個值調用函數fun 。 這需要通過循環來完成,因為只有在調用函數時才知道大小。 如果我們將向量[32 [1 3 4 5 6 7] [2 55 1]]
傳遞給上述宏展開生成的函數,則打印如下:
:macroo/int 0 32
:macroo/short 0 1
:macroo/short 0 3
:macroo/short 0 4
:macroo/short 0 5
:macroo/short 0 6
:macroo/short 0 7
:macroo/int 0 2
:macroo/short 0 55
:macroo/int 0 1
在這一行:
`(do ~@(for [[i s] (map-indexed vector schema)]
((write-fn buffer s schemas) `(get ~data ~i)))))
你在當前范圍內調用write-fn
宏 ,其中s
只是一個符號,而不是schema
一個條目。 相反,您希望發出將在調用者范圍內運行的代碼:
`(do ~@(for [[i s] (map-indexed vector schema)]
`((write-fn ~buffer ~s ~schemas) (get ~data ~i)))))
並對if
的另一個分支進行類似的更改。
順便說一下,乍一看它看起來像這不是一個宏,但可能是一個更高階的函數:接受一個模式或其他什么,並返回一個數據函數。 我的猜測是你把它作為表演的宏觀,在這種情況下,我會建議你先用緩慢,簡單的方法來試試。 一旦你有了工作,你可以在必要時使它成為一個宏。 或者,也許我錯了,這里有一些基本上必須是宏觀的東西。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.