Fable: Serializando na Fábula 2

Criado em 2 mar. 2018  ·  17Comentários  ·  Fonte: fable-compiler/Fable

Continuação da discussão iniciada aqui https://github.com/SaturnFramework/Saturn/issues/33

Há também alguns comentários interessantes sobre serialização implícita versus serialização explícita neste tópico do Twitter .

Provavelmente, não fui bom o suficiente para expressar minhas intenções no assunto relacionado e (como qualquer alteração importante) isso deu início a alguma controvérsia. Vou tentar expor meu pensamento atual abaixo para ter uma base melhor para a discussão :)

  • O Fable 2 alpha seria lançado inicialmente sem suporte de reflexão. Minha suposição é que isso afetará principalmente ofJson/toJson pois acho que não há muitos outros reflexos agora no Fable. Observe que a versão alfa não se destina, obviamente, à produção, mas para os usuários experimentarem e fornecerem feedback.

  • Por que descartar o suporte de reflexão? Bem, no final, estou reescrevendo grandes partes do código para (espero) torná-lo mais limpo, mais fácil de manter e atraente para os contribuidores. Na refatoração, notei que o modelo de reflexão no Fable não é consistente e polui muito tanto o código JS gerado (reduzir o tamanho do pacote é um dos principais objetivos do Fable 2) quanto a base do código Fable. É por isso que eu gostaria de _ começar do zero_ com o Fable 2 alpha para ver as necessidades reais dos usuários e reimplementá-lo do zero (ou não, se não precisarmos).

  • Como a reflexão seria reimplementada? No momento, as informações de serialização estão incorporadas aos tipos. Isso faz com que os tipos pareçam mais gordos e é um problema quando as pessoas estão comparando alternativas no REPL, pois verão que o Fable gera muito mais código para tipos simples (isso já aconteceu). Para Fable 2, considerei duas opções:

    • Disponibilize informações de reflexão por meio de um método estático na esperança de que sejam removidas com o movimento da árvore durante a construção para a produção. Esta provavelmente seria a maneira mais fácil, mas ainda exibirá o código no REPL.
    • Gere informações de reflexão no site da chamada para substituir typeof<Foo> por exemplo. Acho que isso será bom para a maioria dos casos, mas pode penalizar aplicativos que usam reflexão extensivamente, pois provavelmente haverá código duplicado.

      • Também considerei uma terceira opção: injetar um arquivo extra com as informações de reflexão para que ele permaneça oculto para o usuário e seja recuperado apenas se necessário. Mas a maneira como o Fable interage agora com empacotadores e ferramentas JS (Webpack ...) torna isso complicado.

  • Como a serialização automática pode funcionar no Fable 2? Os registros se tornarão objetos JS simples e uniões, arrays JS. Portanto, na maioria dos casos, apenas chamar o nativo JSON.parse/stringify funcionaria. O problema seria coisas que não são compatíveis com o navegador JSON api, como mapas, conjuntos, datas, longos, etc ... Portanto, o Fable ainda precisará saber as informações sobre os campos em tempo de execução para poder infle / desinfle-os.

  • O que eu não gosto na serialização atual? Existem algumas coisas

    • Funciona na maioria dos casos, mas não em todos , e pode lhe dar surpresas em tempo de execução, o que não é bom se estivermos vendendo uma linguagem _segura_.
    • De alguma forma, ele _mirror_ Newtonsoft.Json no frontend, e algumas pessoas esperam que ele suporte coisas como atributos, incorporação de informações de tipo no json, etc.

      • Outras linguagens que conheço (F # incluído) não têm serialização embutida em seu sistema central. Isso aumenta o custo de manutenção da base de código do Fable e torna mais difícil refatorá-lo (como aconteceu com o Fable 2). Eu ficaria muito feliz em mover a serialização para uma biblioteca externa.

  • Quais são as alternativas? Como comentado na questão acima, a principal alternativa no momento que deveria funcionar com o Fable 2 alpha de imediato é Thot.Json . Essa biblioteca oferece mais controle sobre o JSON gerado e uma validação muito melhor. A única desvantagem é que você mesmo precisa escrever os decodificadores, mas já há trabalho para gerá-los automaticamente .

dev2.0 discussion

Comentários muito úteis

Uma das razões pelas quais gosto do Fable é por causa de seu suporte JSON. Ter que ir e adicionar função de codificador / decodificador em todo o lugar vai ser difícil de vender. Lembro-me de uma versão muito, muito inicial, os tipos F # eram muito próximos de objetos JS e na maioria das vezes você poderia apenas JSON.parse/stringify e saber essa limitação significava que eu poderia quase fazê-lo funcionar. Infelizmente, conforme o Fable ficou melhor, comecei a usar Lists and DateTimes no meu JSON, então, se eles fossem, seria uma espécie de reescrita do projeto: S

Se a geração de código do Thot.Json pudesse fazer parte da cadeia de construção para cliente e servidor (em net46x - sim, eu sei, um dia terei de atualizar), talvez como algum tipo de evento de pré-construção que chama de falso (que eu usar para implantar um banco de dados sql para FSharp.Data.SqlClient), então poderia ser viável? Ou as tarefas / metas de compilação do MS ainda são uma coisa ... Como o paket é restaurado automaticamente?

_Desculpei com Newtonsoft.Json anos atrás._

Todos 17 comentários

Uma das razões pelas quais gosto do Fable é por causa de seu suporte JSON. Ter que ir e adicionar função de codificador / decodificador em todo o lugar vai ser difícil de vender. Lembro-me de uma versão muito, muito inicial, os tipos F # eram muito próximos de objetos JS e na maioria das vezes você poderia apenas JSON.parse/stringify e saber essa limitação significava que eu poderia quase fazê-lo funcionar. Infelizmente, conforme o Fable ficou melhor, comecei a usar Lists and DateTimes no meu JSON, então, se eles fossem, seria uma espécie de reescrita do projeto: S

Se a geração de código do Thot.Json pudesse fazer parte da cadeia de construção para cliente e servidor (em net46x - sim, eu sei, um dia terei de atualizar), talvez como algum tipo de evento de pré-construção que chama de falso (que eu usar para implantar um banco de dados sql para FSharp.Data.SqlClient), então poderia ser viável? Ou as tarefas / metas de compilação do MS ainda são uma coisa ... Como o paket é restaurado automaticamente?

_Desculpei com Newtonsoft.Json anos atrás._

Apenas para gerar um contraponto, estou usando atualmente reflexão em um aplicativo de nó Fable 1 de produção para desserializar tipos de mensagem em um DU:

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

e para autodeterminar os tipos de codificação de fluxos de nó para que eu possa compor transformações juntos e tê-los verificado / configurado como esperado no tempo de execução:

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

Espera-se que os consumidores deste serviço enviem uma mensagem ao daemon para obter os dados transmitidos: https://github.com/intel-hpdd/device-scanner/tree/master/IML.DeviceScannerDaemon (mantendo um registro / string em vez de uma matriz é o ideal), mas acho que posso simplesmente corresponder string antes de tentar serializar para contornar isso.

O maior problema para mim é perder a capacidade de autoconfigurar fluxos de nós com base na falta de informações de reflexão no tempo de execução, mas também posso contornar isso.

@davidtme Estou explorando uma maneira de integrar a geração de decodificadores / codificadores Thot.Json na cadeia de construção ou via suporte TP etc.

Vou abrir uma edição no Thot.Json para rastrear meu progresso futuro neste assunto.

Para sua informação, acabei de publicar Thot.Json.Net, que fornece a mesma API que Thot.Json para Fable.

A ideia é que você pode usar a diretiva do compilador para compartilhar o 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

Documentação

@alfonsogarciacaro Seria possível detectar o tipo de informação em tempo de compilação e adaptar o processo serialization dependendo disso?

A informação do tipo está disponível no tipo de compilação, sim. Embora se quisermos que _both_ acesse informações de tipo sem reflexão e mova a serialização para fora do compilador, precisaríamos de algum tipo de plugin (que também não estará disponível no Fable 2 alpha: wink :). Mas o que você quer dizer exatamente com "adaptar o processo de serialização"?

TBH, acho estranho excluir funcionalidade apenas para tornar o javascript gerado mais atraente para as pessoas que estão avaliando o Fable. Eu sei que o lema do Fable é gerar um bom javascript e eu realmente gosto disso, mas IMO, o ponto de venda mais forte que o Fable tem como compilador para javascript, está usando F #, a incrível interoperabilidade com o ecossistema JS e sendo aplicável a qualquer javascript tempo de execução. Um bom javascript gerado é apenas isso: bom ter. (Que tal fazer uma votação para perguntar aos usuários?)

funciona na maioria dos casos, mas não em todos, e pode lhe dar surpresas em tempo de execução, o que não é bom se estivermos vendendo uma linguagem segura.

Em seguida, implementamos os casos excepcionais com falha. As falhas de serialização / desserialização automática parecem ocorrer em lugares onde não temos metadados suficientes em tempo de execução.

De alguma forma, ele espelha o Newtonsoft.Json no frontend, e algumas pessoas esperam que ele suporte coisas como atributos, incorporação de informações de tipo no json, etc.

Eu diria que fornece a mesma facilidade do Newtonsoft.Json e os usuários já estão muito felizes com ele como padrão. Se as pessoas desejam mais controle e personalizações, Thot.Json ou Fable.SimpleJson fornecem o nível de controle necessário.

Outras linguagens que conheço (F # incluído) não têm serialização embutida em seu sistema central. Isso aumenta o custo de manutenção da base de código do Fable e torna mais difícil refatorá-lo (como aconteceu com o Fable 2). Eu ficaria muito feliz em mover a serialização para uma biblioteca externa.

Eu concordo que os custos de manutenção aumentaram em ofJson<'a> e 'toJson', mas realmente vale a pena. Se fôssemos fazer uma biblioteca externa, a reflexão deve ser bem implementada para que os consumidores possam escrever tais conversores

@ Zaid-Ajaj Do meu ponto de vista, não é apenas para tornar o JavaScript gerado bonito, mas também para reduzir o tamanho do pacote.

Vemos muitas pessoas reclamando disso, especialmente em países onde a conexão à Internet ainda é lenta :)

@alfonsogarciacaro Eu estava

@alfonsogarciacaro Seria possível embutir as informações de tipos em um "módulo de tipos".

O módulo pode conter todas as informações de tipo do aplicativo e se não for usado, o Webpack deve ser capaz de removê-lo, não?

Muito obrigado por seus comentários. Eu entendo que os atuais ofJson/toJson ajudantes são muito convenientes e não devem ser substituídos, a menos que forneçamos algo que seja quase tão fácil de usar. Obrigado também pelos exemplos @jgrund , é muito útil ver como o Fable é usado na produção. Pelo que posso ver, a reflexão só é usada para ofJson , há algum outro lugar onde você usa typeof<'T> ou similar?

Obrigado também por seus insights @ Zaid-Ajaj. Como Maxime diz, não é apenas uma questão de tornar o código mais legível (na verdade, em alguns lugares ele pode se tornar _less_ legível, mas mais otimizado no Fable 2), mas reduzir o tamanho dos pacotes e tornar o código gerado um melhor ajuste para ferramentas de otimização JS. É também uma questão de sobrevivência, pois agora que temos bibliotecas mais maduras e complexas como Fulma, se os tamanhos dos pacotes forem muito grandes, os usuários podem considerar outras opções (e sabemos que a competição é acirrada entre as linguagens funcionais que compilam para JS). Fable tenta compilar a linguagem F # com _maioria_ de seus recursos e FSharp.Core e _algumas_ do BCL e decidir se a reflexão é um recurso da linguagem F # ou parte do tempo de execução BCL / .NET é outro tópico. Claro, é algo muito útil, mas eu gostaria de usar a versão alfa do Fable 2 para avaliar seus custos / benefícios e se temos alternativas viáveis.

@MangelMaxime Sim, essa é a terceira opção que eu estava considerando acima. Tenho algumas dúvidas com relação às compilações de relógios, porque este módulo Types pode ficar em um estado inconsistente. Mas não tenho certeza, acho que podemos tentar.

Mais uma vez, obrigado a todos por seus comentários, eles são realmente úteis e, por favor, tenha certeza de que é a minha menor intenção fazer qualquer coisa que possa prejudicar os usuários atuais do Fable. Vou tentar lançar uma versão alfa do Fable 2 para que seja mais fácil comparar as alternativas e seus efeitos: +1:

Meu único motivo mais importante para escolher Fable em vez de Elm foi poder usar F # em ambos os lados e reutilizar os tipos nas camadas. Eu não preciso da reflexão em si, mas como @davidtme disse, a implementação anterior "simplesmente funcionou". Eu ficaria confortável em desistir de alguns dos aspectos do sistema de tipos para o propósito de serialização JSON, mas isso é verdade para praticamente qualquer formato de serialização de plataforma cruzada e estou bem com isso.

A serialização extraída em um plug-in do compilador seria um grande compromisso, se você pudesse fazê-lo;)

