2

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

(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)

我做错了什么,如何解决?作为宏观知识的新手,我希望我的理解有些不对劲。

4

3 回答 3

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当您希望能够传递文字或符号时,上述内容使您制作绑定的策略不可行。

于 2013-01-21T07:45:25.880 回答
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")

这个mapcatsa函数下来了"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函数。呸。现在我开始了解如何进行概括。最里面的表达式总是在要重新组合的字符串列表中的最后一个字符串中使用某种偏函数。外部表达式只是s 与连续更靠前的字符串,直到最外面的地方使用字符串列表中的第一个字符串重新组合。mapstrmapstrmapcat

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

希望我现在已经给出了足够的上下文来解释这个函数是如何工作的。的最后一次迭代permute发生在没有剩余 colls 时(即(nil? remaining)返回true)。它只是map在最后一个 coll 中给出的任何功能。

当还有剩余的 coll 时,它是当前 coll 的变体mapcat。这个置换变体使用带有匿名参数permute的部分函数,​​并且正在关闭剩余的 colls。通过这样做,它将逐步构建一个部分函数,​​一旦它到达 colls 列表的末尾,最终将被调用。然后,在我的脑海中,我想象它向后追溯,调用 nested s 直到它最终与结果重新组合的 coll 解开。fpermutemapcat

我想这个函数虽然简洁,但可能不是最佳的。坦率地说,我没有太多的 CS 背景,但从我读到的关于 Clojure 的内容来看,使用loop/recur而不是自递归调用往往更“有效”。我想如果优化对您很重要,那么loop重新设计要使用的功能将是相当简单的。recur

于 2013-01-21T08:55:59.093 回答
1

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

于 2013-01-21T08:27:15.970 回答