Fable: Serializando en Fable 2

Creado en 2 mar. 2018  ·  17Comentarios  ·  Fuente: fable-compiler/Fable

La continuación de la discusión comenzó aquí https://github.com/SaturnFramework/Saturn/issues/33

También hay algunos comentarios interesantes sobre la serialización implícita frente a la explícita en este hilo de Twitter .

Probablemente no fui lo suficientemente bueno para expresar mis intenciones en el tema vinculado y (como cualquier cambio importante) esto ha iniciado cierta controversia. Intentaré exponer mi pensamiento actual a continuación para tener una mejor base para la discusión :)

  • Fable 2 alpha se enviaría inicialmente sin soporte de reflexión. Mi suposición es que esto afectará principalmente a ofJson/toJson ya que creo que no hay muchas otras reflexiones en este momento en Fable. Tenga en cuenta que, obviamente, la versión alfa no estará destinada a la producción, sino a los usuarios para que la prueben y proporcionen comentarios.

  • ¿Por qué soltar el soporte de reflexión? Bueno, al final estoy reescribiendo gran parte del código para (con suerte) hacerlo más limpio, más fácil de mantener y atractivo para los colaboradores. En la refactorización, he notado que el modelo de reflexión en Fable no es consistente y contamina mucho tanto el código JS generado (reducir el tamaño de los paquetes es uno de los principales objetivos de Fable 2) como el código base de Fable. Es por eso que me gustaría _comenzar de nuevo_ con Fable 2 alpha para ver las necesidades reales de los usuarios y volver a implementarlo desde cero (o no, si no lo necesitamos).

  • ¿Cómo se reimplementaría la reflexión? En este momento, la información de serialización está incrustada en los tipos. Esto hace que los tipos parezcan más gordos y es un problema cuando las personas están comparando alternativas en el REPL, ya que verán que Fable genera mucho más código para tipos simples (ya sucedió). Para Fable 2, he considerado dos opciones:

    • Haga que la información de reflexión esté disponible a través de un método estático con la esperanza de que se elimine sacudiendo los árboles cuando se construya para la producción. Esta sería probablemente la forma más fácil, pero aún mostrará el código en el REPL.
    • Genere información de reflexión en el sitio de la llamada para reemplazar typeof<Foo> por ejemplo. Creo que esto será bueno para la mayoría de los casos, pero puede penalizar a las aplicaciones que usan la reflexión de manera extensiva, ya que probablemente habrá código duplicado.

      • También consideré una tercera opción: inyectar un archivo adicional con la información de reflexión para que permanezca oculto para el usuario y solo se recupere si es necesario. Pero la forma en que Fable interactúa en este momento con los paquetes y herramientas de JS (Webpack ...) hace que esto sea complicado.

  • ¿Cómo podría funcionar la serialización automática en Fable 2? Los registros se convertirán en objetos JS simples y uniones, matrices JS. Entonces, en la mayoría de los casos, simplemente llamar al nativo JSON.parse/stringify funcionaría. El problema serían las cosas que no son compatibles con el navegador JSON api, como mapas, conjuntos, fechas, largos, etc. Por lo tanto, Fable aún necesitará conocer información sobre los campos en tiempo de ejecución para poder inflarlos / desinflarlos.

  • ¿Qué no me gusta de la serialización actual? Hay algunas cosas

    • Funciona para la mayoría de los casos, pero no para todos , y puede darte sorpresas en tiempo de ejecución, lo cual no es algo bueno si estamos vendiendo un idioma _safe_.
    • De alguna manera _mirror_ Newtonsoft.Json en el lado de la interfaz, y algunas personas esperan que admita cosas como atributos, incrustación de información de tipo en el json, etc.

      • Otros idiomas que conozco (F # incluido) no tienen serialización incorporada en su sistema central. Esto aumenta el costo de mantenimiento del código base de Fable y hace que sea más difícil refactorizarlo (como sucedió con Fable 2). Me haría muy feliz trasladar la serialización a una biblioteca externa.

  • ¿Cuales son las alternativas? Como se comentó en el número anterior, la principal alternativa en este momento que debería funcionar con Fable 2 alpha de inmediato es Thot.Json . Esta biblioteca le brinda más control sobre el JSON generado y una validación mucho mejor. El único inconveniente es que debe escribir los decodificadores usted mismo, pero ya hay trabajo para generarlos automáticamente .

dev2.0 discussion

Comentario más útil

Una de las razones por las que me gusta Fable es por su compatibilidad con JSON. Tener que ir y agregar la función de codificador / decodificador por todas partes va a ser una venta difícil. Recuerdo que en una versión muy, muy temprana, los tipos de F # estaban muy cerca de los objetos JS y la mayoría de las veces solo se podía JSON.parse/stringify y saber esa limitación significaba que podía hacer que funcionara. Desafortunadamente, a medida que Fable mejoró, comencé a usar Lists y DateTimes en mi JSON, así que si fueran, sería una reescritura del proyecto: S

Si la generación de código Thot.Json podría ser parte de la cadena de compilación tanto para el cliente como para el servidor (en net46x, sí, lo sé, algún día tendré que actualizar), tal vez como una especie de evento previo a la compilación que llame falso (que yo usar para implementar una base de datos SQL para FSharp.Data.SqlClient), ¿podría ser viable? ¿O son las tareas / objetivos de compilación de MS todavía una cosa ... cómo el paquete se restaura automáticamente?

_Me peleé con Newtonsoft.Json hace años_

Todos 17 comentarios

Una de las razones por las que me gusta Fable es por su compatibilidad con JSON. Tener que ir y agregar la función de codificador / decodificador por todas partes va a ser una venta difícil. Recuerdo que en una versión muy, muy temprana, los tipos de F # estaban muy cerca de los objetos JS y la mayoría de las veces solo se podía JSON.parse/stringify y saber esa limitación significaba que podía hacer que funcionara. Desafortunadamente, a medida que Fable mejoró, comencé a usar Lists y DateTimes en mi JSON, así que si fueran, sería una reescritura del proyecto: S

Si la generación de código Thot.Json podría ser parte de la cadena de compilación tanto para el cliente como para el servidor (en net46x, sí, lo sé, algún día tendré que actualizar), tal vez como una especie de evento previo a la compilación que llame falso (que yo usar para implementar una base de datos SQL para FSharp.Data.SqlClient), ¿podría ser viable? ¿O son las tareas / objetivos de compilación de MS todavía una cosa ... cómo el paquete se restaura automáticamente?

_Me peleé con Newtonsoft.Json hace años_

Solo para generar un contrapunto, actualmente estoy usando la reflexión en una aplicación de nodo Fable 1 de producción tanto para deserializar tipos de mensajes en una DU:

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

y para determinar automáticamente los tipos de codificación de los flujos de nodos para poder componer transformaciones juntas y hacer que se verifiquen / configuren como se esperaba en tiempo de ejecución:

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

Se espera que los consumidores de este servicio envíen un mensaje al demonio para obtener datos transmitidos: https://github.com/intel-hpdd/device-scanner/tree/master/IML.DeviceScannerDaemon (por lo que se mantiene un registro / cadena en su lugar de una matriz es ideal) pero creo que puedo hacer coincidir cadenas antes de intentar serializar para evitar eso.

El problema más importante para mí es perder la capacidad de configurar automáticamente los flujos de nodos en función de la falta de información de reflexión en tiempo de ejecución, pero es posible que también pueda solucionar esto.

@davidtme Estoy explorando formas de integrar la generación de decodificadores / codificadores Thot.Json en la cadena de compilación o mediante el soporte de TP, etc.

Abriré un número en Thot.Json para seguir mi progreso futuro en este tema.

Para su información, acabo de publicar Thot.Json.Net, que proporciona la misma API que Thot.Json para Fable.

La idea es que puede usar la directiva del compilador para compartir el código:

// 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

Documentación

@alfonsogarciacaro ¿Sería posible detectar información de tipo en tiempo de compilación y adaptar el proceso serialization dependiendo de eso?

La información de tipo está disponible en el tipo de compilación, sí. Aunque si queremos que _ambos_ accedan a la información de tipos sin reflexión y muevan la serialización fuera del compilador, necesitaríamos algún tipo de complemento (que tampoco estará disponible en Fable 2 alpha: wink :). Pero, ¿qué quiere decir exactamente con "adaptar el proceso de serialización"?

TBH, me parece extraño excluir la funcionalidad solo para hacer que el javascript generado sea más atractivo para las personas que evalúan Fable. Sé que es el lema de Fable generar un buen javascript y realmente me gusta, pero en mi opinión, el punto de venta más fuerte que Fable tiene como compilador de javascript es usar F #, la increíble interoperabilidad con el ecosistema JS y ser aplicable a cualquier javascript. tiempo de ejecución. Un buen javascript generado es solo eso: bueno tenerlo. (¿Qué tal hacer una encuesta para preguntar a los usuarios?)

funciona para la mayoría de los casos, pero no para todos, y puede darte sorpresas en tiempo de ejecución, lo cual no es algo bueno si estamos vendiendo un lenguaje seguro.

Luego implementamos los casos excepcionales que fallan. Las fallas de serialización / deserialización automática parecen ocurrir en lugares donde no tenemos suficientes metadatos en tiempo de ejecución.

De alguna manera refleja Newtonsoft.Json en el lado de la interfaz, y algunas personas esperan que admita cosas como atributos, incrustación de información de tipo en el json, etc.

Yo diría que proporciona la misma facilidad de Newtonsoft.Json y los usuarios ya están muy contentos con su uso como predeterminado. Si la gente quiere más control y personalizaciones, Thot.Json o Fable.SimpleJson proporcionan el nivel de control necesario.

Otros idiomas que conozco (F # incluido) no tienen serialización incorporada en su sistema central. Esto aumenta el costo de mantenimiento del código base de Fable y hace que sea más difícil refactorizarlo (como sucedió con Fable 2). Me haría muy feliz trasladar la serialización a una biblioteca externa.

Estoy de acuerdo en que los costos de mantenimiento aumentan con ofJson<'a> y 'toJson' pero realmente vale la pena. Si tuviéramos que hacer una biblioteca externa, entonces la reflexión debe estar bien implementada para que los consumidores puedan escribir tales convertidores.

@ Zaid-Ajaj Desde mi punto de vista, no se trata solo de hacer que el JavaScript generado se vea bien, sino también de reducir el tamaño del paquete.

Vemos a mucha gente quejándose de eso, especialmente en países donde la conexión a Internet sigue siendo lenta :)

@alfonsogarciacaro Estaba pensando en hacer que Fable personalice la llamada del serializador para admitir Mapas, Conjuntos, etc. usando el nombre de la propiedad como clave, pero es una mala idea. Esto significaría una gran cantidad de duplicación de código y también cada llamada de serializador no sería la misma. Olvidemos esta idea :)

