【问题标题】:Problem with passing a vector as a binding to the for macro将向量作为绑定传递给 for 宏的问题
【发布时间】:2015-02-09 23:07:18
【问题描述】:

我想使用 for 宏处理任意数量的列表。我想创建一个将向量作为绑定传递的函数,因为列表的数量会有所不同。

如果我对绑定进行硬编码,它会按预期工作:

=> (def list1 '("pink" "green"))
=> (def list2 '("dog" "cat"))
=> (for [A list1 B list2] (str A "-" B))
("pink-dog" "pink-cat" "green-dog" "green-cat")

当我尝试单独创建一个向量并将其用作绑定时,我遇到了问题。这里我手动创建绑定向量:

=> (def testvector (vec (list 'A list1 'B list2)))

这看起来不错:

=> testvector
[A ("pink" "green") B ("dog" "cat")]
=> (class testvector)
clojure.lang.PersistentVector

然而,

=> (for testvector (str A "-" B))
#<CompilerException java.lang.IllegalArgumentException: for requires a vector for its binding (NO_SOURCE_FILE:36)>

我不明白为什么 testvector 在用作 for 中的绑定时不被视为向量。抓住稻草,我将 testvector 放在方括号中,这使 for 宏保持快乐(它看到一个向量)但现在我有一个带有一个元素的向量(即向量中的一个向量),这不起作用,因为绑定需要是名称和集合的对。

=> (for [testvector] (str A "-" B))
#<CompilerException java.lang.IllegalArgumentException: for requires an even number of forms in binding vector (NO_SOURCE_FILE:37)>

任何关于如何将向量作为绑定动态传递给 for 的建议都将不胜感激。

【问题讨论】:

  • (vec (list ...)) 可以更简单地写成 (vector ...)。

标签: binding clojure list-comprehension


【解决方案1】:

关键是 for 是一个宏。在宏扩展时,testvector 是一个符号。它将在评估时评估为向量,但从 for 宏的角度来看,它不是向量。

user=> (defmacro tst [v] (vector? v))
#'user/tst
user=> (tst testvector)
false
user=> (vector? testvector)
true
user=> (defmacro tst2 [v] `(vector? ~v))
#'user/tst2
user=> (tst2 testvector)
true

如果您检查 for 宏的源代码(在 core.clj 中),您会发现 for 使用了未引用的 向量? 调用,就像上例中的 tst

【讨论】:

  • 对于我们这些仍在学习 Clojure 的人来说,真正让它发挥作用的下一步是什么?我已经尝试过 (defmacro combo [v] `(for ~v [A B])) 并且它不适用于关于 for 需要向量的相同错误消息
  • @JonathanBenn 是的,即使您将 for 包装在一个宏中,它本身仍然是一个宏,并且适用相同的限制。这个答案不是解决方案,只是解释为什么它不起作用。一个聪明的宏观时间解决方案目前正在逃避我,但我认为你可以用递归函数解决 OP 的测试问题。
  • 谢谢。我一直在努力寻找解决方案,但我的 Clojure-fu 还不够先进。我会试试你对递归函数的建议。
【解决方案2】:

您可以尝试强制评估绑定向量。与其尝试定义将包装for 宏的宏,不如将其包装在一个函数中,例如

(defn for-fn [bindings expr]
  (eval `(for ~bindings ~expr))) 

然后您实际上可以构建一个带有一些额外约束的绑定向量,因为绑定向量内的所有 s 表达式都需要有效并且包含一个动词作为第一个元素。

(let [bindings '[a (list 1 2) b (list 3 4) c (range 10 12)
                 :when (> (+ a b c) 15)]
      expr '(str a "-" b "-" c)]
  (for-fn bindings expr)) 

还有你的例子:

(def list1 '("pink" "green"))
(def list2 '("dog" "cat"))
(def testvector (vector 'A (cons 'list  list1) 'B (cons 'list list2)))

(for-fn testvector '(str A "-" B))
=> ("pink-dog" "pink-cat" "green-dog" "green-cat")

注意:由于for-fn 是函数,您需要引用表达式(str A "-" B) 以防止早期评估(在绑定A 和B 之前)。

【讨论】:

    【解决方案3】:

    这是最后的手段。请注意,无论您在哪里看到 read-string 都是 Here Be Dragons! 的代码(由于安全风险,以及代码行为缺乏编译时一致性保证)

    (def list1 '("pink" "green"))
    (def list2 '("dog" "cat"))
    (for [A list1 B list2] (str A "-" B))
    
    (def testvector (vec (list 'A list1 'B list2)))
    
    (def testvector-vec (vec (list 'A (vec list1) 'B (vec list2))))
    
    (def for-string (str "(for " testvector-vec "(str A \"-\" B))"))
    
    (eval (read-string for-string))
    > ("pink-dog" "pink-cat" "green-dog" "green-cat")
    

    【讨论】:

    • 不要使用字符串来操作 lisp 中的任何代码。这是一个口齿不清,所以你可以做(eval `(for ~testvector-vec (str ~'A "-" ~'B)))
    【解决方案4】:

    虽然不能解决您的问题,但应该注意的是,您正在做的事情可以更容易地用 map 而不是例如 for 来实现

    user=> (def list1 '("pink" "green"))
    #'user/list1
    user=> (def list2 '("dog" "cat"))
    #'user/list2
    user=> (map #(str %1 "-" %2) list1 list2)
    ("pink-dog" "green-cat")
    user=> 
    

    学习和试验时另一个有用的技巧是使用关键字而不是字符串。这可以减少输入,即无需将值放在引号中,有时可以帮助更轻松地识别错误。而不是 (def list1 '("pink" "green")) 你可以做 (def list1 '(:pink :green))。更好的是,尝试使用向量,而不是使用列表,然后您不必引用它(节省另一个击键)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-30
      • 1970-01-01
      • 2020-05-26
      • 2016-09-08
      • 2017-01-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多