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 在这两种情况下都会使用相同的读取。如果您想执行不带 eval 的读取,那么就使用不带 eval 的读取,例如通过引号进行。

'#example/symbolize "hello"

?

REPL 在此处看来是独立的。您可以看到它没有对 `edn/read-string` 的输出进行 eval,即使它是一个符号。我在发布之前还测试了 clj 文件源中的字面量,结果相同。
源文件也会被读取和评估,因此这是完全相关的。
一些额外的背景信息:[链接](https://clojure.org/guides/learn/syntax#_evaluation)
感谢您在周日回复。我的理解是,标签字面量是经过 eval 的,因此与标签元素相比,工作方式大不相同。我在文档中并未完全理解这一点。

现在我明白,标签字面量与标签元素之间有一个非常不同的接口。我可以向人们明确指出:“如果您使用我在 edn 中创建的这个标签并以我的库读取,它就像这样工作,但如果您尝试在您的源文件中使用这个字面量,则需要像这样引用它。”我只认为两个接口如此不同不是很好,但我想这不用说自明了。我确信还有我从未考虑过的用例。

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

我理解这是从安全性的角度出发,但你会得到与标记字面量不同的接口。我天真地期望,既然edn在讨论标记字面量的读取器引用中明确提及,这两个在“读取”这个意义上应该是一样的,特别是考虑到从评估标记字面量中获得的潜在好处如此微小,以至于人们往往会选择界面一致性。但这正是我回到说肯定有很多我没有考虑到的用例的原因。这里不是在抱怨,只是说明这对我来说为什么是一个障碍。
by
不,标记字面量和标记元素是同一回事。它们是一个读取时构造。它们是否被评估取决于上下文。repl中的代码和源文件中的代码将被读取和评估。读取和read-string会读取(但不评估)。引用是另一种读取但不评估的技术。这里没有区别——这都在于你怎么使用它。
by
也许我们在这个周末的末尾谈得有点不一致了。

我想说的是,如果你在一个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,感谢您的回复。

我认为我一直没有注意到的是,标签化字面量当然在读取时应用,因此它们当然会被求值 - 不能只是将它们作为数据。而标签化元素 - 在你正在读取的 edn 文件中 - 将在运行时通过显式读取调用而被消耗,除非你故意这样做,否则它们不会求值。

我认为看到你关于 clojure.core/read-string 的例子有助于清楚地理解这一点,所以谢谢您!

(我这里有一段更长且更混乱的评论,我将其替换为这个!)
这里仅有的评论是它们都是“标签化字面量”。无论是 EDN 读取器还是 Clojure 读取器,都会读取标签后面的格式(必须是有效的 EDN 或 Clojure 数据),然后对那个格式调用指定的函数:这是读取过程中的纯粹符号求值。

如果上下文表明读取后的格式将被求值,那么读取器产生的符号格式将被求值。

这不仅仅针对标签化字面量。读取器将文本转换为(EDN 或 Clojure)格式。生成的格式可能被评估(如果你在 REPL 中,或者正在加载命名空间源文件,或者特意从你的编辑器中评估一个格式)。

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