@alfonsogarciacaro ¿Sería posible incrustar la información de tipos en un "módulo de tipos"?

El módulo podría contener toda la información de tipo de la aplicación y, si no se usa, Webpack debería poder eliminarlo, ¿no?

Muchas gracias por sus comentarios. Entiendo que los ofJson/toJson helpers actuales son muy convenientes y no deberían ser reemplazados a menos que proporcionemos algo que sea casi tan fácil de usar. Gracias también por las muestras @jgrund , es muy útil ver cómo se usa Fable en producción. Por lo que puedo ver, la reflexión solo se usa para ofJson , ¿hay algún otro lugar donde use typeof<'T> o similar?

Gracias también por tus ideas @ Zaid-Ajaj. Como dice Maxime, no se trata solo de hacer que el código sea más legible (de hecho, en algunos lugares puede volverse _menos_ legible pero más optimizado en Fable 2), sino de reducir el tamaño de los paquetes y hacer que el código generado se ajuste mejor a las herramientas de optimización de JS. También es una cuestión de supervivencia, ya que ahora que tenemos bibliotecas más maduras y complejas como Fulma, si los tamaños de los paquetes son demasiado grandes, los usuarios pueden considerar otras opciones (y sabemos que la competencia es dura entre los lenguajes funcionales que compilan en JS). Fable intenta compilar el lenguaje F # con _la mayoría_ de sus características y FSharp.Core y _algunos_ del BCL y decidir si la reflexión es una característica del lenguaje F # o parte del tiempo de ejecución de BCL / .NET es otro tema. Por supuesto, es algo muy útil, pero me gustaría usar la versión alfa de Fable 2 para evaluar sus costos / beneficios y si tenemos alternativas viables.

