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-string
читает строки, отформатированные в соответствии со спецификацией EDN.
Тем не менее, я начинаю с Clojure Script, и не ясно, если cljs.reader/read-string
соответствовать. Этот вопрос был вызван тем фактом, что у меня был веб-сервис, который генерировал код 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
но после тестирования cljs.reader/read-string
в REPL Rhino, я получил ту же ошибку, что означает, что он выбрасывается 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
Что говорит о том, что они должны иметь точно такое же поведение.
3 ответа
Резюме: Clojure - это расширенный набор EDN. По умолчанию, pr
, prn
а также 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
Строка в старом понимании Lispy цикла Read-Eval-Print-Loop, то есть она дает доступ к фактическому читателю, используемому при компиляции Clojure.[0]
Позже Rich Hickey & co решил продвигать нотацию данных Clojure и опубликовал спецификацию EDN. EDN является подмножеством Clojure; оно ограничено элементами данных языка Clojure.
Поскольку Clojure - это Лисп и, как и все другие, рекламирует философию "код - это код данных", реальные последствия приведенного выше абзаца могут быть не совсем понятны. Я не уверен, что где-либо есть подробные различия, но тщательное изучение описания 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
) функция.
Но у вашего веб-приложения нет доступа к работающей JVM. Требование Java-апплета для приложений ClojureScript не выглядело как очень многообещающая идея, тем более, что основная цель ClojureScript состояла в том, чтобы расширить область применения языка Clojure за пределы JVM (и CLR). Поэтому было принято решение, что ClojureScript не будет иметь доступа к своему собственному читателю, и, следовательно, не будет иметь доступа и к своему собственному компилятору (т. Е. Нет eval
ни read
ни read-string
в ClojureScript). Это решение и его последствия более подробно обсуждаются здесь кем-то, кто действительно знает, как все произошло (меня там не было, поэтому в исторической перспективе этого объяснения могут быть некоторые неточности).
Таким образом, ClojureScript не имеет эквивалента clojure.core/read-string
(а некоторые утверждают, что это не настоящий шутник). Тем не менее, было бы неплохо иметь какой-то способ связи структур данных Clojure между сервером Clojure и клиентом ClojureScript, и это действительно было одним из мотивирующих факторов в работе EDN. Так же, как Clojure получил ограниченную (и более безопасную) функцию чтения (clojure.edn/read-string
) после публикации спецификации EDN, 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
; Что уникально для Лиспа, так это то, что eval
принимает структуру данных, а не символьную строку, что значительно упрощает динамическую генерацию и оценку кода. Одним из важных следствий того, что компилятор является "просто еще одной функцией", является то, что компилятор на самом деле работает с целым языком, уже определенным и доступным, и весь прочитанный до сих пор код, также доступный, что открывает дверь в макросистему Lisp.
cljs.reader/read
поддерживает только EDN, но pr
и т.д. будет выводить теги (в частности, для протоколов и записей), которые не будут читаться.
В общем, если на стороне Clojure вы можете проверить, что (= value (clojure.edn/read-string (pr-str value)))
, ваше взаимодействие cljs должно работать. Это может быть ограничением, и есть некоторые обсуждения обходных путей или исправлений в библиотеке EDN.
В зависимости от того, как выглядят ваши данные, вы можете взглянуть на tagged
библиотека, как описано в Кулинарной книге Clojure.
На самом деле, можно зарегистрировать собственный анализатор тегов через cljs.reader/register-tag-parser!
для записи у меня это выглядит так:
(register-tag-parser! (s/replace (pr-str m/M1) "/" ".") m/map->M1)
@Gary - довольно хороший ответ