请在 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

被选中
 
最佳答案

第一个只是读取。
第二个被读取后进行评估(因为 RE PL)

所以,没有差异。REPL在这两种情况下使用相同的读取方式。如果您想进行无评估的读取,那么请使用无评估的读取方式,例如通过引号

'#example/symbolize "hello"

?

这里的重试(repl)似乎没有垂直相关性。你可以看到,即便它是符号,它也没有评估 `edn/read-string` 的输出。我也在我发布之前测试了 clj 文件中的字面量,结果是一样的。
源文件也会被读取和评估,因此这是完全相关的。
在这里有一些额外的背景信息:[https://clojure.org/guides/learn/syntax#_evaluation](https://clojure.org/guides/learn/syntax#_evaluation)
感谢您在周日回复。我的理解是,标记字面量和标记元素的工作方式完全不同。无论如何,我没有从文档中了解到这一点。

现在我明白,标记字面量和标记元素提供了完全不同的接口。我可以设法告诉人们:“如果你使用我在 edn 中制造的标签并用我的库读取,它的工作方式就像这样;但如果你试图在你的源代码中使用这个字面量,你需要像这样引用它。”我只是认为这两个接口如此不同是不太理想的,但我想这恐怕是显而易见的。我相信还有我从未考虑过的用例。

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

我明白这是从安全的角度来看,但你得到的是一个与标记文字不同的接口。由于edn明确引用了讨论标记文字的读取器参考,我天真地期待两者会在“读取”这一意义上是一致的,尤其是考虑到评估标记文字的潜在优势如此之小,以至于人们往往倾向于选择接口一致性。但我确实回到了我说我肯定有许多我没有考虑到的用例。这并不是在抱怨,只是在解释为什么这对我来说是一个减速带。
不,标记文字文字和标记元素是同一件事。它们是读取时的结构。它们是否被评估取决于上下文。repl和源文件中的代码都会被读取和评估。read和read-string都会读取(但不评估)。引用是另一种读取但不评估的技术。这里没有区别——一切都取决于你如何使用。
好吧,也许我们在这个周末结束时开始谈论不同的话题了。

我想要说的是,如果你在一个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中或加载命名空间源文件,或者从您的编辑器中显式求值一个形式)。

在很多方面,这类似于宏的工作方式:读取器将文本转换成形式,符号形式被传递给宏(函数),它返回一个新的符号形式,并且在需要的情况下对该新形式进行求值。
...