clojure clojurescript edn

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



(3)

No tengo claro la relación entre todas estas funciones de cadena de lectura. Bueno, está claro que clojure.core/read-string puede leer cualquier cadena serializada que sea enviada por pr[n] o incluso print-dup . También está claro que clojure.edn/read-string sí lee cadenas que están formateadas de acuerdo con la especificación EDN .

Sin embargo, estoy empezando con Clojure Script y no está claro si cljs.reader/read-string cumple. Esta pregunta se ha desencadenado por el hecho de que tenía un servicio web que estaba emitiendo código de clojure serializado de esa manera:

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

Eso fue producir la serialización de objetos que incluye los tipos de datos. Sin embargo, esto no fue legible por cljs.reader/read-string . Siempre estaba recibiendo error de este tipo:

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

Al principio, pensé que cljs-ajax había lanzado este error, pero después de probar cljs.reader/read-string en un REPL de rinoceronte, obtuve el mismo error, lo que significa que cljs.reader/read-string sí lo hizo. . Es lanzado por la función del maybe-read-tagged-type en cljs.reader pero no está claro si esto es porque el lector solo funciona con datos EDN, o si ...?

Además, del documento Diferencias de Clojure , lo único que se dice es:

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

Lo que sugiere que deberían tener exactamente el mismo comportamiento.


En realidad, es posible registrar un analizador de etiquetas personalizado a través de cljs.reader / register-tag-parser!

para un registro lo tengo así: (register-tag-parser! (s/replace (pr-str m/M1) "/" ".") m/map->M1)

@Gary - muy buena respuesta


Resumen: Clojure es un superconjunto de EDN. De forma predeterminada, pr , prn y pr-str , cuando se proporcionan las estructuras de datos de Clojure, producen EDN válidos. *print-dup* cambia eso y hace que utilicen todo el poder de Clojure para ofrecer garantías más firmes sobre la "uniformidad" de los objetos en la memoria después de un viaje de ida y vuelta. ClojureScript solo puede leer EDN, no Clojure completo.

Solución sencilla: no establezca *print-dup* en true, y solo pase datos puros de Clojure a ClojureScript.

Solución más difícil: use literales etiquetados, con un lector asociado (posiblemente compartido) en ambos lados. (Sin embargo, esto no implicará *print-dup* ).

Relacionado tangencialmente: la mayoría de los casos de uso para EDN están cubiertos por Transit , que es más rápido, especialmente en el lado de ClojureScript.

Vamos a empezar con la parte Clojure. Clojure tenía, desde el principio, una función clojure.core/read-string , que read cadena en el antiguo sentido de Lispy del Read-Eval-Print-Loop, es decir, da acceso al lector real utilizado en la compilación de Clojure. . [0]

Más tarde, Rich Hickey & co decidió promocionar la notación de datos de Clojure y publicó la especificación EDN . EDN es un subconjunto de Clojure; está limitado a los elementos de datos del lenguaje Clojure.

Como Clojure es un Lisp y, como todas las trampas, promociona la filosofía de "el código es el código de los datos", las implicaciones reales del párrafo anterior pueden no estar completamente claras. No estoy seguro de que haya una diferencia detallada en ninguna parte, pero un examen cuidadoso de la descripción de Clojure Reader y la especificación EDN mencionada anteriormente muestra algunas diferencias. Las diferencias más evidentes se encuentran alrededor de los caracteres macro y, en particular, el símbolo # dispatch, que tiene muchos más objetivos en Clojure que en EDN. Por ejemplo, la notación #(* % %) %% #(* % %) es válida para Clojure, que el lector de Clojure convertirá en el equivalente de la siguiente EDN: (fn [x] (* xx)) . De particular importancia para esta pregunta es la macro de lector especial #= escasamente documentada, que se puede utilizar para ejecutar código arbitrario dentro del lector.

Como el lenguaje completo está disponible para el lector de Clojure, es posible incrustar código en la cadena de caracteres que el lector está leyendo y hacer que se evalúe en ese momento en el lector. Algunos ejemplos se pueden encontrar here .

La función clojure.edn/read-string está estrictamente limitada al formato EDN, no a todo el lenguaje Clojure. En particular, su operación no está influenciada por la variable *read-eval* y no puede leer todos los fragmentos de código de Clojure válidos posibles.

Resulta que el lector Clojure, en su mayoría por razones históricas, está escrito en Java. Rich Hickey decidió reutilizarlo en el compilador de ClojureScript (esta es la razón principal por la que es un software importante, funciona bien y ha sido depurado y probado en la batalla por algunos años de uso activo de Clojure en la naturaleza). el compilador ClojureScript se ejecuta en la JVM). El proceso de compilación de ClojureScript ocurre principalmente en la JVM, donde el lector de Clojure está disponible, y así el código ClojureScript se analiza mediante la función clojure.core/read-string (o más bien su primo cercano clojure.core/read ).

