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 投票

selected
 
最佳答案

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

所以,没有差异。REPL 在这两种情况下都使用相同的读取。如果您想进行无评估的读取,则可以举例说明:通过引号读取

'#example/symbolize "hello"

?

这里的repl看起来是正交的。你可以看到它没有评估出`edn/read-string`的输出,尽管它是一个符号。在我发布之前,我也测试了clj文件的源代码中的字面量,结果是一样的。
源文件也会被读取和评估,所以这一点是完全相关的。
感谢您在周日回复。我的感想是,标记字面量会被评估,因此和标记元素在本质上有很大不同。无论什么原因,我没有从文档中完全理解这一点。

现在我明白了,标记字面量和标记元素是完全不同的接口。我可以特别告诉人们“如果你使用我在edn中创建的标记并用我的库读取它,它工作起来就像这样,但如果你在源代码中尝试使用这个字面量,你需要像这样用引号引用它”。我只认为这两个接口的不同如此之大是不太理想的,但我想这大概是显而易见的。我相信还有我从未考虑过的用例。

再次感谢。
换句话说——尽管有“读取”这个词,但使用`edn/read-string`总会得到一个数据结构(或对象),就是这样。它被读取进来了,但只是为了数据——真正的读取器读取会生成立即自动评估的符号(对吗?)并运行每个`clojure.core/read`的eval,而不仅仅是提供数据?

我从安全的角度来看,这我理解,但你最终得到的接口与标记字面量不同。我天真地期待,由于edn在讨论标记字面量的读取器参考中被明确提到,这两个应该“读取”在相同的意义上,尤其是由于对字面量使用eval的潜在优点似乎很小,人们往往会倾向于选择接口一致性。但这就是我回头来说我肯定有很多我还没有考虑到的用例。这里不是在抱怨,只是说明这对我来说是个障碍。
不是的,标记字面和标记元素是同一件事。它们是读取时的构造。它们是否被评估取决于上下文。repl和源代码中的代码将被读取和评估。读取和read-string将读取(但不会eval)。引用是另一种读取但不eval的技术。这里没有区别——一切取决于你怎么使用。
也许周末结束时我们一直在谈论不同的事情。

我想说的是,如果你在一个edn文件中使用了与函数Y相关联的标记X,将其读取进来并传给`edn/read-string`,你会得到未经评估的符号。如果你在标记字面中使用完全相同的X/Y组合,你会得到经过评估的符号。基本上,这就像我上面的repl会话所显示的那样。

就我的观点而言,这是两个不同的接口,但我可能我们的术语不同。“你得到什么取决于上下文”——我同意你的观点。我意识到如果我用`clojure.core/read-string`,我会得到其他行为。我并不是在抱怨这种差异,只是在指出它。

我不指望你会上面对我说的话表示“同意”,无论如何,我已经占用了你不少时间,希望你能够享受周末的剩余时光,再次表示感谢。
+1 投票

亚历克斯是对的,我认为你误解了他的回答。看看这个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中,或在加载某个命名空间源文件,或在你的编辑器中特定评估一个形式)。

从许多方面来看,这与宏的工作方式类似:读者将文本转换为表单,符号形式被传递给宏(函数),然后返回一个新的符号形式,如果上下文需要,将评估该新形式。
...