Fable: Sérialisation dans Fable 2

Créé le 2 mars 2018  ·  17Commentaires  ·  Source: fable-compiler/Fable

Poursuite de la discussion commencée ici https://github.com/SaturnFramework/Saturn/issues/33

Il y a aussi des commentaires intéressants sur la sérialisation implicite ou explicite dans ce fil Twitter .

Je n'étais probablement pas assez bon pour exprimer mes intentions dans le problème lié et (comme tout changement décisif), cela a déclenché une controverse. Je vais essayer d'exposer ma réflexion actuelle ci-dessous pour avoir une meilleure base pour la discussion :)

  • Fable 2 alpha serait initialement livré sans support de réflexion. Mon hypothèse est que cela affectera principalement ofJson/toJson car je pense qu'il n'y a pas beaucoup d'autres réflexions en ce moment dans Fable. Veuillez noter que la version alpha ne sera évidemment pas destinée à la production, mais aux utilisateurs d'essayer et de donner leur avis.

  • Pourquoi abandonner le support de réflexion ? Eh bien, à la fin, je réécris de grandes parties du code pour (espérons-le) le rendre plus propre, plus maintenable et plus attrayant pour les contributeurs. Lors de la refactorisation, j'ai remarqué que le modèle de réflexion dans Fable n'est pas cohérent et pollue beaucoup à la fois le code JS généré (la réduction de la taille des bundles est l'un des principaux objectifs de Fable 2) et la base de code Fable. C'est pourquoi j'aimerais _commencer à neuf_ avec Fable 2 alpha pour voir les besoins réels des utilisateurs et les réimplémenter à partir de zéro (ou pas, si nous n'en avons pas besoin).

  • Comment la réflexion serait-elle réimplémentée ? À l'heure actuelle, les informations de sérialisation sont intégrées dans les types. Cela rend les types plus gros et c'est un problème lorsque les gens comparent des alternatives dans le REPL car ils verront que Fable génère beaucoup plus de code pour les types simples (c'est déjà arrivé). Pour Fable 2, j'ai envisagé deux options :

    • Rendre les informations de réflexion disponibles via une méthode statique dans l'espoir qu'elles seront supprimées en secouant l'arbre lors de la construction pour la production. Ce serait probablement le moyen le plus simple, mais cela affichera toujours le code dans le REPL.
    • Générez des informations de réflexion dans le site d'appel pour remplacer typeof<Foo> par exemple. Je pense que ce sera une bonne chose dans la plupart des cas, mais cela peut pénaliser les applications qui utilisent beaucoup la réflexion, car il y aura probablement du code dupliqué.

      • J'ai également envisagé une troisième option : injecter un fichier supplémentaire avec les informations de réflexion afin qu'il reste caché à l'utilisateur et qu'il ne soit récupéré que si nécessaire. Mais la façon dont Fable interagit actuellement avec les bundles et outils JS (Webpack...) rend les choses compliquées.

  • Comment la sérialisation automatique pourrait-elle fonctionner dans Fable 2 ? Les enregistrements deviendront des objets JS simples et les unions, des tableaux JS. Donc, dans la plupart des cas, il suffit d'appeler le natif JSON.parse/stringify . Le problème serait des choses qui ne sont pas compatibles avec l'api JSON du navigateur, comme les cartes, les ensembles, les dates, les longs, etc... Donc Fable aura toujours besoin de connaître les informations sur les champs au moment de l'exécution afin de correctement les gonfler/dégonfler.

  • Ce que je n'aime pas dans la sérialisation actuelle ? Il y a quelques choses

    • Cela fonctionne dans la plupart des cas mais pas dans tous les cas, et cela peut vous donner des surprises à l'exécution, ce qui n'est pas quelque chose de bien si nous vendons un langage _sûr_.
    • C'est en quelque sorte _miroir_ Newtonsoft.Json sur le front-end, et certaines personnes s'attendent à ce qu'il prenne en charge des éléments tels que les attributs, l'intégration d'informations de type dans le json, etc.

      • Les autres langages que je connais (F# inclus) n'ont pas de sérialisation intégrée dans leur système principal. Cela augmente le coût de maintenance de la base de code de Fable et rend plus difficile sa refactorisation (comme cela s'est produit avec Fable 2). Cela me ferait très plaisir de déplacer la sérialisation vers une bibliothèque externe.

  • Quelles sont les alternatives ? Comme commenté dans le numéro ci-dessus, la principale alternative pour le moment qui devrait fonctionner immédiatement avec Fable 2 alpha est Thot.Json . Cette bibliothèque vous donne plus de contrôle sur le JSON généré et une bien meilleure validation. Le seul inconvénient est que vous devez écrire les décodeurs vous-même, mais il y a déjà du travail pour les générer automatiquement .

dev2.0 discussion

Commentaire le plus utile

L'une des raisons pour lesquelles j'aime Fable est son support JSON. Devoir ajouter une fonction d'encodeur/décodeur partout va être difficile à vendre. Je me souviens dans les toutes premières versions, les types F# étaient très proches des objets JS et la plupart du temps, vous ne pouviez que JSON.parse/stringify et sachant que cette limitation signifiait que je pouvais à peu près le faire fonctionner. Malheureusement, au fur et à mesure que Fable s'améliorait, j'ai commencé à utiliser des listes et des dates/heures dans mon JSON, donc s'ils partaient, ce serait un peu une réécriture de projet :S

Si la génération de code Thot.Json pouvait faire partie de la chaîne de construction pour le client et le serveur (dans net46x - oui je sais, un jour je devrai mettre à niveau), peut-être comme une sorte d'événement de pré-construction qui appelle faux (ce que je utiliser pour déployer une base de données SQL pour FSharp.Data.SqlClient) pourrait alors être réalisable ? Ou les tâches/cibles de construction de MS sont-elles toujours une chose... Comment le paquet est-il restauré automatiquement ?

_Je me suis brouillé avec Newtonsoft.Json il y a des années._

Tous les 17 commentaires

L'une des raisons pour lesquelles j'aime Fable est son support JSON. Devoir ajouter une fonction d'encodeur/décodeur partout va être difficile à vendre. Je me souviens dans les toutes premières versions, les types F# étaient très proches des objets JS et la plupart du temps, vous ne pouviez que JSON.parse/stringify et sachant que cette limitation signifiait que je pouvais à peu près le faire fonctionner. Malheureusement, au fur et à mesure que Fable s'améliorait, j'ai commencé à utiliser des listes et des dates/heures dans mon JSON, donc s'ils partaient, ce serait un peu une réécriture de projet :S

Si la génération de code Thot.Json pouvait faire partie de la chaîne de construction pour le client et le serveur (dans net46x - oui je sais, un jour je devrai mettre à niveau), peut-être comme une sorte d'événement de pré-construction qui appelle faux (ce que je utiliser pour déployer une base de données SQL pour FSharp.Data.SqlClient) pourrait alors être réalisable ? Ou les tâches/cibles de construction de MS sont-elles toujours une chose... Comment le paquet est-il restauré automatiquement ?

_Je me suis brouillé avec Newtonsoft.Json il y a des années._

Juste pour générer un contre-point, j'utilise actuellement la réflexion dans une application de nœud de production Fable 1 à la fois pour désérialiser les types de messages dans un DU :

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

et pour déterminer automatiquement les types d'encodage des flux de nœuds afin que je puisse composer des transformations ensemble et les faire vérifier/configurer comme prévu au moment de l'exécution :

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

Les consommateurs de ce service doivent envoyer un message au démon pour obtenir des données en streaming : https://github.com/intel-hpdd/device-scanner/tree/master/IML.DeviceScannerDaemon (donc en gardant un enregistrement/une chaîne à la place d'un tableau est idéal) mais je pense que je peux simplement faire correspondre les chaînes avant d'essayer de sérialiser pour contourner cela.

Le plus gros problème pour moi est de perdre la possibilité de configurer automatiquement les flux de nœuds en fonction du manque d'informations de réflexion au moment de l'exécution, mais je peux peut-être également contourner ce problème.

@davidtme J'explore un moyen d'intégrer la génération de décodeurs/encodeurs Thot.Json dans la chaîne de construction ou via le support TP, etc.

J'ouvrirai un numéro sur Thot.Json pour suivre mes futurs progrès sur ce sujet.

Pour info, je viens de publier Thot.Json.Net qui fournit la même API que Thot.Json pour Fable.

L'idée étant que vous pouvez utiliser la directive du compilateur pour partager le code :

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

Documentation

@alfonsogarciacaro Serait-il possible de détecter les informations de type au moment de la compilation et d'adapter le processus serialization en fonction de cela ?

Les informations sur le type sont disponibles dans le type de compilation, oui. Bien que si nous voulons que _both_ accède aux informations de type sans réflexion et déplace la sérialisation en dehors du compilateur, nous aurions besoin d'une sorte de plugin (qui sera également indisponible dans Fable 2 alpha :wink:). Mais qu'entendez-vous exactement par « adapter le processus de sérialisation » ?

TBH, je trouve étrange d'exclure des fonctionnalités juste pour rendre le javascript généré plus attrayant pour les personnes évaluant Fable. Je sais que c'est la devise de Fable de générer un joli javascript et j'aime vraiment ça, mais l'OMI, l'argument de vente le plus fort de Fable en tant que compilateur de javascript, utilise F #, l'interopérabilité impressionnante avec l'écosystème JS et applicable à n'importe quel javascript Durée. Javascript généré Nice est juste cela: agréable à avoir. (Que diriez-vous de faire un sondage pour demander aux utilisateurs ?)

cela fonctionne dans la plupart des cas mais pas dans tous les cas, et cela peut vous donner des surprises au moment de l'exécution, ce qui n'est pas quelque chose de bien si nous vendons un langage sûr.

Ensuite, nous mettons en œuvre les cas exceptionnels défaillants. Les échecs de sérialisation/désérialisation automatique semblent se produire dans des endroits où nous n'avons pas suffisamment de métadonnées au moment de l'exécution.

Il reflète en quelque sorte Newtonsoft.Json sur le front-end, et certaines personnes s'attendent à ce qu'il prenne en charge des éléments tels que les attributs, l'intégration d'informations de type dans le json, etc.

Je dirais qu'il offre la même facilité que Newtonsoft.Json et que les utilisateurs sont déjà très satisfaits de son utilisation par défaut. Si les gens veulent plus de contrôle et de personnalisation, alors Thot.Json ou Fable.SimpleJson fournissent le niveau de contrôle nécessaire.

Les autres langages que je connais (F# inclus) n'ont pas de sérialisation intégrée dans leur système principal. Cela augmente le coût de maintenance de la base de code de Fable et rend plus difficile sa refactorisation (comme cela s'est produit avec Fable 2). Cela me ferait très plaisir de déplacer la sérialisation vers une bibliothèque externe.

Je suis d'accord pour dire que les coûts de maintenance sont augmentés de ofJson<'a> et 'toJson' mais cela en vaut vraiment la peine. Si nous devions faire une bibliothèque externe alors la réflexion doit être bien mise en œuvre pour que les consommateurs puissent écrire de tels convertisseurs

@Zaid-Ajaj De mon point de vue, il ne s'agit pas seulement de rendre le JavaScript généré agréable, mais également de réduire la taille du paquet.

Nous voyons beaucoup de gens s'en plaindre surtout dans les pays où la connexion internet est encore lente :)

@alfonsogarciacaro Je pensais faire en sorte que Fable personnalise l'appel du sérialiseur pour prendre en charge les cartes, les ensembles, etc. en utilisant le nom de la propriété comme clé, mais c'est une mauvaise idée. Cela signifierait beaucoup de duplication de code et chaque appel de sérialiseur ne serait pas le même. Oublions cette idée :)

@alfonsogarciacaro Serait-il possible d'intégrer les informations sur les types dans un "module de type".

Le module pourrait contenir toutes les informations de type de l'application et s'il n'est pas utilisé, Webpack devrait pouvoir le supprimer non ?

Merci beaucoup pour vos commentaires. Je comprends que les ofJson/toJson helpers actuels sont très pratiques et qu'ils ne devraient pas être remplacés à moins que nous ne fournissions quelque chose qui soit presque aussi facile à utiliser. Merci aussi pour les exemples @jgrund , c'est très utile pour voir comment Fable est utilisé en production. Pour ce que je peux voir, la réflexion n'est utilisée que pour ofJson , existe-t-il un autre endroit où vous utilisez typeof<'T> ou similaire ?

Merci aussi pour vos idées @Zaid-Ajaj. Comme le dit Maxime, il ne s'agit pas seulement de rendre le code plus lisible (en fait, à certains endroits, il peut devenir _moins_ lisible mais plus optimisé dans Fable 2), mais aussi de réduire la taille des bundles et de rendre le code généré mieux adapté aux outils d'optimisation JS. C'est aussi une question de survie car maintenant que nous avons des bibliothèques plus matures et plus complexes comme Fulma, si les tailles des bundles sont trop grandes, les utilisateurs peuvent envisager d'autres options (et nous savons que la concurrence est rude entre les langages fonctionnels compilant vers JS). Fable essaie de compiler le langage F# avec _la plupart_ de ses fonctionnalités et FSharp.Core et _quelques_ de la BCL et décider si la réflexion est une fonctionnalité du langage F# ou une partie du runtime BCL/.NET est un autre sujet. Bien sûr, c'est quelque chose de très utile, mais j'aimerais utiliser la version alpha de Fable 2 pour évaluer ses coûts/bénéfices et si nous avons des alternatives viables.

@MangelMaxime Oui, c'est la troisième option que Types peut devenir dans un état incohérent. Mais je ne suis pas sûr, je suppose que nous pouvons essayer.

Encore une fois, merci à tous pour vos commentaires, ils sont vraiment utiles et assurez-vous que c'est la moindre de mes intentions de faire quoi que ce soit qui puisse blesser les utilisateurs actuels de Fable. Je vais essayer de sortir une version alpha de Fable 2 pour que ce soit plus facile de comparer les alternatives et leurs effets :+1:

Ma raison la plus importante pour choisir Fable plutôt que Elm était de pouvoir utiliser F# des deux côtés et de réutiliser les types à travers les niveaux. Je n'ai pas besoin de la réflexion en soi, mais comme l' a dit

La sérialisation extraite dans un plugin de compilateur serait un excellent compromis, si vous pouviez le retirer ;)

existe-t-il un autre endroit où vous utilisez typeof<'T> ou similaire ?

Désolé, je n'ai pas mis en évidence la section pertinente. https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L144

En faisant cela, je suis capable de définir un encodage de flux et l'état du côté lisible uniquement à partir des informations de temps de compilation, ce qui m'évite de configurer chaque flux manuellement avec les informations déjà encodées par les types.

Sur cette base, je peux faire des choses comme composer des flux ensemble sans me soucier des options de codage en dur au préalable, ce qui est plutôt sympa : https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/test/ Stream.Test.fs#L221 -L232

Une autre utilisation intelligente de la réflexion ici :
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L70

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

L'idée entière du projet Fable.Remoting est basée sur la disponibilité de la réflexion à la fois au niveau du serveur et du client.

Probablement pas une demande populaire - à moins que la communication ne soit sensible à la latence ou aux performances - mais qu'en est-il de la prise en charge des types de valeur brute pour le transport (avec ou sans prise en charge de la copie zéro) ? Par exemple, prendre en charge les types de valeur .NET emballés et non gérés (« blittable ») ? Par définition, il a la même représentation partout à la fois en natif et en .NET. Cette représentation brute peut être critique, car parfois la sérialisation/désérialisation de json est trop gourmande en CPU/mémoire/gc/latence sur le serveur (en particulier avec beaucoup de clients et/ou un taux de messages élevé). La copie zéro sur le client peut également être prise en charge en utilisant un support ArrayView/TypedArray/DataView (les champs seront lus/écrits directement depuis/dans le tampon).
Peut-être que cela pourrait également être utilisé pour l'interopérabilité native NodeJS (avec 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
    }

