[英]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.