existe algum outro lugar onde você usa typeof <'T> ou similar?

Desculpe, não destacou a seção relevante. https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L144

Ao fazer isso, sou capaz de definir uma codificação de streams e o estado do lado legível apenas a partir das informações de tempo de compilação, o que me poupa de configurar cada stream manualmente com as informações que já estão codificadas pelos tipos.

Com base nisso, posso fazer coisas como compor streams juntos sem me preocupar com opções de hardcoding de antemão, o que é muito bom: https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/test/ Stream.Test.fs # L221 -L232

Outro uso inteligente de reflexão aqui:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L70

e aqui:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L169

A ideia de projeto Fable.Remoting inteira é baseada na disponibilidade de reflexão tanto no servidor quanto no cliente.

Provavelmente não é uma solicitação popular - a menos que a comunicação seja sensível à latência ou ao desempenho - mas que tal oferecer suporte a tipos de valor bruto para transporte (com ou sem suporte de cópia zero)? Por exemplo, suporte a tipos de valor compactados e não gerenciados ("blittable") .NET? Por definição, ele tem a mesma representação em todos os lugares, tanto no nativo quanto no .NET. Esta representação bruta pode ser crítica, já que às vezes a serialização / desserialização json é muito intensiva em CPU / memória / gc / latência no servidor (especialmente com muitos clientes e / ou alta taxa de mensagens). A cópia zero no cliente também pode ser suportada usando um suporte ArrayView / TypedArray / DataView (os campos serão lidos / gravados diretamente de / para o buffer).
Talvez isso também possa ser usado para interoperabilidade nativa NodeJS (com 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
    }