@MangelMaxime Sí, esa es la tercera opción que estaba considerando anteriormente. Tengo algunas dudas con las compilaciones de relojes, porque este módulo Types puede volverse inconsistente. Pero no estoy seguro, creo que podemos intentarlo.

Nuevamente, gracias a todos por sus comentarios, son realmente útiles y por favor asegúrese de que sea la menor de mis intenciones hacer algo que pueda dañar a los usuarios actuales de Fable. Intentaré lanzar una versión alfa de Fable 2 para que sea más fácil comparar las alternativas y sus efectos: +1:

Mi razón más importante para elegir Fable sobre Elm fue poder usar F # en ambos lados y reutilizar los tipos en los niveles. No necesito la reflexión per se, pero como dijo @davidtme , la implementación anterior "simplemente funcionó". Me sentiría cómodo renunciando a algunos de los aspectos del sistema de tipos para el propósito de la serialización JSON, pero eso es cierto para casi cualquier formato de serialización multiplataforma y estoy de acuerdo con eso.

La serialización extraída en un complemento del compilador sería un gran compromiso, si pudiera lograrlo;)

¿Hay algún otro lugar donde use typeof <'T> o similar?

Lo siento, no resalté la sección relevante. https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L144

Al hacer esto, puedo establecer una codificación de transmisiones y el estado del lado legible solo a partir de la información de tiempo de compilación, lo que me ahorra configurar cada transmisión manualmente con la información que ya está codificada por los tipos.

