Fable: Сериализация в Fable 2

Созданный на 2 мар. 2018  ·  17Комментарии  ·  Источник: fable-compiler/Fable

Продолжение обсуждения началось здесь https://github.com/SaturnFramework/Saturn/issues/33

Также в этой ветке Twitter есть несколько интересных комментариев о неявной и явной сериализации.

Вероятно, я недостаточно хорошо выражал свои намерения в вопросе, о котором идет речь, и (как и любое критическое изменение) это вызвало некоторые споры. Я постараюсь изложить свои текущие взгляды ниже, чтобы иметь лучшую основу для обсуждения :)

  • Изначально альфа- версия Fable 2 будет поставляться без поддержки отражений. Я предполагаю, что это в основном повлияет на ofJson/toJson поскольку я думаю, что сейчас в Fable не так много других отражений. Обратите внимание, что альфа-версия, очевидно, предназначена не для производства, а для пользователей, которые могут попробовать и оставить отзыв.

  • Зачем отказываться от поддержки отражения? Что ж, в конце я переписываю большие части кода, чтобы (надеюсь) сделать его чище, удобнее в обслуживании и привлекательным для участников. При рефакторинге я заметил, что модель отражения в Fable не согласована и сильно загрязняет как сгенерированный код JS (уменьшение размеров пакетов - одна из основных целей для Fable 2), так и базу кода Fable. Вот почему я хотел бы начать новую версию Fable 2 alpha, чтобы увидеть реальные потребности пользователей и реализовать их с нуля (или нет, если нам это не нужно).

  • Как бы переосмыслить рефлексию? Прямо сейчас информация о сериализации встроена в типы. Это заставляет типы выглядеть толще, и это проблема, когда люди сравнивают альтернативы в REPL, поскольку они увидят, что Fable генерирует гораздо больше кода для простых типов (это уже произошло). Для Fable 2 я рассмотрел два варианта:

    • Сделайте информацию об отражении доступной с помощью статического метода в надежде, что она будет удалена путем встряхивания дерева при сборке для производства. Вероятно, это был бы самый простой способ, но он все равно будет отображать код в REPL.
    • Сгенерируйте информацию об отражении на сайте вызова, например, чтобы заменить typeof<Foo> . Я думаю, что это будет хорошо для большинства случаев, но это может наказать приложения, которые широко используют отражение, поскольку, вероятно, будет дублированный код.

      • Я также рассмотрел третий вариант: добавить дополнительный файл с информацией об отражении, чтобы он оставался скрытым для пользователя и извлекался только при необходимости. Но то, как Fable сейчас взаимодействует с JS-сборщиками и инструментами (Webpack ...), усложняет задачу.

  • Как может работать автосериализация в Fable 2? Записи станут простыми объектами JS, а объединения - массивами JS. Так что в большинстве случаев будет работать простой вызов JSON.parse/stringify . Проблема может заключаться в вещах, несовместимых с браузером JSON api, например в картах, наборах, датах, длинных числах и т. Д. Таким образом, Fable все равно потребуется знать информацию о полях во время выполнения, чтобы правильно надуть / спустить их.

  • Что мне не нравится в текущей сериализации? Есть несколько вещей

    • Он работает в большинстве случаев, но не во всех случаях, и может преподнести вам сюрпризы во время выполнения, что не очень хорошо, если мы продаем _безопасный_ язык.
    • Он каким-то образом _mirror_ Newtonsoft.Json на стороне внешнего интерфейса, и некоторые люди ожидают, что он будет поддерживать такие вещи, как атрибуты, встраивание информации о типе в json и т. Д.

      • Другие языки, которые я знаю (включая F #), не имеют встроенной сериализации в его базовую систему. Это увеличивает стоимость обслуживания кодовой базы Fable и затрудняет ее рефакторинг (как это произошло с Fable 2). Было бы очень приятно перенести сериализацию во внешнюю библиотеку.

  • Какие есть альтернативы? Как отмечалось выше, основной альтернативой, которая на данный момент должна работать с альфа- версией Fable 2, является их автоматическому сгенерированию .

dev2.0 discussion

Самый полезный комментарий

Одна из причин, по которой мне нравится Fable, - это поддержка JSON. Необходимость пойти и добавить функцию кодировщика / декодера повсюду будет сложной задачей. Я помню, что в очень, очень раннем выпуске типы F # были очень близки к объектам JS, и большую часть времени вы могли просто JSON.parse/stringify и зная, что это ограничение означало, что я мог почти заставить его работать. К сожалению, когда Fable стал лучше, я начал использовать списки и DateTimes в моем JSON, поэтому, если они уйдут, это будет немного переписать проект: S

Если бы генерация кода Thot.Json могла бы быть частью цепочки сборки как для клиента, так и для сервера (в net46x - да, я знаю, однажды мне придется обновиться), возможно, как какое-то событие перед сборкой, которое вызывает фальшивку (что я использовать для развертывания базы данных sql для FSharp.Data.SqlClient), тогда можно будет работать? Или задачи / цели сборки MS все еще актуальны ... Как пакет автоматически восстанавливается?

_ Я поссорился с Newtonsoft.Json несколько лет назад.

Все 17 Комментарий

Одна из причин, по которой мне нравится Fable, - это поддержка JSON. Необходимость пойти и добавить функцию кодировщика / декодера повсюду будет сложной задачей. Я помню, что в очень, очень раннем выпуске типы F # были очень близки к объектам JS, и большую часть времени вы могли просто JSON.parse/stringify и зная, что это ограничение означало, что я мог почти заставить его работать. К сожалению, когда Fable стал лучше, я начал использовать списки и DateTimes в моем JSON, поэтому, если они уйдут, это будет немного переписать проект: S

Если бы генерация кода Thot.Json могла бы быть частью цепочки сборки как для клиента, так и для сервера (в net46x - да, я знаю, однажды мне придется обновиться), возможно, как какое-то событие перед сборкой, которое вызывает фальшивку (что я использовать для развертывания базы данных sql для FSharp.Data.SqlClient), тогда можно будет работать? Или задачи / цели сборки MS все еще актуальны ... Как пакет автоматически восстанавливается?

_ Я поссорился с Newtonsoft.Json несколько лет назад.

Просто чтобы создать контраргумент, в настоящее время я использую отражение в производственном приложении узла Fable 1 как для десериализации типов сообщений в DU:

https://github.com/intel-hpdd/device-scanner/blob/16233ff62ad710aa02d6c8fe8acdbcad0c3e1e3e/IML.DeviceScannerDaemon/src/Main.fs#L13 -L20

и для автоматического определения типов кодирования потоков узлов, чтобы я мог составлять преобразования вместе и проверять их тип / настраивать, как ожидалось, во время выполнения:

https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L99 -L120

https://github.com/intel-hpdd/device-scanner/blob/16233ff62ad710aa02d6c8fe8acdbcad0c3e1e3e/IML.DeviceScannerDaemon/src/Main.fs#L23 -L42

Ожидается, что потребители этой службы отправят сообщение демону для получения потоковых данных: https://github.com/intel-hpdd/device-scanner/tree/master/IML.DeviceScannerDaemon (поэтому вместо этого сохраните его в виде записи / строки массива является идеальным), но я думаю, что могу просто сопоставить строку, прежде чем пытаться сериализовать, чтобы обойти это.

Для меня более серьезной проблемой является потеря возможности автоматической настройки потоков узлов из-за отсутствия информации об отражении во время выполнения, но я также могу обойти это.

@davidtme Я изучаю способ интеграции генерации декодеров / кодеров Thot.Json в цепочку сборки или через поддержку TP и т. д.

Я открою вопрос на Thot.Json, чтобы отслеживать свои дальнейшие успехи по этой теме.

Для вашей информации я просто публикую Thot.Json.Net, который предоставляет тот же API, что и Thot.Json для Fable.

Идея в том, что вы можете использовать директиву компилятора для совместного использования кода:

// By adding this condition, you can share you code between your client and server 
#if FABLE_COMPILER
open Thot.Json
#else
open Thot.Json.Net
#endif

Документация

@alfonsogarciacaro Можно ли определить информацию о типе во время компиляции и адаптировать процесс serialization зависимости от этого?

Информация о типе доступна в типе компиляции, да. Хотя, если мы хотим, чтобы оба могли получать доступ к информации о типах без отражения и перемещать сериализацию за пределы компилятора, нам понадобится какой-то плагин (который также будет недоступен в Fable 2 alpha: wink :). Но что именно вы имеете в виду под «адаптацией процесса сериализации»?

TBH, мне кажется странным исключать функциональность только для того, чтобы сгенерированный javascript больше понравился людям, оценивающим Fable. Я знаю, что девиз Fable - создание красивого javascript, и мне это очень нравится, но IMO, самый важный коммерческий аргумент, который Fable имеет в качестве компилятора для javascript, использует F #, отличную совместимость с экосистемой JS и применимость к любому javascript. время выполнения. Хороший сгенерированный javascript - это просто: приятно иметь. (Как насчет того, чтобы на самом деле опросить пользователей?)

он работает в большинстве случаев, но не во всех случаях, и он может преподнести вам сюрпризы во время выполнения, что не является чем-то хорошим, если мы продаем безопасный язык.

Затем мы реализуем невыполненные исключительные случаи. Сбои автоматической сериализации / десериализации, похоже, возникают там, где у нас недостаточно метаданных во время выполнения.

Он каким-то образом отражает Newtonsoft.Json на стороне интерфейса, и некоторые люди ожидают, что он будет поддерживать такие вещи, как атрибуты, встраивание информации о типе в json и т. Д.

Я бы сказал, что он обеспечивает ту же простоту, что и Newtonsoft.Json, и пользователи уже очень довольны его использованием по умолчанию. Если люди хотят большего контроля и настроек, то Thot.Json или Fable.SimpleJson обеспечат необходимый уровень контроля.

Другие языки, которые я знаю (включая F #), не имеют встроенной сериализации в его базовую систему. Это увеличивает стоимость обслуживания кодовой базы Fable и затрудняет ее рефакторинг (как это произошло с Fable 2). Было бы очень приятно перенести сериализацию во внешнюю библиотеку.

Я согласен с тем, что расходы на обслуживание увеличиваются с помощью ofJson<'a> и 'toJson`, но оно того стоит. Если бы мы создавали внешнюю библиотеку, тогда необходимо было бы хорошо реализовать отражение, чтобы потребители могли писать такие преобразователи.

@ Zaid-Ajaj С моей точки зрения, это не только для того, чтобы сгенерированный JavaScript выглядел красиво, но и для уменьшения размера пакета.

Мы видим, как многие люди жалуются на это, особенно в стране, где подключение к Интернету все еще медленное :)

@alfonsogarciacaro Я был заинтересован в том, чтобы Fable настраивал вызов сериализатора для поддержки карт, наборов и т. д., используя имя свойства в качестве ключа, но это плохая идея. Это будет означать большое количество дублирования кода, а также каждый вызов сериализатора не будет одинаковым. Забудем об этой идее :)

@alfonsogarciacaro Можно ли встроить информацию о типах в "модуль типов".

Модуль может содержать всю информацию о типе приложения, и если он не используется, Webpack должен иметь возможность удалить его, нет?

Большое спасибо за ваши комментарии. Я понимаю, что текущие помощники ofJson/toJson очень удобны, и их не следует заменять, если мы не предоставим что-то почти столь же простое в использовании. Также спасибо за образцы @jgrund , очень полезно посмотреть, как Fable используется в продакшене. Я вижу, что отражение используется только для ofJson Есть ли другое место, где вы используете typeof<'T> или подобное?

Спасибо также за понимание @ Zaid-Ajaj. Как говорит Максим, дело не только в том, чтобы сделать код более читабельным (на самом деле, в некоторых местах он может стать _ менее_ читаемым, но более оптимизированным в Fable 2), но и в уменьшении размеров пакетов и в том, чтобы сгенерированный код лучше подходил для инструментов оптимизации JS. Это также вопрос выживания, поскольку теперь, когда у нас есть более зрелые и сложные библиотеки, такие как Fulma, если размеры пакетов слишком велики, пользователи могут рассмотреть другие варианты (и мы знаем, что конкуренция между функциональными языками, компилирующими на JS, жесткая). Fable пытается скомпилировать язык F # с использованием _ большинства_ его функций, а также FSharp.Core и _ некоторых_ из BCL, и решение о том, является ли отражение функцией языка F # или частью среды выполнения BCL / .NET, является другой темой. Конечно, это что-то очень полезное, но я бы хотел использовать альфа-версию Fable 2, чтобы оценить ее затраты / преимущества и наличие у нас жизнеспособных альтернатив.

@MangelMaxime Да, это третий вариант, который я рассматривал выше. У меня есть некоторые сомнения по поводу компиляции часов, потому что этот модуль Types может перейти в несогласованное состояние. Но я не уверен, думаю, можно попробовать.

Опять же, спасибо всем за ваши комментарии, они действительно полезны, и, пожалуйста, убедитесь, что я меньше всего собираюсь делать что-либо, что может навредить нынешним пользователям Fable. Я постараюсь выпустить альфа-версию Fable 2, чтобы было легче сравнивать альтернативы и их эффекты: +1:

Моя единственная самая важная причина, по которой я выбрал Fable вместо Elm, заключалась в возможности использовать F # с обеих сторон и повторно использовать типы на разных уровнях. Мне не нужно отражение как таковое, но, как сказал @davidtme , более ранняя реализация «просто сработала». Мне было бы комфортно отказаться от некоторых аспектов системы типов ради сериализации JSON, но это верно практически для любого кроссплатформенного формата сериализации, и меня это устраивает.

Сериализация, извлеченная в плагин компилятора, была бы отличным компромиссом, если бы вы могли это осуществить;)

есть ли другое место, где вы используете typeof <'T> или подобное?

Извините, не выделил соответствующий раздел. https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L144

Делая это, я могу установить кодировку потоков и состояние читаемой стороны только на основе информации о времени компиляции, что избавляет меня от необходимости настраивать каждый поток вручную с информацией, которая уже закодирована по типам.

Исходя из этого, я могу создавать потоки вместе, не беспокоясь о параметрах жесткого кодирования заранее, что довольно приятно: https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/test/ Stream.Test.fs # L221 -L232

Еще одно умное использование отражения здесь:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L70

и тут:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L169

Вся идея проекта Fable.Remoting основана на наличии отражения как на сервере, так и на клиенте.

Вероятно, это не популярный запрос - если связь не зависит от задержки или производительности - но как насчет поддержки типов необработанных значений для транспорта (с поддержкой нулевого копирования или без нее)? Например, поддержка .NET упакованных и неуправляемых ("непреобразуемых") типов значений? По определению, он везде одинаково представлен как в родном, так и в обоих .NET. Это необработанное представление может быть критичным, поскольку иногда сериализация / десериализация json слишком интенсивны для процессора / памяти / gc / задержки на сервере (особенно при большом количестве клиентов и / или высокой скорости передачи сообщений). Нулевое копирование на клиенте также может поддерживаться с помощью поддержки ArrayView / TypedArray / DataView (поля будут считываться / записываться непосредственно из / в буфер).
Возможно, это также можно было бы использовать для собственного взаимодействия NodeJS (с ffi).

[<Struct>]
[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type Vector3 =
    val X: int32
    val Y: int32
    val Z: int32
    new(x,y,z) = {
      X=x;
      Y=y;
      Z=z;
    }
    new(dataview: DataView,offset) = {
    //TODO
    }

Спасибо за комментарий @zpodlovics. Должен сказать, что я не знаком с такой сериализацией. Вы говорите о JSON или двоичной сериализации? (Я немного поиграл с двоичной сериализацией с использованием протокола плоских буферов Google в одном проекте, но он включал много шаблонного кода.) Вы можете привести пример того, как будут выглядеть сериализованные данные? Может быть немного сложно иметь одно и то же представление как в .NET, так и в JS, поскольку Fable удаляет некоторые данные из типов, чтобы оптимизировать их в коде JS и во время выполнения. Помимо ограничений в JS, таких как невозможность определять типы значений (структур).

Кроме того, как отмечалось выше, я бы предпочел, чтобы сериализация была отделена от кода ядра компилятора. В Fable 2 мы, вероятно, сохраним некоторую сериализацию JSON по умолчанию, поскольку теперь она предназначена для удобства пользователей, но более сложные вещи желательно делать в отдельном пакете.

@alfonsogarciacaro Спасибо за комментарий!

Что ж, на самом деле это не формат сериализации, а прямое представление памяти, как оно было бы представлено в нативной структуре C. «Сериализованный» формат будет в точности похож на ac struct (native) [1]. Это также необходимо для собственного взаимодействия, поскольку оно использует то же представление памяти, что и собственное приложение. В предыдущем примере будет использоваться область памяти размером 3 * 4 = 12 байт. Структуру можно «эмулировать» как объект с помощью метода, заменяющего область резервной памяти (.Wrap).

Раннюю структуру можно было бы преобразовать во что-то вроде этого псевдокода с помощью JS DataView API (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32):

например.:

type Vector3 =
    val mutable view: DataView
    val mutable offset: int32
    static member XOffset=0
    static member YOffset=4
    static member ZOffset=8
    new(v: DataView,o:int32) = {
      view=v;
      offset=o;
    }
    member __.Wrap(v: DataView,o:int32) = {
      view=v;
      offset=o;
    }
    member __.X
        with get() = __.view.getInt32(__.offset+Vector3.XOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.XOffset,v)
    member __.Y
        with get() = __.view.getInt32(__.offset+Vector3.YOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.YOffset,v)
    member __.Z
        with get() = __.view.getInt32(__.offset+Vector3.ZOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.ZOffset,v)       

Да, это можно было бы создать вручную, но выполнение этого вручную потребует времени и может привести к ошибкам. Особенно, если у компилятора уже есть вся необходимая информация: типы, структура памяти, смещения полей и т. Д. Какой-то плагин расширения компилятора / api / ast также будет отлично работать с созданным пользователем кодогенератором. В конце концов, все столкнутся с двоичной проблемой - как читать / писать двоичные файлы, например: изображение / аудио / видео / документ / сетевые пакеты / и т. Д.

Встроенное представление необработанной памяти в качестве формата сериализации для взаимодействия (для поддержки ffi) может иметь невероятную ценность для всех. На каждой платформе у разработчиков есть два варианта управления платформой: 1) написать код взаимодействия как собственный код платформы (компилятор платформы, другой набор инструментов, другой процесс сборки / тестирования / развертывания и т. Д.) 2) написать код, представляющий необработанную память для взаимодействия + ffi. Не случайно в .NET есть встроенная поддержка pinvoke.

C:
[1] https://en.wikipedia.org/wiki/Struct_ (C_programming_language)
.СЕТЬ
[2] https://www.developerfusion.com/article/84519/mastering-structs-in-c/
JS
[3] https://github.com/TooTallNate/ref-struct

Большое спасибо за более подробное объяснение @zpodlovics! Я изучал использование представлений данных для имитации структур в прошлом, но я мало что сделал, потому что все еще есть ограничения (например, возможность использовать только числа, вложенные структуры сложно, копирование структуры из массива также сложно и т. Д. ). Кроме того, с помощью Fable уже можно лучше управлять памятью, используя числовые массивы, поскольку они будут преобразованы в типизированные массивы, но пока это не особо привлекательно для пользователей.

Вероятно, нам следует открыть новую проблему для этого, поскольку, как вы сказали, это не совсем связано с сериализацией. На данный момент я не могу считать это приоритетом, но было бы очень приятно увидеть комментарии по этому поводу. История плагина для Fable 2 пока еще не известна, но мы могли бы сделать ее так, чтобы она позволяла делать что-то подобное, если вы заинтересованы в работе с таким плагином.

Однако следует учитывать, что поддержка WebAssembly поступает в .NET / F #, и представление памяти в этом случае будет ближе к модели .NET. Когда это произойдет, Fable может остаться способом интеграции F # ближе к экосистеме JS (как сейчас) и использования текущих инструментов / библиотек, доступных для создания внешнего интерфейса ваших приложений.

@alfonsogarciacaro Можем ли мы закрыть эту проблему?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги