0

我一直在尝试使用 Clojure 标记的文字,并注意到读者不会评估参数,非常像宏。这是有道理的,但是这样做的适当解决方案是什么?显式评估?

例子:给定这个函数

(defn my-data
  ([[arg]]
     (prn (symbol? arg))
     :ok))

而这个定义data_readers.clj

{myns/my-data my.ns/my-data}

以下行为不同:

> (let [x 1] (my.ns/my-data [x]))
false
:ok

所以x传入的值在被传入之前被评估my-data。另一方面:

> (let [x 1] #myns/my-data [x])
true
:ok

所以如果我想使用xinside的值my-datamy-data函数需要对其做一些事情,即检查它x是一个符号,如果是,使用(eval x). 这看起来很难看。有更好的方法吗?

4

1 回答 1

3

概括

在您的示例中,无法获取本地的值x,主要是因为本地仅在运行时获得分配的值,而标记文字是在读取时处理的。(两者之间还有编译时间;在编译时无法获取本地值;因此宏也无法获取本地值。)1

更好的方法是在运行时使用常规函数,因为毕竟您想根据某些参数的运行时值构造一个值。标记文字确实是文字,应该这样使用。

扩展讨论

为了说明上述问题:

(binding [*data-readers* {'bar (fn [_] (java.util.Date.))}]
  (eval (read-string "(defn foo [] #bar x)")))

foo将始终返回相同的值,因为阅读器只有一次机会返回一个值,#bar x然后将其烘焙到foo的字节码中。

还要注意eval,我们可以存储调用返回的数据结构,read-string并在将来的任意点编译它,而不是直接传递给它;返回的值foo将保持不变。显然,这样的文字值不可能依赖于任何本地人的未来值。事实上,在阅读器的操作过程中,甚至不清楚哪些符号将命名为局部变量——这是由编译器确定的,在涉及宏的情况下,该确定的结果可能并不明显。

当然,读者可以自由地返回一个看起来像函数调用的表单、本地名称等。举个例子:

(binding [*data-readers* {'bar (fn [sym] (list sym 1 2 3 4 5))}]
  (eval (read-string "#bar *")))
;= 120
;; substituting + for * in the string yields a value of 15

这里#bar f变得等价于(f 1 2 3 4 5)。不用说,这是对符号的滥用,并没有真正做到你所要求的。


1值得指出的是,eval它无法访问局部变量(它始终在全局范围内运行),但是局部变量在运行时之前没有赋值的问题更为根本。

于 2013-11-09T22:33:36.233 回答