在宏中创建名称空间限定的函数名称

时间:2011-10-04 15:39:55

标签: clojure

我有一堆函数可以映射到外部系统定义的某些代码:

(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和宏的理解。谢谢!

2 个答案:

答案 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#))))