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

Neo*_*mov 26 clojure clojurescript edn

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

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

(with-out-str (binding [*print-dup* true] (prn tags)))
Run Code Online (Sandbox Code Playgroud)

那就是产生包含数据类型的对象序列化.但是,这是不可读的cljs.reader/read-string.我总是得到这种类型的错误:

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

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

另外,根据与Clojure文档的差异,唯一可以说的是:

The read and read-string functions are located in the cljs.reader namespace
Run Code Online (Sandbox Code Playgroud)

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

小智 49

简介:Clojure是EDN的超集.默认情况下,pr,prnpr-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是Read-Eval-Print-Loop的旧Lispy意义上的字符串,即它允许访问Clojure编译中使用的实际读者.[0]

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

由于Clojure是一个Lisp,并且像所有lisps一样,吹嘘"代码是数据是代码"的哲学,上段的实际含义可能并不完全清楚.我不确定在任何地方都有详细的差异,但仔细检查Clojure Reader描述和前面提到的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 applet看起来并不是一个非常有前途的想法,特别是因为ClojureScript的主要目标是将Clojure语言的范围扩展到JVM(和CLR)的范围之外.因此决定ClojureScript无法访问自己的读者,因此也无法访问自己的编译器(即ClojureScript中没有evalread没有read-string).这个决定及其影响将在这里更详细地讨论,实际上知道事情是如何发生的(我不在那里,所以在这个解释的历史视角中可能存在一些不准确之处).

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

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

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

关键是,*print-dup*设置为true,pr并且系列不会产生有效的EDN,但实际上Clojure数据包括一些显式#=(eval build-my-special-type)形式,这些形式都不是有效的EDN.

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


Mic*_*ink 5

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

一般来说,如果在 Clojure 方面您可以验证(= value (clojure.edn/read-string (pr-str value))),则您的 cljs 互操作应该可以工作。这可能是限制性的,并且有一些关于 EDN 库的变通方法或修复的讨论。

根据您的数据是什么样的,您可以查看Clojure Cookbook 中tagged描述的库。

  • @hellofunk `read` 接受一个 `PushbackReader`,而 `read-string` 从你的字符串中创建一个 `PushbackReader`,然后在它上面调用 `read`。 (2认同)

gda*_*nov 5

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

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

@Gary——很好的答案