Merci pour votre commentaire @zpodlovics. Je dois dire que je ne suis pas familier avec ce genre de sérialisation. Parlez-vous de JSON ou de sérialisation binaire ? (J'ai un peu joué avec la sérialisation binaire à l'aide du protocole Google flatbuffers dans un projet, mais cela impliquait beaucoup de code passe-partout.) Pouvez-vous donner un exemple de l'apparence des données sérialisées ? Il peut être un peu difficile d'avoir exactement la même représentation à la fois dans .NET et JS, car Fable supprime certaines données des types pour les optimiser dans le code JS et l'exécution. Outre les limitations de JS, comme l'impossibilité de définir des types de valeur (struct).

De plus, comme indiqué ci-dessus, je préférerais que la sérialisation reste séparée du code principal du compilateur. Dans Fable 2, nous conserverons probablement une sorte de sérialisation JSON par défaut, comme c'est maintenant le cas pour la commodité des utilisateurs, mais les choses plus compliquées devraient de préférence être effectuées dans un package séparé.

@alfonsogarciacaro Merci pour le commentaire !

Eh bien, ce n'est pas vraiment un format de sérialisation, mais une représentation directe de la mémoire telle qu'elle serait représentée dans une structure C native. Le format "sérialisé" ressemblerait exactement à ac struct (natif) [1]. Cela est également requis pour l'interopérabilité native, car elle utilise la même représentation de la mémoire que l'application native. L'exemple précédent utilisera 3 * 4 = région de mémoire de 12 octets. Un Struct peut être "émulé" en tant qu'objet avec une méthode qui remplace la région de mémoire de sauvegarde (.Wrap).

