2024 Clojure 状态调查中分享您的想法!

欢迎!请参阅关于页面以获取更多关于此工作原理的信息。

+1
Clojure

读取者的参考文档指出,“标记字面量是 Clojure 对 edn 标记元素的实现。”

但在至少一个方面,两者是不同的:由标记字面量生成的符号将会被解析,但由标记元素生成并由 clojure.edn/read-string 读取的符号则不会。

这意味着如果读取包含给定标签的 EDN 与使用相同标签在源代码中生成字面量相比,您可能会得到不同的输出。

示例

data_readers.cj

{example/symbolize clojure.core/symbol}

然后

user> (edn/read-string {:readers {(quote example/symbolize) clojure.core/symbol}}
                       "#example/symbolize\"hello\"")   
hello
user> #example/symbolize "hello"
Syntax error compiling at (*cider-repl clojure/foragr:localhost:46533(clj)*:0:0).
Unable to resolve symbol: hello in this context
user>

是否有什么特殊的原因导致这种差异?是否存在一个从标记字面量解析符号是好的场景?如果是这样,为什么不从标记元素和 edn/read-string 中执行相同操作?

2 答案

+1

选中
 
最佳答案

第一个只是读取。
第二个是读取后评估(因为 R E PL)

所以,没有差异。REPL 在这两种情况下都使用相同的读取方式。如果您想要不带评估的读取,那么就不带评估地读取,例如通过引用

'#example/symbolize "hello"

?

在这里,repl(交互式解释器)看起来是垂直的。您可以看到,即使它是一个符号,它也没有评估`edn/read-string`的输出。在我发布之前,我还测试了clj文件中的字面量,结果是一样的。
源文件也会被读取和评估,因此这一点完全相关。
感谢在周日回复。我的收获是,标记字面量会被评估,因此它与标记元素有相当大的不同。无论出于什么原因,我都没有从文档中获得这方面的信息。

现在我明白了,标签字面量与标签元素之间有一个截然不同的接口。我可以努力告诉人们,“如果你使用我在edn中创建的这个标签并使用我的库读取它,它的作用像这样,但是如果你试图在源中使用这个字面量,你需要像这样引用它”。我只是认为这两个接口之间的差异很不好,但我想这可能是不言而喻的。我相信还有我从未考虑过的用例。

再次感谢。
换句话说,尽管有“读取”这个词,使用`edn/read-string`总是得到一个数据结构(或对象),仅此而已。这只是读取数据,而不是真正的读取器读取,它会生成符号以供立即自动求值(是吗?),而不是像`clojure.core/read`那样仅提供数据?

我从安全角度理解这一点,但你最终得到的接口与带标签的文本不同。由于edn明确被引用在讨论带标签的文本的读取器参考中,我天真地期望两个将“读取”在相同的意义上,尤其是由于评估带标签的文本的潜在收益似乎很小,因此人们会倾向于选择接口一致性。但这就是我回头来说我肯定有许多我没有考虑到的用例的原因。在这里不是抱怨,只是解释为什么这对我来说是一个路障。
不,带标签的文本和带标签的元素是同一回事。它们是读取时构造的。它们是否被评估取决于上下文。REPL中的代码和源文件中的代码会被读取和评估。read和read-string会读取(但不会eval)。引用是另一种读取但不评估的技术。在这里没有区别——这些都取决于你怎么使用它。
也许周末接近尾声时我们谈得有些不同。

我想要说的是,如果您在edn文件中使用一个特定的与函数Y关联的标签X,并将其读取并传递给`edn/read-string`,您将得到未评估的符号。如果您在带标签的文本中使用相同的X/Y组合,您将得到已评估的符号。基本上就是我上述REPL会话显示的内容。

从我坐的位置来看,这是两个不同的接口,但我可能在这里使用了不同的术语。“你得到的内容取决于上下文”——我同意你的看法。我意识到如果我使用`clojure.core/read-string`,我就会得到另一种行为。我对这种差异不抱怨,只是在指出这一点。

顺便说一下,我不期望你会“同意”我上面所说的,我已经占用了你足够的时间,请享受周末剩余的时间,再次感谢。
+1

Alex 是正确的,我认为你误解了他的回答。看看这个 REPL 会话是否能说服你

(~/clojure)-(!2003)-> cat src/data_readers.clj
{example/symbolize clojure.core/symbol}

Sun Aug 27 17:02:45
(~/clojure)-(!2004)-> clj
Clojure 1.12.0-alpha4
user=> (require '[clojure.edn :as edn])
nil
user=> (edn/read-string {:readers {(quote example/symbolize) clojure.core/symbol}}
                       "#example/symbolize\"hello\"")
hello ; edn/read-string produces a symbol
user=> (read-string "#example/symbolize\"hello\"")
hello ; core/read-string also produces a symbol
user=> hello ; this symbol is not bound to anything
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: hello in this context
user=> #example/symbolize "hello"
;; Read (produces symbol hello) Eval (tries to lookup the symbol's value and fails)
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: hello in this context
user=> #example/symbolize "\"hello\""
Syntax error compiling at (REPL:0:0).
;; Note the subtle difference in the error: Read produced a SYMBOL again, spelled "hello"
;; and there's no bound "hello" symbol either
Unable to resolve symbol: "hello" in this context
user=>

编辑了
嗨,Sean,非常感谢这一点。

我认为我错过的一个区别是(当然,我应该先知道这些,因为它们在 clojure reader reference 中有记录)带标签的字面量在读取时适用,因此当然会被求值——它们不能只是作为数据存在。而标签化的元素——在读取 edn 文件的上下文中——将通过明确的读取调用在运行时消费,因此除非你特意这样做,否则它们不会被执行。

我认为看到你提供的 clojure.core/read-string 示例,这有助于澄清这一点,谢谢!

(这里有一个更长的,更困惑的评论,我现在用这个来代替!)
这里唯一要说的就是,它们都是“带标签的字面量”。读取器——无论是 EDN 读取器还是 Clojure 读取器——都将读取标签之后的格式(因此它必须是有效的 EDN 或 Clojure 数据),然后对那个格式调用指定的函数:纯粹作为读取过程的符号求值。

如果上下文是这样的,一个格式在阅读后将被求值,那么读取器产生的符号格式将被求值。

这不仅仅与带标签的字面量相关。读取器将文本转换为(EDN 或 Clojure)格式。产生的格式可能被求值(如果你处于 REPL 中或加载命名空间中的源文件,或者在编辑器中特别求值一个格式)。

从很多方面来看,这与宏的工作方式相似:读取器将文本转换为格式,符号格式将被传递给宏(函数),然后返回一个新的符号格式,如果上下文需要,该新格式将被求值。
...