Pero su aplicación web no tiene acceso a una JVM en ejecución. Requerir un applet de Java para las aplicaciones ClojureScript no parecía una idea muy prometedora, especialmente porque el objetivo principal de ClojureScript era extender el alcance del lenguaje Clojure más allá de los límites de la JVM (y el CLR). Así que se tomó la decisión de que ClojureScript no tendría acceso a su propio lector y, por lo tanto, tampoco tendría acceso a su propio compilador (es decir, no hay eval ni read ni read-string en ClojureScript). Esta decisión y sus implicaciones se discuten con mayor detalle here , por alguien que realmente sabe cómo sucedieron las cosas (no estuve allí, por lo que puede haber algunas inexactitudes en la perspectiva histórica de esta explicación).

Por lo tanto, ClojureScript no tiene equivalente de clojure.core/read-string (y algunos dirían que, por lo tanto, no es un verdadero lisp). Sin embargo, sería bueno tener alguna forma de comunicar las estructuras de datos de Clojure entre un servidor Clojure y un cliente ClojureScript, y de hecho ese fue uno de los factores motivadores en el esfuerzo de EDN. Al igual que Clojure obtuvo una función de lectura restringida (y más segura ) ( clojure.edn/read-string ) después de la publicación de la especificación EDN, ClojureScript también obtuvo un lector EDN en la distribución estándar como cljs.reader/read-string . Se puede argumentar que un poco más de coherencia entre los nombres de estas dos funciones (o más bien su espacio de nombres) hubiera sido bueno.

Antes de que podamos finalmente responder a su pregunta original, necesitamos un poco más de contexto con respecto a *print-dup* . Recuerde que *print-dup* era parte de Clojure 1.0, lo que significa que es anterior a EDN, la noción de literales etiquetados y registros. Yo diría que EDN y los literales etiquetados ofrecen una mejor alternativa para la mayoría de los casos de uso de *print-dup* . Como Clojure generalmente se construye sobre unas pocas abstracciones de datos (lista, vector, conjunto, mapa y los escalares habituales), el comportamiento predeterminado del ciclo de impresión / lectura es preservar la forma abstracta de los datos (un mapa es un Mapa), pero no especialmente su tipo concreto. Por ejemplo, Clojure tiene implementaciones múltiples de la abstracción del mapa, como PersistentArrayMap para mapas pequeños y PersistentHashMap para mapas más grandes. El comportamiento predeterminado del lenguaje supone que no le importa el tipo concreto.

Para los casos raros en los que lo haga, o para los tipos más especializados (definidos con deftype o defstruct, en ese momento), es posible que desee un mayor control sobre cómo se leen estos, y para eso está print-dup.

El punto es que, con *print-dup* establecido en true , pr y family no producirán EDN válidos, pero en realidad los datos de Clojure incluyen algunos formularios explícitos #=(eval build-my-special-type) , que no son EDN válidos.

[0]: En "lisps", el compilador se define explícitamente en términos de estructuras de datos, en lugar de en términos de cadenas de caracteres. Si bien esto puede parecer una pequeña diferencia con los compiladores habituales (que de hecho transforman el flujo de caracteres en estructuras de datos durante su procesamiento), la característica definitoria de Lisp es que las estructuras de datos que emite el lector son las estructuras de datos comúnmente utilizadas en el idioma. En otras palabras, el compilador es básicamente una función disponible en todo momento en el idioma. Esto no es tan único como solía ser, ya que la mayoría de los lenguajes dinámicos admiten alguna forma de eval ; Lo que es exclusivo de Lisp es que eval toma una estructura de datos, no una cadena de caracteres, lo que hace que la generación y evaluación dinámica de códigos sea mucho más fácil. Una implicación importante de que el compilador sea "simplemente otra función" es que el compilador se ejecuta realmente con el lenguaje completo ya definido y disponible, y todo el código leído hasta ahora también está disponible, lo que abre la puerta al sistema macro de Lisp.


cljs.reader/read solo es compatible con EDN, pero pr etc. imprimirá etiquetas (en particular, para protocolos y registros) que no leerán.

En general, si en el lado Clojure puede verificar que (= value (clojure.edn/read-string (pr-str value))) , su interoperabilidad cljs debería funcionar. Esto puede ser limitante, y hay algunas discusiones sobre soluciones o arreglos a la biblioteca EDN.

Dependiendo de la apariencia de sus datos, puede echar un vistazo a la biblioteca tagged como se describe en el Libro de cocina de Clojure .