Подтвердить что ты не робот

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

Я не совсем понимаю связь между всеми этими функциями 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 носорога я получил ту же ошибку, что означает, что она выбрана самой 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

Это говорит о том, что они должны точно иметь такое же поведение.

4b9b3361

Ответ 1

Резюме: Clojure - это надмножество EDN. По умолчанию pr, prn и pr-str, при заданных структурах данных Clojure, создайте действительный EDN. *print-dup* изменяет это и заставляет их использовать полную мощность Clojure, чтобы дать более сильные гарантии относительно "одинаковости" объектов в памяти после кругового путешествия. ClojureScript может читать только EDN, а не полный Clojure.

Простое решение: не устанавливайте *print-dup* в true и передавайте только чистые данные из Clojure в ClojureScript.

Более сложное решение: используйте тегированные теги с помощью (возможно, совместно используемого) считывателя с обеих сторон. (Это все равно не будет включать *print-dup*.)

Связано с Tangentially: большинство случаев использования для 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 является Lisp, и, как и все lisp, он обращает внимание на философию "code is data is code", фактические последствия вышеупомянутого абзаца могут быть не совсем ясными. Я не уверен, что существует подробный разброс в любом месте, но тщательное изучение описания Clojure читателя и ранее упомянутой спецификации 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 не будет иметь доступа к собственному читателю и, следовательно, не будет иметь доступа к своему собственному компилятору (т.е. В ClojureScript нет eval и read и read-string). Это решение и его последствия более подробно обсуждаются здесь, кто действительно знает, как это произошло (меня там не было, поэтому могут быть некоторые неточности в исторической перспективе этого объяснения).

Итак, у ClojureScript нет эквивалента clojure.core/read-string (и некоторые из них утверждают, что это не истина lisp). Тем не менее, было бы неплохо иметь способ связывания структур данных 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; уникальным для Lisp является то, что eval использует структуру данных, а не строку символов, что значительно упрощает динамическое создание и оценку кода. Одним из важных следствий компилятора, являющегося "просто другой функцией", является то, что компилятор действительно работает со всем уже определенным и доступным языком, и весь код, который до сих пор читается, также доступен, что открывает дверь в макросистему Lisp.

Ответ 2

На самом деле, можно зарегистрировать собственный тег-парсер через cljs.reader/register-tag-parser!

для записи у меня это выглядит так: (register-tag-parser! (s/replace (pr-str m/M1) "/" ".") m/map->M1)

@Gary - довольно приятный ответ

Ответ 3

cljs.reader/read поддерживает только EDN, но pr и т.д. выведут теги (в частности, для протоколов и записей), которые не будут прочитаны.

В общем случае, если на стороне Clojure вы можете проверить, что (= value (clojure.edn/read-string (pr-str value))), ваш clus-интерфейс должен работать. Это может быть ограничением, и в EDN-библиотеке обсуждается обходные решения или исправления.

В зависимости от того, как выглядят ваши данные, вы можете посмотреть библиотеку tagged, как описано в Clojure Cookbook.