En base a eso, puedo hacer cosas como componer transmisiones juntas sin preocuparme por las opciones de codificación de antemano, lo cual es bastante bueno: https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/test/ Stream.Test.fs # L221 -L232

Otro uso inteligente de la reflexión aquí:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L70

y aquí:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L169

Toda la idea del proyecto Fable.Remoting se basa en la disponibilidad de reflexión tanto en el servidor como en el cliente.

Probablemente no sea una solicitud popular, a menos que la comunicación sea sensible a la latencia o al rendimiento, pero ¿qué tal si se admiten tipos de valor sin procesar para el transporte (con o sin soporte de copia cero)? Por ejemplo, ¿admite tipos de valor empaquetados y no administrados ("blittable") de .NET? Por definición, tiene la misma representación en todas partes, tanto en nativo como en .NET. Esta representación sin procesar podría ser crítica, ya que a veces la serialización / deserialización de json es demasiado cpu / memory / gc / latencia intensiva en el servidor (especialmente con muchos clientes y / o alta tasa de mensajes). La copia cero en el cliente también podría ser compatible con el respaldo de ArrayView / TypedArray / DataView (los campos se leerán / escribirán directamente desde / hacia el búfer).
Quizás esto también podría usarse para la interoperabilidad nativa de NodeJS (con 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
    }

