如何编写模仿Ruby的%w运算符的Clojure宏

时间:2014-12-24 12:30:47

标签: macros clojure

我正在尝试在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)))

2 个答案:

答案 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"]