Macros 如何编写模仿Ruby的Clojure宏';s%w运算符

Macros 如何编写模仿Ruby的Clojure宏';s%w运算符,macros,clojure,Macros,Clojure,我正试图用Clojure编写我的第一个宏。我想模拟Ruby的%w{}运算符,其工作原理如下: irb(main):001:0> %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:(N

我正试图用Clojure编写我的第一个宏。我想模拟Ruby的%w{}运算符,其工作原理如下:

irb(main):001:0> %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)
我想在Clojure中编写一个类似的函数,返回一个单词向量。下面是它的外观:

user=> (%w one two three)
=> ["one" "two" "three"]
我知道这不能定义为普通函数,因为符号在应用之前会被计算,我们会看到这样的结果:

irb(main):001:0> %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)))

它不起作用,因为在宏展开之后,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))))

宏可以看作是常规函数,它接受未计算的代码(作为数据结构)并返回新代码(作为数据),然后对新代码进行计算

您的宏正在返回
(“一”“二”“三”)
,它将作为带有参数
“二”“三”
的函数调用进行计算


简单的解决方案是让您的宏返回
(列出“一”“二”“三”)
或向量
[“一”“二”“三”]

为什么没有人评论问题和所有答案中对
向量的无用调用<代码>(mapf(vec x))
只是
(mapfx)
,而
(cons'list(mapv str(vec words)))
更糟糕:它应该是
(cons'list(map str words))
。还请注意,在这里使用反引号而不是常规引号对
列表
非常重要,否则,如果在
列表
未引用
clojure.core/list
的范围内,宏将失败,这可能是因为
:在命名空间中引用clojure
,或者是因为本地名为
的列表