web-dev-qa-db-ja.com

ClojureとClojureScript:clojure.core / read-string、clojure.edn / read-string、cljs.reader / read-string

これらすべての読み取り文字列関数間の関係についてはよくわかりません。 clojure.core/read-stringは、pr[n]またはprint-dupによって出力されるシリアル化された文字列を読み取ることができることは明らかです。 clojure.edn/read-stringEDN仕様 に従ってフォーマットされた文字列を読み取ることも明らかです。

しかし、私はClojure Scriptから始めており、cljs.reader/read-stringが準拠しているかどうかは明確ではありません。この質問は、clojureコードを発行しているWebサービスがそのようにシリアル化されているという事実によって引き起こされました。

(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によってスローされたと思っていましたが、rhinoREPLでcljs.reader/read-stringをテストした後、同じエラーが発生しました。つまり、cljs.reader/read-string自体によってスローされます。 maybe-read-tagged-typecljs.reader関数によってスローされますが、これがリーダーがEDNデータのみを処理するためか、それとも...?

また、 Clojureとの違い ドキュメントから、言われていることは次のとおりです。

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

これは、それらがまったく同じ動作をする必要があることを示唆しています。

26
Neoasimov

概要:ClojureはEDNのスーパーセットです。デフォルトでは、prprn、およびpr-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-Eval-Print-Loopの古いLispyの意味で文字列をreadsします。つまり、Clojureのコンパイルで使用される実際のリーダーにアクセスできます。 [0]

その後、Rich Hickey&coは、Clojureのデータ表記を促進することを決定し、 EDN仕様 を公開しました。 EDNはClojureのサブセットです。 Clojure言語のデータ要素に限定されています。

ClojureはLISPであり、すべてのLispと同様に、「コードはデータはコードである」という哲学を宣伝しているため、上記の段落の実際の意味は完全には明確ではない可能性があります。どこかに詳細な差分があるかどうかはわかりませんが、 Clojure Readerの説明 と前述のEDN仕様を注意深く調べると、いくつかの違いがわかります。最も明らかな違いは、マクロ文字、特に#ディスパッチシンボルに関するものです。これは、EDNよりもClojureの方がはるかに多くのターゲットを持っています。たとえば、#(* % %)表記は有効なClojureであり、Clojureリーダーは次のEDNと同等になります:(fn [x] (* x x))。この質問で特に重要なのは、ほとんど文書化されていない#=特殊リーダーマクロです。これを使用して、リーダー内で任意のコードを実行できます。

Clojureリーダーは完全な言語を使用できるため、リーダーが読み取っている文字列にコードを埋め込み、リーダー内でその場で評価することができます。いくつかの例を見つけることができます ここ

clojure.edn/read-string関数は、Clojure言語全体ではなく、EDN形式に厳密に制限されています。特に、その操作は*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アプレットを要求することは、特にClojureScriptの主な目的がJVMの範囲を超えてClojure言語の範囲を拡張することであったため、非常に有望なアイデアのようには見えませんでした。そのため、ClojureScriptは独自のリーダーにアクセスできず、その結果、独自のコンパイラーにもアクセスできないという決定が下されました(つまり、evalreadread-stringもありません。この決定とその影響については、実際にどのように起こったかを知っている人が詳細に説明しています ここ (私はそこにいなかったので、この説明の歴史的な観点に誤りがあるかもしれません) )。

したがって、ClojureScriptにはclojure.core/read-stringに相当するものはありません(したがって、それは真のLISPではないと主張する人もいます)。それでも、ClojureサーバーとClojureScriptクライアントの間でClojureデータ構造を通信する方法があればいいのですが、それがEDNの取り組みの動機付け要因の1つでした。 ClojureがEDN仕様の公開後に制限された(そしてより安全な)読み取り関数(clojure.edn/read-string)を取得したように、ClojureScriptも標準でEDNリーダーを取得しましたcljs.reader/read-stringとして配布。これらの2つの関数の名前(またはむしろそれらの名前空間)の間のもう少し一貫性が良かったと主張されるかもしれません。

最終的に元の質問に答える前に、*print-dup*に関するもう1つの小さなコンテキストが必要です。 *print-dup*はClojure1.0の一部であったことを忘れないでください。つまり、EDN、タグ付きリテラル、およびレコードの概念よりも前のものです。 EDNとタグ付きリテラルは、*print-dup*のほとんどのユースケースに対してより良い代替手段を提供すると私は主張します。 Clojureは通常、いくつかのデータ抽象化(リスト、ベクトル、セット、マップ、および通常のスカラー)の上に構築されるため、印刷/読み取りサイクルのデフォルトの動作は、データの抽象的な形状を保持することです(マップはマップ)、しかし特にその具体的なタイプではありません。たとえば、Clojureには、小さいマップの場合は PersistentArrayMap 、大きいマップの場合は PersistentHashMap など、マップ抽象化の複数の実装があります。言語のデフォルトの動作は、具象型を気にしないことを前提としています。

まれなケース、またはより特殊なタイプ(その時点でdeftypeまたはdefstructで定義されている)の場合、これらの読み取り方法をより細かく制御する必要がある場合があります。これがprint-dupの目的です。

重要なのは、*print-dup*trueに設定されている場合、prとファミリは有効なEDNを生成しませんが、実際には、いくつかの明示的な#=(eval build-my-special-type)フォームを含むClojureデータnot有効なEDN。

[0]:「lisps」では、コンパイラーは文字列ではなくデータ構造の観点から明示的に定義されています。これは通常のコンパイラ(実際に処理中に文字ストリームをデータ構造に変換する)との小さな違いのように見えるかもしれませんが、LISPの明確な特徴は、リーダーによって出力されるデータ構造がで一般的に使用されるデータ構造であるということです。言語。言い換えれば、コンパイラは基本的に言語で常に利用可能な単なる関数です。ほとんどの動的言語は何らかの形式のevalをサポートしているため、これは以前ほどユニークではありません。 LISPのユニークな点は、evalが文字列ではなくデータ構造をとることです。これにより、動的なコード生成と評価がはるかに簡単になります。コンパイラが「単なる別の関数」であることの重要な意味の1つは、コンパイラが実際に定義されて利用可能な言語全体で実行され、これまでに読み取られたすべてのコードも利用可能であるということです。これにより、LISPマクロシステムへの扉が開かれます。

49
Gary Verhaegen

実際には、cljs.reader/register-tag-parserを介してカスタムタグパーサーを登録することが可能です!

レコードの場合、次のようになります:(register-tag-parser! (s/replace (pr-str m/M1) "/" ".") m/map->M1)

@Gary —非常に良い答え

4
gdanov

cljs.reader/readはEDNのみをサポートしますが、prなどは読み取らないタグ​​(特にプロトコルとレコード)を出力します。

一般に、Clojure側の場合は、(= value (clojure.edn/read-string (pr-str value)))、cljs相互運用機能が機能するはずです。これは制限となる可能性があり、EDNライブラリの回避策または修正についての議論があります。

データがどのように見えるかに応じて、 Clojure Cookbook で説明されているようにtaggedライブラリを確認できます。

4