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 文件中的文字,结果是一样的。
源文件也被读取和评估,因此这与内容完全相关。
感谢你在周日回复。我的收获是,标记字面量是评估的,因此与标记元素有很大的不同。不管是什么原因,从文档的撰写中我没有完全理解这一点。

现在我明白了标记字面量与标记元素拥有一个非常不同的接口。我可以不辞劳苦地告诉人们,“如果你使用我在edn中创建的此标签并使用我的库读取它,它的行为就像这样,但如果你试图在源代码中使用这个字面量,你需要像这样引用它”。我只是认为这两个接口差异很大并不太理想,但我想这可能是显而易见的。我确信有我从未考虑过的用例。

再次感谢。
by
换句话说,尽管有“读取”这个词,使用`edn/read-string`无论如何最后都得到一个数据结构(或对象)。它是读进来的,但只是为了数据——一个真正的读取器读取将产生符号以便即时自动评估(对吗?)并且运行eval per `clojure.core/read`,而不是仅仅给你数据?

我从安全角度理解这个问题,但你得到的接口与标记文字不同。我天真地期望,鉴于edn明确在讨论标记文字的读取器引用中被引用,这两个在“读取”的意义上是相同的。特别是,从评估标记文字中获得的潜在好处如此之小,以至于人们往往会选择接口的一致性。但这就是我再次说肯定有很多我未曾考虑的用例的原因。在这里不是抱怨,我只是解释为什么这对我是个阻碍。
by
不,标记文字和标记元素是一回事。它们是读取时构建起来的。它们是否被评估取决于上下文。REPL和源文件中的代码都会被读取和评估。read和read-string会读取(但不会评估)。引号是另一种读取但不评估的技术。这里没有区别——一切都取决于你怎么用它。
by
也许在周末的最后,我们在说不同的话。

我要说的是,如果你在edn文件中使用一个与给定的标记X相关联的函数Y,并将它读入并传给`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参考中有所记录,标记字面量是在读取时应用的,所以当然它们会被求值 - 不可能只将它们作为数据。而标记元素(在读取edn文件的过程中)将通过显式读取调用来在运行时消费,除非你特意这样做,否则不会求值。

我看出你的clojure.core/read-string示例,这有助于澄清这一点,感谢你!

(我这里有一个更长的、更混乱的评论,我现在用这个替换!)
我在这里要说的唯一点是,它们都是“标记字面量”。阅读器(无论是EDN读取器还是Clojure读取器)都会读取标签后面的形式(因此它必须是有效的EDN或Clojure数据),然后对该形式调用指定的函数:纯粹是作为读取过程中的符号求值。

如果上下文是读取后将被求值的形式,那么由读取器产生的符号形式将被求值。

这并不仅限于标记字面量。阅读器将文本转换为(EDN或Clojure)形式。这些形式可能被求值(如果你在REPL中,或者加载命名空间源文件,或者从编辑器中特别求值一个形式)。

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