我一直在尝试使用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
因此,如果我想在x
内使用my-data
的值,则my-data
函数需要对其执行某些操作,即检查x
是否为符号,若然,请使用(eval x)
。这看起来很难看。有更好的方法吗?
答案 0 :(得分:3)
在您的示例中无法获取本地x
的值,主要是因为locals仅在运行时获取指定的值,而标记的文本在读取时处理。 (两者之间也有编译时间;在编译时不可能得到本地的值;因此宏也不能得到本地的值。) 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
无法访问本地(它总是在全局范围内运行),但本地人没有在运行时分配值的问题更为基础