Obrigado pelo seu comentário @zpodlovics. Devo dizer que não estou familiarizado com esse tipo de serialização. Você está falando sobre JSON ou serialização binária? (Eu brinquei um pouco com a serialização binária usando o protocolo flatbuffers do Google em um projeto, mas envolvia muito código clichê.) Você pode dar um exemplo de como os dados serializados seriam? Pode ser um pouco difícil ter a mesma representação exata em .NET e JS, pois o Fable remove alguns dados dos tipos para otimizá-los no código JS e no tempo de execução. Além das limitações em JS, como não ser capaz de definir tipos de valor (struct).

Além disso, conforme comentado acima, prefiro manter a serialização separada do código do núcleo do compilador. No Fable 2, provavelmente manteremos algum tipo de serialização JSON padrão, pois agora é para a conveniência dos usuários, mas coisas mais complicadas devem ser feitas preferencialmente em um pacote separado.

@alfonsogarciacaro Obrigado pelo comentário!

Bem, não é realmente um formato de serialização, mas uma representação de memória direta, uma vez que seria representado em uma estrutura C nativa. O formato "serializado" seria exatamente semelhante a uma estrutura (nativo) [1]. Isso também é necessário para interoperabilidade nativa, pois usa a mesma representação de memória do aplicativo nativo. O exemplo anterior usará a região de memória 3 * 4 = 12 bytes. Um Struct pode ser "emulado" como objeto com um método que substitui a região de memória de apoio (.Wrap).

