简介:Clojure是EDN的超集。默认情况下,当给定Clojure数据结构时,
pr
、
prn
和
pr-str
会产生有效的EDN。
*print-dup*
更改了这一点,并使它们使用Clojure的全部功能,以在往返后对内存中的对象的“相同性”提供更强的保证。ClojureScript只能读取EDN,而不能读取完整的Clojure。
简单的解决方案:不要将*print-dup*
设置为true,并且只从Clojure传递纯数据到ClojureScript。
更复杂的解决方案:使用标记文字,其中包含(可能共享的)关联读取器。 (尽管如此,这仍不涉及*print-dup*
。)
与此有关的次要问题:大多数EDN的用例都可以通过Transit来处理,它更快,特别是在ClojureScript方面。
让我们从Clojure部分开始。Clojure从一开始就有一个clojure.core/read-string
函数,它以旧的Lispy意义上的读取-求值-打印循环(Read-Eval-Print-Loop)方式read
字符串,即它提供了实际在编译Clojure时使用的读取器的访问权限。[0]
后来,Rich Hickey和他的团队决定推广Clojure的数据表示法,并发布了EDN规范。EDN是Clojure的一个子集,仅限于Clojure语言的数据元素。
作为 Lisp 方言的 Clojure,和所有的 Lisp 一样,它宣扬 "代码即数据,数据即代码" 的哲学。因此,上面这段话的实际含义可能并不完全清晰。我不确定是否有任何详细的差异说明,但是对
Clojure Reader description 和之前提到的 EDN 规范进行仔细研究,可以发现一些差异。最明显的差异在于宏字符,特别是调度符号
#
,在 Clojure 中比在 EDN 中有更多的目标。例如,
#(* % %)
表示法是有效的 Clojure,Clojure 读器将把它转换成以下 EDN 的等价形式:
(fn [x] (* x x))
。对于这个问题尤其重要的是,还有一个鲜有文档记录的特殊读取器宏
#=
,它可以用来在读取器内部执行任意代码。
由于完整的语言可供 Clojure 读取器使用,因此可以将代码嵌入到读取器正在读取的字符字符串中,并使其立即在读取器中评估。您可以在
这里 找到一些示例。
clojure.edn/read-string
函数严格限制于EDN格式,而非整个Clojure语言。具体来说,它的运行不受
*read-eval*
变量的影响,并且无法读取所有可能的有效Clojure代码片段。
事实证明,Clojure阅读器基本上是出于历史原因而用Java编写的。由于它是一款重要的软件,工作良好,并且已经在几年的活跃Clojure使用中进行了大量调试和测试,Rich Hickey决定在ClojureScript编译器中重复使用它(这是ClojureScript编译器在JVM上运行的主要原因)。 ClojureScript编译过程大部分发生在JVM上,Clojure阅读器可用,因此ClojureScript代码由
clojure.core/read-string
(或其近亲
clojure.core/read
)函数解析。
但是您的Web应用程序无法访问正在运行的JVM。对于ClojureScript应用程序来说,需要Java小程序并不是一个非常有前途的想法,特别是因为ClojureScript的主要目标是将Clojure语言的范围扩展到JVM(和CLR)之外。因此,决定ClojureScript将无法访问其自己的读取器,并且因此也将无法访问其自己的编译器(即在ClojureScript中没有eval、read或read-string)。这个决定及其影响在
这里进行了更详细的讨论,由一个实际知道事情经过的人进行(我不在那里,因此在这个解释的历史观点上可能存在一些不准确之处)。
因此,ClojureScript没有
clojure.core/read-string
的等效物(有些人会认为它因此不是真正的lisp)。尽管如此,在Clojure服务器和ClojureScript客户端之间通信Clojure数据结构仍然是很好的,这也是EDN计划的推动因素之一。就像在EDN规范发布后Clojure获得了受限制(更加安全)的读取函数(
clojure.edn/read-string
)一样,ClojureScript也在标准发行版中获得了一个EDN读取器,即
cljs.reader/read-string
。可以说,这两个函数(或者说它们的命名空间)之间的一致性需要更好一些。
在我们最终回答您的原始问题之前,我们需要了解更多关于*print-dup*
的背景。请记住*print-dup*
是Clojure 1.0的一部分,这意味着它先于EDN、tagged literals和records。我认为EDN和tagged literals为大多数*print-dup*
的用例提供了更好的替代方案。由于Clojure通常是建立在几个数据抽象(列表、向量、集合、映射和通常的标量)之上的,打印/读取循环的默认行为是保留数据的抽象形状(映射是映射),但不一定是其具体类型。例如,Clojure有多个映射抽象的实现,例如PersistentArrayMap用于小型映射,而PersistentHashMap用于较大的映射。语言的默认行为假定您不关心具体类型。
对于罕见情况或更专业的类型(在定义时使用deftype或defstruct),您可能希望更多地控制它们的读取,这就是print-dup的作用。
关键是,如果将*print-dup*设置为true,则pr和相关函数将不会生成有效的EDN,而实际上是包括一些明确的# =(eval build-my-special-type)形式的Clojure数据,这些形式并不是有效的EDN。
[0]: 在"Lisp"中,编译器是明确以数据结构为基础定义的,而不是以字符串为基础。虽然这似乎与通常的编译器有些不同(它们确实在处理过程中将字符流转换为数据结构),但Lisp的定义特点是读取器发出的数据结构是语言中常用的数据结构。换句话说,编译器基本上只是语言中随时可用的一个函数。这已经不像过去那么独特了,因为大多数动态语言都支持某种形式的
eval
; Lisp独特之处在于
eval
接受一个数据结构,而不是一个字符串,这使得动态代码生成和评估变得更加容易。编译器作为“仅仅是另一个函数”的一个重要含义是,编译器实际上已经运行,并且整个语言已经被定义和可用,并且到目前为止阅读的所有代码也都可用,这打开了Lisp宏系统的大门。
cljs.reader/read[-read]
符合 EDN 标准。但是,是否有关于这一事实的文档?我正在查看代码,但并没有明确说明这是实际情况。 - Neoasimovread
接受一个PushbackReader
,而read-string
将你的字符串转换为一个PushbackReader
,然后在其上调用read
。 - Michael Victor Zink