2024 Clojure 状态调查!中分享您的看法。

欢迎!请查阅关于页面以获取更多关于该工作方式的信息。

+1投票
Clojure

Reader 的参考文档中提到“标记文字 Literals 是 Clojure 的 edn 标记化元素的实现。”

但至少在一点上两者是不同的:由标记文字 Literals 产生的符号将会被解析,而由标记元素产生的符号以及通过 clojure.edn/read-string 读取的符号则不会。

这意味着如果您在包含给定标记的 EDN 中读取,与使用相同的标记在源中产生 Literals 的输出可能不同。

示例

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>

为什么会有这种差异?有没有任何场景下从标签文字 Literals 解析符号是有益的?如果是这样的话,为什么不从标记元素和 edn/read-string 中做同样的操作呢?

2 个答案

+1投票

被选中
 
最佳答案

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

因此,没有分歧。REPL在这两种情况下都使用了相同的读取。如果您想要无评估的读取,那么就无评估地读取,例如使用引号。

'#example/symbolize "hello"

?

REPL在这里似乎是正交的。您可以看到,尽管它是一个符号,但它并没有评估`edn/read-string`的输出。我在发帖前也在clj文件源中测试了这个字面量,结果是相同的。
源文件也会被读取和评估,因此这完全相关。
以下是一些额外的背景信息:https://clojure.org/guides/learn/syntax#_evaluation
感谢您在周日回复。我的理解是,标记字面量与标记元素的接口相当不同。我可以尽力告诉人们:“如果你使用我在edn中创建的这个标记并使用我的库读取它,它会像这样工作,但如果你试图在你的源代码中使用这个字面量,你需要像这样引用它。”我只是觉得这两个接口的差异有点不好,但我猜这不用说了。我相信一定有一些我从未考虑过的用例。

现在我明白了,标记字面量与标记元素相比,有着相当不同的接口。我可以不遗余力地向人们指出:“如果你使用我在edn中创建的这个标记并使用我的库读取它,它会像这样工作,但如果你试图在你的源代码中使用这个字面量,你需要像这样引用它。”我只是觉得这两个接口的差异有点不好,但我猜这不用说了。我相信一定有一些我从未考虑过的用例。

再次感谢。
换一个说法——尽管有“读取”这个词,但在使用 `edn/read-string` 时,始终都会得到一个数据结构(或对象)。它是读入的,但仅仅是为了数据——真正的读取器会读取符号进行即时自动求值(对吧?)

我理解这是从安全角度出发,但你得到的接口与标签化字面量不同。我从阅读器的标签化字面量参考中了解到的 edn 是明确引用的,所以我认为两者应该以同样的方式“读取”。尤其是考虑到求值标签化字面量的潜在好处非常小,所以人们往往会选择接口一致性。但这就是我回到即使我没有考虑到的许多用例的原因。这里并没有抱怨,只是解释为什么这对我来说是一个障碍。
不,标签化字面量和标签化元素是同一回事。它是一个读取时构造。是否评估它取决于上下文。REPL 中的代码和在源文件中的代码会被读取和评估。read 和 read-string 会读取(但不评估)。引号是另一种读取但不评估的技术。这里没有差异——关键是你的用法。
也许我们在周末的最后阶段谈过了。

我的意思是,如果你在一个 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读取器参考中有记录)标记字面量在读取时应用,因此当然会被求值 - 无法将它们作为数据保留。而标记元素 - 在你读取的EDN文件上下文中 - 将会在运行时通过显式的读取调用消耗,因此除非你特意这么做,否则不会求值。

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

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

如果在读取后的上下文中该形式将被求值,则读取器产生的符号形式将被求值。

这不是标记字面量的特例。读取器将文本转换为(EDN或Clojure)形式。得到的形式可以被求值(如果你在REPL中或在加载一个命名空间的源文件,或者从你的编辑器中特别求值一个形式)。

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