在clojure宏中使用count函数会产生异常

时间:2013-01-21 06:47:13

标签: macros clojure

我有这个函数创建一个符号向量:

(defn makevars [n] 
"Gives a list of 'n' unique symbols" 
(let [vc (repeatedly n #(gensym ))] vc)) 

这是有问题的宏:

(defmacro make-strings [syms]
;eg given 2 strings ["abc" "ab"] => ("aa" "ab" "ba" "bb" "ca" "cb")  
(let [nl (count syms) vs (makevars nl) forargs (vec (interleave vs syms))]  
`(for ~forargs (str ~@vs))))

当宏以(make-strings ["abc" "ab"])执行时,会发生所需的输出。但是,如果宏以(make-strings sy)运行,其中sy定义为["abc" "ab"],则会发生此错误:

UnsupportedOperationException count not supported on this type: Symbol  clojure.lang.RT.countFrom (RT.java:545)

我做错了什么,如何解决?作为宏观传说的新手,我希望我的理解有些不妥。

3 个答案:

答案 0 :(得分:4)

首先,我强烈建议您在此处删除宏,并使用普通函数实现您想要实现的逻辑:宏只在您想要创建一些方便的自定义语法时才有用,并且您只想计算两组字符的笛卡尔积。最简单的方法是使用contrib lib math.combinatorics

user=> (def sy ["abc" "ab"])
user=> (map #(apply str %) (apply cartesian-product sy))
("aa" "ab" "ba" "bb" "ca" "cb")

也就是说,您遇到的问题是宏在编译时执行,因此接收您作为参数传递的未评估表单。这意味着当您致电:

(make-strings sy)

而不是传递sy引用的向量,而是将sy符号赋给make-strings宏,以便调用

(count sy)
然后

将触发上述异常。由于宏的编译时间性质无法评估它们的输入,因此您无法从宏中获取符号后面的值:对这些符号的所有操作必须以宏本身的返回形式完成,这是然后在运行时执行。

当您希望能够传递文字或符号时,上述方法使您制定for绑定的策略不可行。

答案 1 :(得分:3)

我认为skuro可以很好地回答出错的原因,并提出一个非常有用的库,您可以用它来解决您的原始目标。 “那我为什么读这个呢?”我听你问。我只是觉得分享如何通过几行代码来概括将函数应用于笛卡尔积的产品会很有趣 - 而不会陷入可怕的宏观土地。

当我说概括时,我的意思是目标是能够做到这样的事情:

user> (permute str ["abc" "ab"])
=> ("aa" "ab" "ba" "bb" "ca" "cb")

好的,如果我们开发这种迄今为止不存在的置换函数,我们可以使用相同的函数来执行以下操作:

user> (permute + [[1 2 3] [10 20 30]])
=> (11 21 31 12 22 32 13 23 33)

这是一个玩具示例,但希望通过这种方式传达您可以获得的灵活性。

嗯,这是我提出的一种非常简洁的方式:

(defn permute [f [coll & remaining]]
  (if (nil? remaining)
    (map f coll)
    (mapcat #(permute (partial f %) remaining) coll)))

我开始使用的核心思想是使用mapmapcat进行迭代,以获得尽可能多的迭代,因为要组合不同的字符串。我从你的例子开始,写了一个“详细的”非一般解决方案:

user> (mapcat (fn [i] (map (partial str i) "ab")) "abc")
=> ("aa" "ab" "ba" "bb" "ca" "cb")

mapcat功能向下"abc"。具体来说,mapcat "abc"向下map str "abc" i "ab" user> (mapcat (fn [i] (mapcat (fn [j] (map (partial str i j) "def")) "ab")) "abc") => ("aad" "aae" "aaf" "abd" "abe" "abf" "bad" "bae" "baf" "bbd" "bbe" "bbf" "cad" "cae" "caf" "cbd" "cbe" "cbf") mapcat mapcat map str map 1}}),来自str的每个元素。

为了让我理解如何将它概括为一个函数,我必须更深入一级,并用第三个字符串尝试。

mapcat

str这是一个permute函数,permute(nil? remaining) true以组合方式将元素组合在一起的函数。唷。现在我开始看到如何概括。最里面的表达式总是map在要重新组合的字符串列表中的最后一个字符串中使用某种部分mapcat函数。外部表达式只是permute s,具有相继更多的前向字符串,直到最外面的字符串使用字符串列表中的第一个字符串来重新组合。

我想从这里我注意到我不需要一次定义整个部分f函数,而是在我递归调用permute时我可以“构建它”。

希望我现在已经给出足够的上下文来解释该函数的工作原理。 mapcat的最后一次迭代发生在没有剩余的colls时(即loop返回recur)。它只是loop无论在最后一次碰撞中给出的任何函数。

如果有剩余的colls,则recur是当前coll下的{{1}}变体。这种置换变体使用{{1}}的部分函数和匿名参数,并且{{1}}使用其余的colls。通过这样做,它将逐步构建一个部分函数,​​一旦到达coll列表的末尾,它将最终被调用。然后,在我的脑海中,我想象它向后追踪,调用嵌套的{{1}},直到它最终解开结果重新组合的colls。

我想这个功能虽然简洁,但可能不是最佳的。坦率地说,我没有太多的CS背景,但是从我读到的关于Clojure的内容来看,使用{{1}} / {{1}}代替自我递归调用往往更有效率”。我想如果优化对您很重要,重新使用{{1}} / {{1}}函数是非常简单的。

答案 2 :(得分:1)

未评估宏参数。当您将["abc" "ab"]作为参数提供给宏时,它会将其作为字符串向量,因为字符串是自我评估对象。但是当您发送sy宏时,只会获得一个符号'sy。这就是发生错误的原因。