A estrutura anterior pode ser traduzida para algo como este pseudocódigo usando a API JS DataView (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32):

por exemplo.:

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)       

Sim, isso poderia ser criado manualmente, mas fazê-lo manualmente seria demorado e sujeito a erros. Especialmente se eu, o compilador já tiver todas as informações necessárias: os tipos, o layout de memória, os deslocamentos de campos, etc. Algum tipo de plugin de extensão do compilador / api / ast também seria perfeitamente adequado com codegen personalizado criado. Eventualmente, todos enfrentarão o problema binário - como ler / escrever binários, por exemplo: imagem / áudio / vídeo / documento / pacotes de rede / etc.

A representação de memória bruta integrada como formato de serialização para interoperabilidade (para oferecer suporte a ffi) pode ter um valor incrível para todos. Em cada plataforma, os desenvolvedores têm duas opções para manipular a plataforma: 1) escrever o código de interoperabilidade como código nativo da plataforma (compilador de plataforma, conjunto de ferramentas diferente, processo de construção / teste / implantação diferente, etc) 2) escrever código que representa a memória bruta para interoperabilidade + ffi. Não é por acaso que o .NET tem suporte a pinvoke embutido.

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

Muito obrigado pela explicação mais detalhada @zpodlovics! Eu explorei o uso de visualizações de dados para emular structs no passado, mas não fiz muito porque ainda existem limitações (como apenas ser capaz de usar números, aninhar structs é difícil, copiar um struct de um array também é complicado, etc. ) Além disso, já é possível controlar melhor a memória com o Fable usando arrays numéricos, pois eles serão traduzidos para Typed Arrays, mas isso não teve muito apelo entre os usuários até agora.

Provavelmente deveríamos abrir um novo problema para isso, pois, como você disse, não está exatamente relacionado à serialização. Não posso considerar isso uma prioridade no momento, mas seria muito bom ver contribuições nesse sentido. A história do plug-in para Fable 2 ainda está definida, mas poderíamos torná-la de modo que permita algo assim se você estiver interessado em trabalhar com esse plug-in.

No entanto, uma coisa a se considerar é que o suporte WebAssembly está chegando ao .NET / F # e a representação da memória, nesse caso, será mais próxima do modelo .NET. Quando isso acontece, o Fable pode permanecer como uma forma de integrar o F # mais perto do ecossistema JS (como está agora) e tirar proveito das ferramentas / libs atuais disponíveis para construir o front-end de seus aplicativos.

@alfonsogarciacaro Podemos encerrar esta edição?

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

ncave picture ncave  ·  3Comentários

MangelMaxime picture MangelMaxime  ·  3Comentários

forki picture forki  ·  3Comentários

krauthaufen picture krauthaufen  ·  3Comentários

forki picture forki  ·  3Comentários