我正在尝试在Clojure中编写我的第一个宏。我想模仿Ruby的%w {}运算符,它的工作原理如下:
irb(main):001:0> %w{one two three}
=> ["one", "two", "three"]
我想在Clojure中编写一个类似于返回单词向量的函数。以下是它的外观:
user=> (%w one two three)
=> ["one" "two" "three"]
我知道这是一个无法定义为普通函数的东西,因为符号会在应用之前被评估,我们会看到类似这样的东西:
user=> (%w one two three)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: one in this context, compiling:(NO_SOURCE_PATH:1:1)
这是我对宏的尝试:
(defmacro %w [& words]
(map str (vec words)))
但它不起作用。
user=> (%w one two three)
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn user/eval801 (NO_SOURCE_FILE:1)
为什么会这样?
答复
所以问题是宏实际上返回了正确的输出,但是然后repl试图评估它并且“one”不是一个有效的函数。
感谢下面的答案,这里有两个正确的宏来解决这个问题:
(defmacro %w-vec [& words]
"returns a vector of word strings"
(mapv str (vec words)))
(defmacro %w-list [& words]
"returns a list of word strings"
(cons 'list (map str words)))
答案 0 :(得分:3)
它不起作用,因为宏扩展clojure试图评估("一个""两个""三个"),诱导你的错误信息
user=> (%w one two three)
ClassCastException java.lang.String ("one") cannot be cast to clojure.lang.IFn (interface for callable stuff) user/eval801 (NO_SOURCE_FILE:1)
现在你可以这样做
(defmacro %w [& words]
(mapv str (vec words)))
生成矢量
或
(defmacro %w [& words]
(cons 'list (mapv str (vec words))))
生成(列表"一个""两个""三个")
或语法引用
(defmacro %w [& words]
`(list ~@(map str (vec words))))
答案 1 :(得分:2)
宏可以被认为是常规函数,它采用未评估的代码(作为数据结构)并返回新代码(作为数据),然后进行评估。
您的宏正在返回("one" "two" "three")
,它将作为调用函数 "one"
进行评估,其参数为"two" "three"
。
直接的解决方案是让您的宏返回(list "one" "two" "three")
或向量["one" "two" "three"]
。