我正在通过Clojure为Brave和True工作。在关于宏的章节中,有一个练习:
编写一个宏,使用一次宏调用定义任意数量的属性检索函数。以下是您的称呼方式:
(defattrs c-int :intelligence
c-str :strength
c-dex :dexterity)
这些功能的作用是从地图中检索值。例如:(def character {:name "Travis", :intelligence 20, :strength 23, :dexterity 13})
(c-int character)
的结果当然是20
,这样的功能很容易定义为(def c-int #(:intelligence %))
这是我提出的解决问题的方法:
(defmacro defattrs
[& attributes]
`(let [attribute-pairs# (partition 2 (quote ~attributes))]
(map (fn [[function-name# attribute-key#]]
(def function-name# #(attribute-key# %)))
attribute-pairs#)))
我遇到的问题是def
使用生成的符号名称而不是它解析的内容来定义函数(考虑到def
的用法,事后才有意义)。我尝试使用带有定义函数的表达式,例如:
(let [x ['c-int :intelligence]]
(def (first x) #((second x) %)))
导致此错误:CompilerException java.lang.RuntimeException: First argument to def must be a Symbol, compiling:(/tmp/form-init5664727540242288850.clj:2:1)
关于如何实现这一目标的任何想法?
答案 0 :(得分:2)
您已找到反引号和波浪号的用例。试试这个:
(let [x ['c-int :intelligence]]
(eval `(def ~(first x) #(~(second x) %))))
(def character {:name "Travis", :intelligence 20, :strength 23, :dexterity 13})
(c-int character) => 20
反引号类似于单引号,因为它使下一个表单成为列表,符号等的数据结构。不同之处在于数据结构旨在用作模板,其中内部可以使用代字号替换位。很酷的部分是代字号并不仅仅替换项目,而是适用于可以是任意Clojure表达式的实时代码。
答案 1 :(得分:1)
您使用attributes
参数进行的普通操作不需要以表格形式生成:
def
表单的函数。将以上内容应用到您的代码中,我们得到......
(defmacro defattrs [& attributes]
(let [attribute-pairs (partition 2 attributes)]
(map (fn [[function-name attribute-key]]
`(def ~function-name #(~attribute-key %)))
attribute-pairs)))
def
。function-name
和attribute-key
参数的值已插入def
表单。还有一个问题。
map
的结果是一系列def
表格。解决方案是cons
一个do
到序列的前面:
(defmacro defattrs [& attributes]
(let [attribute-pairs (partition 2 attributes)]
(cons 'do
(map (fn [[function-name attribute-key]]
`(def ~function-name ~attribute-key))
attribute-pairs))))
我还将#(~attribute-key %)
缩写为后引用表单中的等效~attribute-key
。
让我们看看扩展的样子:
(macroexpand-1 '(defattrs dooby :brrr))
;(do (def dooby :brrr))
看起来不错。试试吧!
(defattrs gosh :brrr)
(gosh {:brrr 777})
;777
有效。