Clojure& ClojureScript:clojure.core / read-string,clojure.edn / read-string和cljs.reader / read-string

时间:2014-07-09 18:53:36

标签: clojure clojurescript edn

我不清楚所有这些读字符串函数之间的关系。好吧,很明显clojure.core/read-string可以读取pr[n]甚至print-dup输出的任何序列化字符串。很明显,clojure.edn/read-string会读取根据EDN specification格式化的字符串。

但是,我从Clojure脚本开始,并不清楚cljs.reader/read-string是否符合。这个问题是由于我有一个Web服务正在以这种方式序列化发出的clojure代码而引发的:

(with-out-str (binding [*print-dup* true] (prn tags)))

那就是生成包含数据类型的对象序列化。但是,cljs.reader/read-string无法读取此内容。我总是得到这种类型的错误:

Could not find tag parser for = in ("inst" "uuid" "queue" "js")  Format should have been EDN (default)

起初,我认为cljs-ajax抛出了这个错误,但是在测试了rhino REPL中的cljs.reader/read-string之后,我得到了同样的错误,这意味着它被cljs.reader/read-string抛出本身。它由maybe-read-tagged-type中的cljs.reader函数抛出,但不清楚这是因为读者只能使用EDN数据,还是......?

此外,从Differences from Clojure文件中,唯一可以说的是:

The read and read-string functions are located in the cljs.reader namespace

这表明他们应该完全具有相同的行为。

3 个答案:

答案 0 :(得分:47)

总结:Clojure是EDN的超集。默认情况下,prprnpr-str在给定Clojure数据结构时会生成有效的EDN。 *print-dup*改变了这一点,并让他们利用Clojure的全部力量来提供更强有力的保证"同一性"往返后内存中的对象。 ClojureScript只能读取EDN,而不是完整的Clojure。

简单解决方案:不要将*print-dup*设置为true,只将纯数据从Clojure传递给ClojureScript。

更难解决方案:使用带标记的文字,两边都有(可能是共享的)关联阅读器。 (但这仍然不涉及*print-dup*。)

切线相关:EDN的大多数用例由Transit覆盖,速度更快,特别是在ClojureScript方面。


让我们从Clojure部分开始。 Clojure从一开始就有一个clojure.core/read-string函数,read是旧的Lispy意义上的Read-Eval-Print-Loop中的字符串,即它可以访问编译中使用的实际读者Clojure。[0]

后来,Rich Hickey& co决定推广Clojure的数据符号并发布EDN spec。 EDN是Clojure的子集;它仅限于Clojure语言的数据元素。

由于Clojure是一个Lisp,并且像所有的lisps一样,吹捧"代码是数据是代码"哲学,上段的实际含义可能不完全清楚。我不确定在任何地方都有详细的差异,但仔细检查Clojure Reader description和之前提到的EDN规范会显示一些差异。最明显的区别在于宏字符,尤其是#调度符号,它在Clojure中比在EDN中有更多目标。例如,#(* % %)符号是有效的Clojure,Clojure读者将转换为以下EDN的等价物:(fn [x] (* x x))。对于这个问题特别重要的是几乎没有记录的#=特殊读者宏,它可用于在读者内部执行任意代码。

由于Clojure读者可以使用完整的语言,因此可以将代码嵌入到阅读器正在阅读的字符串中,然后在阅读器中对其进行评估。可以找到一些示例here

clojure.edn/read-string函数严格限于EDN格式,而不是整个Clojure语言。特别是,它的操作不受*read-eval*变量的影响,并且它无法读取所有可能的有效Clojure代码片段。

事实证明,由于历史原因,Clojure读者是用Java编写的。由于它是一个重要的软件,运行良好,并且经过大量的调试和经过多年积极的Clojure在野外使用的战斗测试,Rich Hickey决定在ClojureScript编译器中重用它(这是主要的原因) ClojureScript编译器在JVM上运行。 ClojureScript编译过程主要发生在可以使用Clojure读取器的JVM上,因此ClojureScript代码由clojure.core/read-string(或者更接近它的近似clojure.core/read)函数解析。

但是您的Web应用程序无法访问正在运行的JVM。要求ClojureScript应用程序的Java applet看起来并不是一个非常有前途的想法,特别是因为ClojureScript的主要目标是将Clojure语言的范围扩展到JVM(和CLR)的范围之外。因此,决定ClojureScript无法访问自己的阅读器,因此无法访问自己的编译器(即没有eval也不read也不read-string ClojureScript)。这个决定及其影响将更详细地讨论here,实际上知道事情是如何发生的人(我不在那里,所以在这个解释的历史视角中可能存在一些不准确之处。)

所以ClojureScript没有等价的clojure.core/read-string(有些人认为它不是真正的口齿不清)。仍然,有一些方法可以在Clojure服务器和ClojureScript客户端之间传递Clojure数据结构,这确实是EDN工作中的激励因素之一。就像Clojure在EDN规范发布后获得限制(和更安全)阅读功能(clojure.edn/read-string)一样,ClojureScript也在标准发行版中获得了cljs.reader/read-string的EDN阅读器。可能有人认为,这两个函数的名称(或者更确切地说是它们的名称空间)之间的一致性会更好。

在我们最终回答您的原始问题之前,我们还需要关于*print-dup*的一小部分背景信息。请记住,*print-dup*是Clojure 1.0的一部分,这意味着它早于EDN,标记文字的概念和记录。我认为EDN和标记文字为*print-dup*的大多数用例提供了更好的选择。由于Clojure通常建立在一些数据抽象(列表,向量,集合,映射和通常的标量)之上,因此打印/读取循环的默认行为是保留数据的抽象形状(映射是一个地图),但不是特别是它的具体类型。例如,Clojure有多个地图抽象实现,例如PersistentArrayMap用于小地图,PersistentHashMap用于较大地图。该语言的默认行为假定您不关心具体类型。

对于您所做的极少数情况,或者对于更专业的类型(当时使用deftype或defstruct定义),您可能希望更多地控制如何读取这些类型,这就是print-dup的用途。

重点是,*print-dup*设置为truepr并且系列不会生成有效的EDN,但实际上Clojure数据包括一些显式的#=(eval build-my-special-type)形式, 有效的EDN。

[0]:在" lisps"中,编译器是根据数据结构明确定义的,而不是根据字符串定义的。虽然这看起来与通常的编译器(在处理过程中确实将字符流转换为数据结构)有些不同,但Lisp的定义特征是读者发出的数据结构是常用的数据结构。语言。换句话说,编译器基本上只是该语言中始终可用的函数。这不像过去那样独特,因为大多数动态语言都支持某种形式的eval; Lisp的独特之处在于eval采用数据结构,而不是字符串,这使得动态代码生成和评估变得更加容易。编译器的一个重要含义是"只是另一个函数"是编译器实际运行的是已经定义和可用的整个语言,并且到目前为止读取的所有代码也可用,这为Lisp宏系统打开了大门。

答案 1 :(得分:4)

cljs.reader/read仅支持EDN,但pr等会输出不会读取的标签(特别是协议和记录)。

一般情况下,如果在Clojure方面你可以验证(= value (clojure.edn/read-string (pr-str value))),你的cljs interop应该可以工作。这可能是限制性的,并且对EDN库的变通方法或修复方法进行了一些讨论。

根据您的数据情况,您可以按照Clojure Cookbook中的说明查看tagged库。

答案 2 :(得分:4)

实际上,可以通过cljs.reader / register-tag-parser注册自定义标签解析器!

记录我看起来像这样: (register-tag-parser! (s/replace (pr-str m/M1) "/" ".") m/map->M1)

@Gary - 非常好的回答