【问题标题】:Passing seq-exprs to for using quoting将 seq-exprs 传递给使用引用
【发布时间】:2017-08-25 13:55:21
【问题描述】:

我正在尝试使用引用将seq-exprs 传递给for,但我在下面的两次尝试都导致CompilerException java.lang.IllegalArgumentException: for requires a vector for its binding。我显然误解了引用和评估(以及相关概念)的工作原理。

(let [v ['b (range 2)]]
   (for v b))

(let [v '[b (range 2)]]
   (for v b))

正确的方法是什么?您将如何向初学者解释为什么上述实现都不起作用?谢谢!

编辑:
我知道我能做到

(let []
   (for [b (range 2)] b))

我想要实现的是能够传递绑定向量(例如,另一个函数的输出)

编辑 2,动机/背景:
我正在构建一个转译器 [repo],我正在尝试翻译和展开一个 for 循环(包括嵌套循环,example),所以我认为我可以将要迭代的变量范围附加到seq-exprs同时通过嵌套循环更深入地遍历 AST(抽象语法树),然后评估每个原始块中包含的翻译关系(关系总是可以一个一个地翻译)给定由 clojure for 生成的“绑定”。如果有“更好”的方法,您有什么建议吗?

【问题讨论】:

  • 最后你想要这样的东西:(for [i (0 1)])?你想达到什么目的?你可以像这样使用 for:(for [i (range 2)] (println "i is: " i)),也 for requires vector binding like this ->[]
  • 这是一个最小的例子 - 我希望能够将向量 seq-exprs 作为变量传递给 for
  • 因为 for 是一个宏,它在编译时扩展,这意味着绑定向量应该是一个文字向量,而不是一个 var,你想要做的事情是完全不可能的(编译器只是不知道v 的运行时值)。您可以做的是使用另一个返回for 宏的宏(它仍然无法对v 的运行时值进行操作)或者您可以使用eval,这很糟糕。你为什么还要这样做?
  • for 产生一个序列。如果您总是要将参数向量传递给for,为什么不将for 产生的序列传递给您希望使用它的任何东西?

标签: clojure


【解决方案1】:

这正是宏和函数之间的区别。函数在运行时获取和返回数据。然而,宏在编译时获取并返回代码

考虑以下几点:

(defn foo [arg]
  (str arg))

(let [x (dec 10)]
  (foo x)) ;=> "9"

foo 将整数 9 视为参数,因为 x 的当前 runtime 值为 9。但是,foo 无法知道值 9 由符号x,因为符号x是代码。

(defmacro bar [arg]
  (str arg))

(let [x 9]
  (bar x)) ;=> "x"

(str 'x) => "x"

所以如果我们把它改成宏,我们会得到x的编译时间值,也就是x这个符号。

(我们将其更改为字符串,然后将其作为代码返回,字符串"x"作为代码的计算结果为"x"。)

这里重要的一点是foo 无法知道它传递了一个名为x 的东西,它只知道运行时值为 9,bar 只有 知道它传递了一个名为 x 的东西,它无法知道 x 的运行时值是什么。

这就是宏具有传染性的原因。

所以在您的示例中,vruntime 值是 '[b (range 2)],但您传递给 forcompile-time 值只是符号v 本身。

要解决您眼前的问题,您可以这样做:

(let [v '[b (range 2)]]
  (eval `(for ~v ~'b)))

但这只是……太可怕了。您可能想要的是编写一个宏。

我不确定这个宏会是什么样子,因为我必须了解您要解决的实际问题,但这里有一个宏示例,它采用 seq-exprs 并将它们传递给 @987654343 @。

;; Minimal example of a macro that takes seq-exprs as code
;; and returns code with a for that uses them.
(defmacro forv [seq-exprs body]
  `(vec (for ~seq-exprs ~body)))

【讨论】:

  • 我不知道“可能”。问这个问题的人通常实际上想要一个笛卡尔积之类的东西,最好将其作为一个获取列表列表并返回元组列表的函数。
  • 有道理。
  • @madstap 我试图弄清楚如何使用您提供的宏来解决问题,但我做不到,您能给我举个例子吗?我已经通过以下方式尝试了 (let [v '[b (range 2)] body 'b] (macroexpand '(forv v body))) 并得到了 (clojure.core/vec (clojure.core/for v body)) 。正如您所解释的,宏不会评估参数的运行时值,所以我不知道如何通过编写宏来解决它。
  • @tales 我写的宏是一个宏的最小示例,它接受 seq-exprs 并返回带有使用它们的for 的代码,如果不清楚,抱歉。我不能给你宏的解决方案,因为我不知道你想要什么。您的最小示例是 way too minimal 。 1/2
  • '[b (range 2)] 的值只在运行时存在,到那时for 已经接受了它的参数并基于这些参数返回了代码。从字面上做你说你想做的事,有我上面描述为“可怕”的eval舞蹈,但如果你退后一步问自己“为什么”几次,几乎可以肯定有一个更简单、更容易的解决方案. 2/2
【解决方案2】:

['b (range 2)] => ['b (0 1)] 在此示例中,您有一个名为 b 的符号,并且您正在调用函数 (range 2),其 eval 为 (0 1)

'[b (range 2)] => [b (range)] 在此示例中,您引用了所有表达式,因此您最终会得到符号 brange symbol within a list data structure

关于你的 for 循环,你得到这个异常是因为你没有正确使用它(缺少向量绑定)。

这就是你应该如何编写一个 for 循环:

(for [i (range 5)] ;;you need to define vector in here
     (do 
         (println "I: " i) 
         i));;let's return i here

结果:

I: 0
I: 1
I: 2
I: 3
I: 4
=> (0 1 2 3 4)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-13
    • 1970-01-01
    • 2015-07-18
    • 1970-01-01
    • 2015-05-03
    • 2014-08-21
    • 1970-01-01
    • 2022-08-22
    相关资源
    最近更新 更多