我有一堆函数可以映射到外部系统定义的某些代码:
(defn translate-from-ib-size-tick-field-code [val]
(condp = val
0 :bid-size
3 :ask-size
5 :last-size
8 :volume))
(defn translate-to-ib-size-tick-field-code [val]
(condp = val
:bid-size 0
:ask-size 3
:last-size 5
:volume 8))
我想创建一个宏来删除重复:
#_ (translation-table size-tick-field-code
{:bid-size 0
:ask-size 3
:last-size 5
:volume 8})
我这样启动了宏:
(defmacro translation-table [name & vals]
`(defn ~(symbol (str "translate-to-ib-" name)) [val#]
(get ~@vals val#)))
结果函数体似乎正确,但函数名称错误:
re-actor.conversions> (macroexpand `(translation-table monkey {:a 1 :b 2}))
(def translate-to-ib-re-actor.conversions/monkey
(.withMeta (clojure.core/fn translate-to-ib-re-actor.conversions/monkey
([val__10589__auto__]
(clojure.core/get {:a 1, :b 2} val__10589__auto__))) (.meta ...
我希望“translate-to-ib-”显示为函数名称的一部分,而不是命名空间的前缀,结果显示出来。
如何使用clojure宏执行此操作?如果我只是做错了并且由于某种原因不应该使用宏,请告诉我,但我也想知道如何创建这样的函数名称来提高我对clojure和宏的理解。谢谢!
答案 0 :(得分:7)
宏观问题有两个:
1)在引用传递给macroexpand
的表单时,您正在使用反引号,该命名空间限定了符号:
`(translation-table monkey {:a 1 :b 2})
=> (foo.bar/translation-table foo.bar/monkey {:a 1, :b 2})
其中foo.bar
是您所在的名称空间。
2)您正在使用符号defn
构建name
项的名称,当它符合名称空间时,将字符串化为“foo.bar/monkey”。这是一个可行的版本:
(defmacro translation-table [tname & vals]
`(defn ~(symbol (str "translate-to-ib-" (name tname))) [val#]
(get ~@vals val#)))
请注意,我们使用tname
函数获取了没有命名空间部分的name
名称。
至于宏是否是正确的解决方案,可能不是:-)对于这样的简单案例,我可能只使用地图:
(def translate-from-ib-size-tick-field-code
{0 :bid-size
3 :ask-size
5 :last-size
8 :volume})
;; swap keys & vals
(def translate-to-ib-size-tick-field-code
(zipmap (vals translate-from-ib-size-tick-field-code)
(keys translate-from-ib-size-tick-field-code)))
(translate-from-ib-size-tick-field-code 0)
=> :bid-size
(translate-to-ib-size-tick-field-code :bid-size)
=> 0
如果速度至关重要,请查看case
。
答案 1 :(得分:3)
关于不同观点的一些未经请求的建议:(get ~@vals val#)
非常可疑。你的宏声称要接受任意数量的论点,但是如果它超过两个,它就会做一些没有任何意义的事情。例如,
(translation-table metric {:feet :meters}
{:miles :kilometers}
{:pounds :kilograms})
除了是一个糟糕的翻译表之外,还要扩展到始终抛出异常的代码:
(defn translate-to-ib-metric [val]
(get {:feet :meters}
{:miles :kilometers}
{:pounds :kilograms}
val)))
当然, get
并没有采用那么多论点,而且这并不是你的意思。最简单的解决方法是只允许两个参数:
(defmacro translation-table [name vals]
(... (get ~vals val#)))
但请注意,这意味着每次调用函数时都会重建值映射 - 如果计算成本高或有副作用,则会出现问题。所以,如果我把它写成一个宏(虽然看到贾斯汀的答案 - 你为什么会这样?),我会这样做:
(defmacro translation-table [name vals]
`(let [vals# ~vals]
(defn ~(symbol (str "translate-to-ib-" name)) [val#]
(get vals# val#))))