La structure précédente pouvait être traduite en quelque chose comme ce pseudocode à l'aide de l'API JS DataView (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32) :

par exemple.:

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)       

Oui, cela pourrait être créé manuellement, mais le faire manuellement prendrait du temps et serait source d'erreurs. Surtout si le compilateur a déjà toutes les informations nécessaires : les types, la disposition de la mémoire, les décalages de champs, etc. Une sorte d'extension de compilateur plugin/api/ast conviendrait également parfaitement avec un codegen créé sur mesure. Finalement, tout le monde sera confronté au problème binaire - comment lire/écrire des binaires, par exemple : image/audio/video/document/network packets/etc.

La représentation de la mémoire brute intégrée en tant que format de sérialisation pour l'interopérabilité (pour prendre en charge ffi) pourrait avoir une valeur incroyable pour tout le monde. Sur chaque plate-forme, les développeurs ont deux options pour manipuler la plate-forme : 1) écrire le code d'interopérabilité en tant que code natif de la plate-forme (compilateur de plate-forme, chaîne d'outils différente, processus de construction/test/déploiement différent, etc.) 2) écrire du code qui représente la mémoire brute pour l'interopérabilité + ff. Ce n'est pas un hasard si le .NET prend en charge le pinvoke intégré.

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

Merci beaucoup pour l'explication plus détaillée @zpodlovics ! J'ai exploré l'utilisation de vues de données pour émuler des structures dans le passé, mais je n'ai pas fait grand-chose car il y a toujours des limitations (comme ne pouvoir utiliser que des nombres, l'imbrication de structures est difficile, la copie d'une structure à partir d'un tableau est également délicate, etc. ). De plus, il est déjà possible de mieux contrôler la mémoire avec Fable en utilisant des tableaux numériques, car ils seront traduits en tableaux typés, mais cela n'a pas eu beaucoup d'attrait pour les utilisateurs jusqu'à présent.

Nous devrions probablement ouvrir un nouveau problème pour cela car, comme vous l'avez dit, ce n'est pas exactement lié à la sérialisation. Je ne peux pas considérer cela comme une priorité pour le moment, mais ce serait très bien de voir des contributions à cet égard. L'histoire du plugin pour Fable 2 est encore à déterminer, mais nous pourrions faire en sorte qu'il permette quelque chose comme ça si vous êtes intéressé à travailler avec un tel plugin.

Une chose à considérer cependant est que la prise en charge de WebAssembly arrive sur .NET/F# et la représentation de la mémoire dans ce cas sera plus proche du modèle .NET. Lorsque cela se produit, Fable peut rester un moyen d'intégrer F# plus près de l'écosystème JS (comme c'est le cas actuellement) et de tirer parti des outils / bibliothèques actuels disponibles pour créer le frontend de vos applications.

@alfonsogarciacaro Pouvons-nous clore ce problème ?

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

forki picture forki  ·  3Commentaires

MangelMaxime picture MangelMaxime  ·  3Commentaires

ncave picture ncave  ·  3Commentaires

MangelMaxime picture MangelMaxime  ·  3Commentaires

alfonsogarciacaro picture alfonsogarciacaro  ·  3Commentaires