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

'#example/symbolize "hello"

?

REPL在这里似乎是正交的。你可以看到它甚至对于`edn/read-string`的输出也没有进行eval,尽管它是一个符号。我在发布之前也在一个clj文件的源中测试了这个字面量,结果是一样的。
源文件也会被读取和计算,因此这也完全相关。
这里还有一些额外的背景信息:https://clojure.org/guides/learn/syntax#_evaluation
谢谢你星期日回复。我的结论是,标记字面量和标记元素具有相当不同的接口。我可以专门告诉人们,“如果你使用我在edn中创建的此标记并使用我的库进行读取,它的工作方式像这样,但如果你尝试在源中使用该字面量,你需要像这样进行引用”。我只是认为这两个接口差异很大,但这可能不需要我说了。我确信还有我没有考虑到的用例。

现在我明白了,标记字面量与标记元素确实有相当不同的界面。我可以说“如果你使用这个我在edn中创建的标记,并用我的库进行读取,它的工作方式是这样的,但要使用该字面量作为源,你需要像这样进行引用”。我只是认为这两个接口的差异不太好,但我想这可能是显而易见的。我相信还有我没有考虑到的用例。

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

我明白这是从安全角度考虑的,但你最终得到的是一个与标签字面量不同的接口。自从 edn 在讨论标签字面量的读取器参考资料中明确提及,我天真地预期这两个在“读取”这个意义上是相同的,尤其是考虑到从评估标签字面量中获得的潜在好处如此之小,以至于人们往往会选择接口一致性。但这正是我回到我确实确信有很多我没有考虑的使用例子的地方。这里不是在抱怨,只是解释这对我来说为什么是一个障碍。
by
不,标签字面量和标记元素是同一件事。它们是读取时构造的。它们是否评估取决于上下文。在 repl 和源文件中的代码将被读取和评估。read 和 read-string 将会读取(但不评估)。引用是另一种读取但不会评估的技术。这里没有区别——一切都取决于你如何使用它。
by
也许我们在周末的末尾在谈论不同的事情。

我的意思是,如果你在 edn 文件中使用特定标签 X 链接到函数 Y,并将其读取并传递给 `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 中,或者正在加载命名空间的一个源文件,或者从你的编辑器中特别求值一个形式)。

在许多方面,这与宏的工作方式相似:阅读器将文本转换为表单,符号形式传递给宏(函数),返回新的符号形式,如果上下文需要,则评估该新形式。
...