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

?

这里的重排似乎与这个问题无关。你可以看到,即使它是一个符号,它也没有评估 `edn/read-string` 的输出。我也在发布前测试了 clj 文件源中的字面量,结果相同。
源文件也被读取和评估,因此这是完全相关的。
这里有一些额外的背景信息:https://clojure.org/guides/learn/syntax#_evaluation
感谢你在周日回复。我的理解是,标记字面量会被评估,因此与标记元素的工作方式有本质的不同。不知为何,我在文档中并没有看到这一点。

现在我明白了,标记字面量和标记元素之间有很大的差异。我必须尽力告诉人们,“如果你使用我在 edn 中创建的此标签并使用我的库读取它,它会像这样工作,但如果你尝试在源中使用该字面量,你需要像这样引用它”。我只是觉得这两个接口差异太大,但我想这可能是不言而喻的。我相信还有我从未考虑过的用例。

再次感谢。
by
换成另一种说法——尽管有“读取”这个词,但使用`edn/read-string`时,最终总是会得到一个数据结构(或对象),仅此而已。它是读取进来的,但只是为了数据——一个真正的读取器会生成符号进行立即自动评估(对吧?)并针对每个` clojure.core/read`执行评估,而不仅仅是给出数据?

我理解这是出于安全考虑,但最终得到的接口与标识符相同。由于edn被明确引用在关于标识符的读取器参考中,我幼稚地期望这两者会在相同的意义上“读取”,尤其是考虑到从评估标识符中获得的好处似乎如此微小,以至于人们可能会倾向于选择接口一致性。但是,这正是我回到这里说的一定还有很多我不曾考虑过的用例。这里不是在抱怨,只是解释这对我来说为什么是一个障碍。
by
不,标识符和标识符元素是同一回事。它们是一个读取时间结构。它们是否会被评估取决于上下文。在repl和源代码中的代码会被读取和评估。读取和read-string会读取(但不评估)。引用是另一种读取但不会评估的技术。这里没有区别——一切取决于如何使用。
by
也许在这周末的最后阶段,我们是在自说自话。

我说的意思是,如果你在edn文件中使用标签X链接到函数Y,并将它读入并传递给`edn/read-string`,你会得到未经评估的符号。如果你在这个特定的X/Y组合中 用标记字面量,你会得到经过评估的符号。基本上就是我上面repl会话所示的内容。

从我这里看,这是两种不同的接口,但也许我们的术语有所不同。“你得到什么取决于上下文”——我在这里和你意见一致。我意识到,如果我使用` clojure.core/read-string`,我会得到另外一种行为。我不是在抱怨这种差异,只是在指出它。

我不期待您会对上面我说的话“同意”与否,不管怎样,我已经花费了您足够的时间,请享受周末的剩余时间,再次感谢。
+1
by

亚历克斯是对的,我想您可能误解了他的答案。看看这个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=>
by
编辑 by
嗨Sean,谢谢你的回复。

我认为我之前没有注意到的区别是(当然,我应该知道这一点,因为它们已经在clojure读取器参考中记录了)标记文字在读取时应用,所以当然会被求值——不能只把它们当作数据。而标记元素——在读取edn文件的情况下——将通过显式的读取调用在运行时消费,因此除非您特意这样做,否则不会求值。

我认为看到您的clojure.core/read-string例子有助于明确这一点,所以谢谢您!

(这里有一段更长、更困惑的评论,我现在要替换它!)
by
我在这里要说的唯一一点是,这些都是“标记文字”。读取器——不管是EDN读取器还是Clojure读取器——都会读取标签后的形式(因此它必须是有效的EDN或Clojure数据),然后对该形式调用指定的函数:纯粹是作为读取过程一部分的符号评估。

如果在读取后要将形式求值的情况下,则读取器产生的符号形式将被求值。

这不是标记文字独有的。读取器把文本转换为(EDN或Clojure)形式。这些形式可能与(您在REPL中或在为命名空间加载源文件时或在您的编辑器中特意求值一个形式时)求值。

从许多方面来说,这与宏的工作方式类似:读取器把文本转换成形式,符号形式被传递给宏(函数),它返回一个新的符号形式,如果上下文需要,这个新形式会被求值。
...