Gracias por tu comentario @zpodlovics. Debo decir que no estoy familiarizado con este tipo de serialización. ¿Estás hablando de JSON o serialización binaria? (He jugado un poco con la serialización binaria usando el protocolo flatbuffers de Google en un proyecto, pero involucró una gran cantidad de código repetitivo). ¿Puede dar un ejemplo de cómo se verían los datos serializados? Puede ser un poco difícil tener exactamente la misma representación tanto en .NET como en JS, ya que Fable elimina algunos datos de los tipos para optimizarlos en código JS y tiempo de ejecución. Además de las limitaciones en JS, como no poder definir tipos de valor (estructura).

Además, como se comentó anteriormente, preferiría mantener la serialización separada del código del núcleo del compilador. En Fable 2 probablemente mantendremos algún tipo de serialización JSON predeterminada, ya que ahora es para la conveniencia de los usuarios, pero las cosas más complicadas deberían hacerse preferiblemente en un paquete separado.

@alfonsogarciacaro ¡ Gracias por el comentario!

Bueno, no es realmente un formato de serialización, sino una representación de memoria directa como se representaría en una estructura C nativa. El formato "serializado" se vería exactamente como ac struct (nativo) [1]. Esto también es necesario para la interoperabilidad nativa, ya que utiliza la misma representación de memoria que la aplicación nativa. El ejemplo anterior utilizará una región de memoria de 3 * 4 = 12 bytes. Una estructura podría "emularse" como objeto con un método que reemplace la región de memoria de respaldo (.Wrap).

La estructura anterior podría traducirse a algo como este pseudocódigo usando la API JS DataView (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32):

p.ej.:

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)       

Sí, esto podría crearse manualmente, pero hacerlo manualmente llevaría mucho tiempo y sería propenso a errores. Especialmente si el compilador ya tiene toda la información necesaria: los tipos, el diseño de la memoria, las compensaciones de los campos, etc. Algún tipo de extensión del compilador plugin / api / ast también estaría perfectamente bien con codegen personalizado. Con el tiempo, todo el mundo se enfrentará al problema de los binarios: cómo leer / escribir archivos binarios, por ejemplo: imagen / audio / video / documento / paquetes de red / etc.

La representación de memoria sin procesar incorporada como formato de serialización para interoperabilidad (para admitir ffi) podría tener un valor increíble para todos. En cada plataforma, los desarrolladores tienen dos opciones para manipular la plataforma: 1) escribir el código de interoperabilidad como código nativo de la plataforma (compilador de plataforma, cadena de herramientas diferente, proceso de construcción / prueba / implementación diferente, etc.) 2) escribir código que represente la memoria sin procesar para la interoperabilidad + ffi. No es un accidente que .NET tenga soporte integrado para pinvoke.

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

¡Muchas gracias por la explicación más detallada @zpodlovics! Exploré el uso de vistas de datos para emular estructuras en el pasado, pero no hice mucho porque todavía hay limitaciones (como solo poder usar números, anidar estructuras es difícil, copiar una estructura de una matriz también es complicado, etc. ). Además, ya es posible controlar mejor la memoria con Fable utilizando matrices numéricas, ya que se traducirán a matrices con tipo, pero esto no ha tenido mucho atractivo entre los usuarios hasta ahora.

Probablemente deberíamos abrir un nuevo problema para esto ya que, como dijiste, no está exactamente relacionado con la serialización. No puedo considerarlo una prioridad en este momento, pero sería muy bueno ver contribuciones al respecto. La historia del complemento para Fable 2 aún está por determinar, pero podríamos hacer que permita algo como esto si está interesado en trabajar en dicho complemento.

Sin embargo, una cosa a considerar es que el soporte de WebAssembly está llegando a .NET / F # y la representación de la memoria en ese caso estará más cerca del modelo .NET. Cuando esto sucede, Fable puede permanecer como una forma de integrar F # más cerca del ecosistema JS (como lo es ahora) y aprovechar las herramientas / bibliotecas actuales disponibles para construir la interfaz de sus aplicaciones.

@alfonsogarciacaro ¿Podemos cerrar este tema?

¿Fue útil esta página
0 / 5 - 0 calificaciones