Fable: Prise en charge de Python pour Fable

Créé le 4 janv. 2021  ·  54Commentaires  ·  Source: fable-compiler/Fable

La description

Ce numéro est une discussion sur la prise en charge de Python pour Fable. Le Babel AST actuellement utilisé par Fable est suffisamment proche pour générer du code Python. Surtout maintenant que les classes sont prises en charge.

Un POC a été réalisé pour montrer que cela est possible. Cependant, beaucoup de travail est nécessaire pour :

  1. Ajouter un support décent pour Python
  2. Rendez Fable plus agnostique JS et prenez en charge plusieurs langues cibles.

La bibliothèque Expression permet une programmation fonctionnelle en Python inspirée de F#. Il fournit sa propre implémentation de types de données tels que option , result , seq , map , list (appelé FrozenList), boîte aux lettres processeur, ... Ainsi, il devrait être adapté comme l'équivalent Python de la bibliothèque Fable. En Python, vous utilisez Expression pour faire du code F#. Dans Fable, vous générez du code Python à l'aide d'Expression.

Cas d'utilisation :

  • F# s'exécutant dans Jupyter notebook avec le noyau Python, c'est-à-dire similaire à ce qui est fait par exemple avec Hy et Calysto Hy .
  • Script F# utilisant Python pour une friction réduite
  • Prend en charge plus facilement les environnements embarqués tels que micro:bit et Rasberry PI .
  • Partage du modèle de données entre le code F# et Python

Points à discuter et à décider :

  • Python pour F# devrait-il être plus Pythonic que .NET ? Il existe déjà un support .NET pour Jupyter que nous ne devrions pas essayer de remplacer ou de concurrencer. Fable pour Python devrait peut-être cibler Python au lieu de .NET (si possible) et être plus naturel pour les développeurs Python qui recherchent F#.
  • Devrions-nous essayer d'être compatible avec Babel AST ou devrions-nous diverger dans notre propre Python AST. C'est peut-être inévitable, mais Babel nous donne un bon point de départ.
  • Devrions-nous utiliser Fable (comme avec Peeble ) ou devrions-nous rester dans Fable et faire en sorte que Fable prenne en charge plusieurs langues. Avoir notre propre repo donne de la liberté et de la vitesse (en n'ayant pas besoin de se soucier de JS), mais nous risquons d'être laissés pour compte et obsolètes avec le temps. Mon sentiment est que nous devrions faire partie de Fable.
  • Les types de base Python sont également différents de F#. Par exemple, int a une longueur arbitraire. Devrions-nous émuler les types .NET (machine) pour être compatibles ou devrions-nous exposer le type Python int tant que F# int ?

Installation du POC

Le code source du POC réside actuellement ici :

  1. Installez la dernière version de Python 3.9 depuis https://www.python.org ou brew install [email protected] sur Mac. Notez que python peut être disponible sur votre système en tant que python , python3 ou les deux.
  2. Clonez à la fois le dépôt et basculez vers la branche python .
  3. Construire une bibliothèque de fables pour Python : dotnet fsi build.fsx library-py
  4. Pour exécuter des tests : dotnet fsi build.fsx test-py
  5. Dans Fable, modifiez QuickTest.fs en un code F# très simple. Ensuite, exécutez dotnet fsi build.fsx quicktest partir du répertoire supérieur.

Vous pouvez maintenant éditer à la fois le code Fable et Expression et l'exemple de code sera recompilé si nécessaire. Notez que vous avez besoin d'une sauvegarde supplémentaire de QuickTest.fs pour déclencher la recompilation après avoir modifié le code Fable. Le fichier python sera disponible en tant que quicktest.py . Une belle démo consiste à afficher à la fois F#, JS et Python dans vscode. Ensuite, vous verrez comment le code est transformé lorsque vous enregistrez le fichier F# :

Screenshot 2021-01-15 at 13 18 09

Vous pouvez exécuter le code Python généré dans le terminal :

$ python3 quicktest.py

Liens

Quelques liens pertinents :

N'hésitez pas à contribuer avec des discussions, des commentaires, des idées et du code 😍

Commentaire le plus utile

C'est du bon travail @dbrattli ! 👏 👏 👏 Mon idée originale avec Fable était de fournir un moyen de compiler facilement F# dans d'autres langages. Pour le meilleur ou pour le pire, Fable a évolué avec JS comme objectif principal, il faudrait donc un peu de travail pour le rendre plus indépendant du langage, mais je serais ouvert à cela. Quelques commentaires à vos questions :

  • Repos identique ou différent ? Je serais en faveur de garder autant de code commun que possible. L'utilisation de différents dépôts les fera probablement diverger assez rapidement (il est déjà difficile de synchroniser la branche avec le support des citations), mais nous devons également rendre le code suffisamment modulaire pour ne pas avoir de conditions partout en fonction de la langue cible. Une solution intermédiaire serait d'essayer d'extraire le plus de code possible vers une Fable "Nucleus" (on ne peut pas utiliser Core 😉) et ensuite d'avoir différents dépôts pour les implémentations du langage.

  • Babel AST : Cela a été utilisé parce qu'à l'origine, nous comptions sur Babel pour imprimer le code. Dans un monde idéal, maintenant que nous avons notre imprimante, il devrait être possible de la retirer. Mais il y a beaucoup de travail dans l'étape Fable-to-Babel donc ce serait difficile. Pour la même raison, il serait préférable de réutiliser autant de code que possible à partir de cette étape, peut-être en faisant de Babel AST quelque chose de plus générique pour les langages non fonctionnels. Quelques exemples:

    • Passer de l'AST basé sur l'expression à l'AST expression/instruction. Pour JS, cela inclut le déplacement des déclarations de variables vers le haut de la fonction lorsque cela est nécessaire pour éviter trop d'IIFE. Je suppose que ce sera quelque chose de similaire avec Python.
    • Transformer la correspondance de modèle (DecisionTree) en instructions if/switch.
    • Optimisation des appels de queue. Cela se fait en utilisant des boucles étiquetées JS. En Python, nous aurions besoin d'utiliser une autre technique pour nous assurer de ne pas rompre avec une boucle imbriquée.
    • Conversion des importations en identifiants uniques (cela pourrait être fait directement dans la Fable AST si nécessaire).
  • Rendre F# plus pythonique ? Avec Fable, j'ai toujours essayé de trouver un équilibre où la sémantique .NET est parfois sacrifiée pour éviter les frais généraux. Par exemple, nous utilisons simplement le numéro JS au lieu d'avoir un type spécifique pour les ints (bien que nous le fassions pendant longtemps), cela s'appliquerait à Python int je suppose. Cependant, de nombreuses contributions ont été faites pour respecter la sémantique .NET à des points spécifiques afin d'éviter des résultats inattendus, par exemple dans les divisions entières ou les conversions numériques explicites.
    Quoi qu'il en soit, quand vous dites "plus pythonique", vous voulez dire un style de code ou des API natives ? Fable n'essaie pas de prendre en charge la plupart des BCL .NET, mais à la fin, nous devons prendre en charge les fonctions/opérateurs communs car c'est ce à quoi les développeurs sont habitués (même les nouveaux arrivants, car ils trouveraient dans les tutoriels). Il est bon de donner la possibilité d'utiliser les API natives dans tous les cas ( Fable.Extras est une bonne chose dans ce sens). Par exemple, JS.console.log formate généralement mieux les objets que printfn , mais nous devons prendre en charge ce dernier car c'est ce que les développeurs utilisent le plus.

Tous les 54 commentaires

C'est du bon travail @dbrattli ! 👏 👏 👏 Mon idée originale avec Fable était de fournir un moyen de compiler facilement F# dans d'autres langages. Pour le meilleur ou pour le pire, Fable a évolué avec JS comme objectif principal, il faudrait donc un peu de travail pour le rendre plus indépendant du langage, mais je serais ouvert à cela. Quelques commentaires à vos questions :

  • Repos identique ou différent ? Je serais en faveur de garder autant de code commun que possible. L'utilisation de différents dépôts les fera probablement diverger assez rapidement (il est déjà difficile de synchroniser la branche avec le support des citations), mais nous devons également rendre le code suffisamment modulaire pour ne pas avoir de conditions partout en fonction de la langue cible. Une solution intermédiaire serait d'essayer d'extraire le plus de code possible vers une Fable "Nucleus" (on ne peut pas utiliser Core 😉) et ensuite d'avoir différents dépôts pour les implémentations du langage.

  • Babel AST : Cela a été utilisé parce qu'à l'origine, nous comptions sur Babel pour imprimer le code. Dans un monde idéal, maintenant que nous avons notre imprimante, il devrait être possible de la retirer. Mais il y a beaucoup de travail dans l'étape Fable-to-Babel donc ce serait difficile. Pour la même raison, il serait préférable de réutiliser autant de code que possible à partir de cette étape, peut-être en faisant de Babel AST quelque chose de plus générique pour les langages non fonctionnels. Quelques exemples:

    • Passer de l'AST basé sur l'expression à l'AST expression/instruction. Pour JS, cela inclut le déplacement des déclarations de variables vers le haut de la fonction lorsque cela est nécessaire pour éviter trop d'IIFE. Je suppose que ce sera quelque chose de similaire avec Python.
    • Transformer la correspondance de modèle (DecisionTree) en instructions if/switch.
    • Optimisation des appels de queue. Cela se fait en utilisant des boucles étiquetées JS. En Python, nous aurions besoin d'utiliser une autre technique pour nous assurer de ne pas rompre avec une boucle imbriquée.
    • Conversion des importations en identifiants uniques (cela pourrait être fait directement dans la Fable AST si nécessaire).
  • Rendre F# plus pythonique ? Avec Fable, j'ai toujours essayé de trouver un équilibre où la sémantique .NET est parfois sacrifiée pour éviter les frais généraux. Par exemple, nous utilisons simplement le numéro JS au lieu d'avoir un type spécifique pour les ints (bien que nous le fassions pendant longtemps), cela s'appliquerait à Python int je suppose. Cependant, de nombreuses contributions ont été faites pour respecter la sémantique .NET à des points spécifiques afin d'éviter des résultats inattendus, par exemple dans les divisions entières ou les conversions numériques explicites.
    Quoi qu'il en soit, quand vous dites "plus pythonique", vous voulez dire un style de code ou des API natives ? Fable n'essaie pas de prendre en charge la plupart des BCL .NET, mais à la fin, nous devons prendre en charge les fonctions/opérateurs communs car c'est ce à quoi les développeurs sont habitués (même les nouveaux arrivants, car ils trouveraient dans les tutoriels). Il est bon de donner la possibilité d'utiliser les API natives dans tous les cas ( Fable.Extras est une bonne chose dans ce sens). Par exemple, JS.console.log formate généralement mieux les objets que printfn , mais nous devons prendre en charge ce dernier car c'est ce que les développeurs utilisent le plus.

J'adore cette idée. Surtout, j'ai hâte d'utiliser les bibliothèques Python en F#. Idéalement, nous devrions avoir la possibilité de consommer des bibliothèques Python pures ou SciSharp - elles ont tendance à avoir une API presque exacte comme Python (même code F# exécuté dans un contexte Python ou .NET !)

Un tel code F# peut être compilé avec Fable en Python, puis utilisé dans les noyaux Notebooks (Jupyter ou .NET Interactive), ce qui fait que le flux de travail d'analyse est une véritable installation axée sur le domaine (je pourrais considérer tout cela comme une "fonctionnalité mortelle").

.NET Interactive est très extensible, exécuter Fable dans F# Kernel est très simple (j'ai un PoC fonctionnel).

Une autre chose est que je prédis que MS révélera bientôt quelque chose de similaire pour tout le .NET (la prédiction est basée uniquement sur les activités et les objectifs de .NET Interactive, donc je peux me tromper). Même s'ils le font, nous voulons toujours que Fable l'ait séparément afin d'avoir un arsenal JS/F#/Python complet dans un monde fonctionnel. L'approche MS ressemblerait davantage à une saveur Blazor (je suppose).

Merci, c'est un excellent retour @alfonsogarciacaro , @PawelStadnicki . Ce que je voulais dire, c'est si nous voulons laisser le programmeur avoir le sentiment "Python" en exposant davantage la bibliothèque standard Python au lieu d'essayer de réimplémenter .NET (alors ils devraient probablement simplement utiliser .NET à la place).

Je suppose que nous pourrions avoir le meilleur des deux mondes et laisser l'utilisateur décider quoi importer ou non (par exemple, utiliser datetime de Python ou DateTime de .NET). Donc pas besoin de décider, mais juste de mettre en œuvre ce que nous voulons le plus (à la demande). Mais je soupçonne que le même problème est pertinent pour Fable JS. Vous savez que vous implémentez une application Web et vous ne vous attendez probablement pas à ce que tout .NET soit disponible. Vous préféreriez avoir un meilleur accès à l'écosystème JS.

Donc pas besoin de décider à l'avance. Retroussons les manches et allons-y 😄 Ce sera formidable de faire partie de Fable et d'avoir ce problème pour poser des questions en cours de route, par exemple sur les transformations, etc. Il y a beaucoup à apprendre.

PS: Expression btw a également un décorateur d'appel de queue que nous pouvons utiliser (à la fois synchronisé et async). Voici un exemple d' utilisation dans aioreactive.

Cela me fait penser au #1601.

Je me demande s'il existe un bon moyen de rendre Fable suffisamment puissant pour que ceux-ci puissent être créés en tant que "plugins" backend tiers (pensez à LLVM mais spécifique à F#). Des possibilités infinies.

Nous pourrions essayer de rendre Babel AST plus générique (et typé) afin qu'il puisse être facilement transformé/imprimé dans des langages de type C. Cependant, si j'ai appris quelque chose en développant Fable, c'est qu'écrire un simple compilateur F# vers un autre langage est (plus ou moins) facile, mais offre une bonne expérience de développement et une bonne intégration afin que les avantages de F# moins les frictions avec le écosystème étranger pèsent plus que développer dans la langue maternelle est assez difficile.

Ce qu'a dit

Intéressant. Je n'avais pas réalisé que des parties importantes de l'implémentation de Fable BCL étaient écrites en (vraisemblablement) Javascript. J'ai supposé que ce serait déjà plus F#. Je ne doute pas qu'il y ait une bonne raison à cela - pourquoi cela s'est-il avéré plus facile de cette façon ?

@jwosty La

De plus, .NET BCL a une très grande surface d'API, en plus de FSharp.Core , il est donc difficile de maintenir cet effort sans une plus grande équipe (bien que @alfonsogarciacaro réussisse d'une manière

Je suppose que ce que j'essaie de dire, c'est que les contributions (et les idées) sont les bienvenues et appréciées.

La réponse (pas si courte) est : pour des raisons de performances, et pour combler la différence entre les types F# et les types supportés nativement par le navigateur, et pour une meilleure intégration du code généré au sein de l'écosystème JavaScript.

Je peux voir ça.

Je suppose que ce que j'essaie de dire, c'est que les contributions (et les idées) sont les bienvenues et appréciées.

Avec certitude! Je comprends combien d'efforts sont déployés pour maintenir des projets aussi grands et aussi importants que celui-ci, et j'apprécie vraiment la sueur de sang et les larmes que vous y avez versées. Je garde définitivement les yeux ouverts sur les endroits où contribuer, car Fable m'a été récemment très très très utile et j'aimerais redonner.

J'aurais besoin d'un peu d'aide. J'utilise actuellement le projet QuickTest pour le développement (c'est- dotnet fsi build.fsx quicktest dire Mise à jour : j'ai trouvé ASTViewer ).

Mon problème actuel est que Python ne prend pas en charge le lambda multiligne (fonctions fléchées), donc le lambda doit être transformé et extrait en une fonction nommée et appeler cette fonction à la place. Tous les conseils ici seraient appréciés. Par exemple:

let fn args cont  =
    cont args

let b = fn 10 (fun a ->
    a + 1
)

Devra être transformé en :

let fn args cont  =
    cont args

let cont a =
    a + 1
let b = fn 10 cont

Comment dois-je penser lors du remplacement d'une expression d'appel par plusieurs instructions, car lors de la transformation de l'appel de fonction avec des arguments, je dois le remplacer à un niveau supérieur ou utiliser une sorte d'instruction de bloc, par exemple if true then ... Des idées ?

@dbrattli Voir aussi cette imprimante , qui imprime également certaines des propriétés.

Je n'avais pas réalisé que des parties importantes de l'implémentation de Fable BCL étaient écrites en (vraisemblablement) Javascript. J'ai supposé que ce serait déjà plus F#. Je ne doute pas qu'il y ait une bonne raison à cela – pourquoi cela s'est-il avéré plus facile de cette façon ?

Oui, comme l' a dit

Dans Fable 2, nous avons commencé un effort pour porter certains des modules vers F#. Cela a été très bénéfique car cela augmente le dogfooding et nous permet de réutiliser facilement les améliorations de FSharp.Core (comme avec les cartes et les ensembles récemment). Cependant, il existe encore quelques astuces ici et là pour contourner les problèmes de poulet et d'œuf. Et ne parlons pas du gros monstre qu'est le module Remplacements :)

De plus, comme le dit @ncave , la maintenance de cette bibliothèque est une entreprise énorme, c'est pourquoi je suis toujours réticent à augmenter le support de BCL pour réduire la surface de maintenance, mais j'ai eu beaucoup de chance qu'il y ait eu plusieurs grandes contributions à la bibliothèque Depuis le tout début.

@dbrattli À propos de l'impression de l'AST, cela fait un moment que je n'ai pas utilisé l'outil ASTViewer dans le référentiel Fable. Les meilleures options sont celles indiquées par @ncave ou le visualiseur Fantomas, que j'utilise récemment lorsque j'ai besoin d'inspecter le F# AST (assurez-vous simplement de sélectionner Show Typed AST ). Malheureusement, nous n'avons pas encore d'imprimantes spécifiques pour Fable ou Bable AST. Dans le cas de Fable AST, comme il ne s'agit que d'unions et d'enregistrements, un simple printfn "%A" fonctionne, bien que les informations de localisation soient un peu bruyantes.

A propos de l'extraction des lambdas. C'est possible mais demande quelques travaux. Je suppose que nous pouvons faire quelque chose de similaire aux importations, c'est-à-dire les collecter lors de la traversée de l'AST et leur attribuer un identifiant unique. Plus tard, nous pourrons simplement déclarer les fonctions en haut du fichier . Le principal défi sera probablement les valeurs capturées, je ne me souviens pas si le F# AST a des informations à ce sujet, mais même s'il les a, malheureusement nous ne les gardons pas dans l'AST, donc je suppose que nous devrons trouvez tous les identifiants utilisés dans le corps lambda qui ne correspondent pas aux arguments (ou aux liaisons dans le lambda) et convertissez-les en arguments supplémentaires du lambda (et je suppose qu'ils doivent aller au début pour éviter d'interférer avec le curry).

Merci pour l'excellente info @alfonsogarciacaro , mon premier PoC était juste en train de modifier Babel.fs / Fable2Babel.fs , BabelPrinter.fs etc. Je pars maintenant de zéro avec un Python AST séparé, c'est- Python.fs dire PythonPrinter.fs . Ensuite, je vais essayer d'ajouter Babel2Python.fs pour transformer Babel AST en Python AST car cela peut être plus facile que de convertir à partir de Fable AST (mais aussi possible avec Fable2Python.fs si la conversion de Babel finit par être difficile). De cette façon, je ne toucherai pas trop à la base de code existante et j'aurai ma propre transformation où j'essaierai de faire la réécriture lambda.

Ça a du sens. Une difficulté est que Babel AST n'est pas composé de DU, il est donc un peu plus difficile de le parcourir avec la correspondance de motifs. Peut-être que #2158 pourrait vous aider ?

@alfonsogarciacaro Je pense que j'ai réussi à corriger la réécriture des fonctions fléchées et des expressions de fonction d'une manière qui, je pense (j'espère) pourrait réellement fonctionner. L'idée est que chaque expression ( TransformAsExpr ) renvoie également une liste d'instructions (qui doit être concaténée et transmise jusqu'à la dernière liste d'instructions (last-statement-level). Ensuite, tous les les instructions renvoyées (func-def) seront soulevées et écrites devant d'autres instructions. Ainsi, une expression de flèche ou de fonction renverra simplement un name-expression, [statement ] où la flèche/expression de fonction est réécrite en un et renvoyée avec l'expression_nom. Cela signifie que :

module QuickTest

let fn args cont  =
    cont args

let b = fn 10 (fun a ->
    printfn "test"
    a + 1
)

Génère le JS suivant :

import { printf, toConsole } from "./.fable/fable-library.3.1.1/String.js";

export function fn(args, cont) {
    return cont(args);
}

export const b = fn(10, (a) => {
    toConsole(printf("test"));
    return (a + 1) | 0;
});

Ce qui à son tour génère le Python suivant :

from expression.fable.string import (printf, toConsole)

def fn(args, cont):
    return cont(args)


def lifted_5094(a):
    toConsole(printf("test"))
    return (a + 1) | 0


b = fn(10, lifted_5094)

Je pensais que je pourrais avoir des problèmes pour gérer les fermetures, mais dans la plupart (tous?) Des cas, les fermetures sont également levées, par exemple :

module QuickTest

let add(a, b, cont) =
    cont(a + b)

let square(x, cont) =
    cont(x * x)

let sqrt(x, cont) =
    cont(sqrt(x))

let pythagoras(a, b, cont) =
    square(a, (fun aa ->
        printfn "1"
        square(b, (fun bb ->
            printfn "2"
            add(aa, bb, (fun aabb ->
                printfn "3"
                sqrt(aabb, (fun result ->
                    cont(result)
                ))
            ))
        ))
    ))

Sera réécrit en :

from expression.fable.string import (printf, toConsole)

def add(a, b, cont):
    return cont(a + b)


def square(x, cont):
    return cont(x * x)


def sqrt(x, cont):
    return cont(math.sqrt(x))


def pythagoras(a, b, cont):
    def lifted_1569(aa):
        toConsole(printf("1"))
        def lifted_790(bb):
            toConsole(printf("2"))
            def lifted_6359(aabb):
                toConsole(printf("3"))
                return sqrt(aabb, lambda result: cont(result))

            return add(aa, bb, lifted_6359)

        return square(b, lifted_790)

    return square(a, lifted_1569)

En tout cas c'est un bon début 😄

J'ai fait à peu près la même chose pour PHP (et il exécute

https://github.com/thinkbeforecoding/peeble

@ncave Avez-vous d'ailleurs des conseils sur l'utilisation de Fable pour une utilisation interactive telle que les cahiers Jupyter ? Le problème est que Jupyter vous donnera un fragment de code (cellule) et le noyau doit compiler ce fragment avec toutes les déclarations locales existantes des instructions précédentes.

Fable n'est pas optimal pour cela, mais j'ai créé un PoC où je conserve un dictionnaire ordonné des déclarations/déclarations précédentes (let, type, open) et crée un programme F# valide en les utilisant dans l'ordre avec le dernier fragment de code. Toutes les déclarations dans le fragment de code sont supprimées du dict avant l'exécution et ajoutées après. Cela semble fonctionner, mais je me demande s'il y a peut-être une meilleure façon?

Source : https://github.com/dbrattli/Fable.Jupyter/blob/main/fable/kernel.py#L85

Ensuite, j'ai un cli Fable exécuté en arrière-plan qui recompile chaque fois que Fable.fs est mis à jour.

dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter --exclude Fable.Core --forcePkgs --python

@dbrattli Cela semble raisonnable, si les fragments sont petits. Pour les plus gros, peut-être que l'utilisation d'un fichier séparé pour chaque cellule (fragment) peut accélérer les choses, car Fable utilise une mise en cache pour éviter le travail inutile sur les fichiers qui n'ont pas changé.

Juste pour info, si vous avez besoin de plus de contrôle, vous pouvez également utiliser Fable comme bibliothèque , pas seulement CLI (voir fable-standalone , et un exemple d' utilisation ).

1) Un problème que j'ai est que le Fable AST n'a pas Throw / Raise. Cela sera donc transformé en une expression Emit qui est (plus) difficile à analyser pour moi. Ou du moins, cela semble inutile lorsque Babel a un ThrowStatement. Y a-t-il une bonne raison pour laquelle il n'est pas utilisé ou dois-je essayer de le réparer ? @alfonsogarciacaro

let divide1 x y =
   try
      Some (x / y)
   with
      | :? System.DivideByZeroException -> printfn "Division by zero!"; None

let result1 = divide1 100 0

Génère :

export function divide1(x, y) {
    try {
        return ~(~(x / y));
    }
    catch (matchValue) {
        throw matchValue;
    }
}

export const result1 = divide1(100, 0);

throw matchValue est un EmitExpression .

2) Devrions-nous ajouter un ThisExpr à Fable AST, afin que je puisse identifier si this est utilisé comme ce mot-clé. Je ne peux pas être sûr qu'avec Fable.IdentExpr soit défini sur this s'il s'agit du mot-clé this ou non, car ceux-ci doivent être traduits en self en Python.
https://github.com/fable-compiler/Fable/blob/nagareyama/src/Fable.Transforms/Fable2Babel.fs#L792

C'est drôle, le lancer était en fait dans Fable 2 AST mais je l'ai supprimé pour Fable 3 dans le but de simplifier l'AST car il n'était utilisé qu'à un seul endroit, mais je suppose que nous pouvons l'ajouter à nouveau.

À propos de « ça », c'est délicat. Je dois le vérifier à nouveau car dans le F# AST, cela est représenté différemment dans les constructeurs ou les méthodes (il y a quelques commentaires à ce sujet dispersés dans le code). Il existe une propriété IsThisArgument pour les identifiants dans Fable AST, mais permettez-moi de vérifier s'il ne s'agit que du premier argument des membres détachés ou si cela fonctionne également pour "réel ceci".

@alfonsogarciacaro Je pourrais utiliser un peu d'aide pour savoir comment gérer le code compilé "interne" vs "utilisateur", par exemple Exception have eg .message , mais comment dois-je le traduire? Puis-je être sûr que le code compilé par l'utilisateur n'aura jamais un .message et finira toujours par exemple par Test__Message_229D3F39(x, e) afin qu'ils n'interfèrent jamais ? Ou dois-je envelopper une exception interceptée dans un objet JsException et ajouter une propriété .message juste pour être sûr ? Comment Fable fait-il cela de F# à Babel ? Gardez-vous une trace des types, ou?

Ex : comment ex.Message.StartsWith en F# devient-il ex.message.indexOf en JS ? Je devrai traduire cela en str(ex).index en Python (ou envelopper les objets).

En fait, nous avons un problème avec les exceptions F# (comme dans: exception Foo of int * int) que je n'ai pas corrigé avant la version Fable 3 😅 System.Exception est traduit par JS Error donc ils ont un champ .message. Mais les exceptions F# ne dérivent pas de l'erreur JS pour des raisons de performances (je pense qu'en ce moment, essayer d'accéder à .Message échoue simplement). Il y a une discussion à ce sujet quelque part. Pour votre cas et pour une exception générale, je pense que vous pouvez supposer pour l'instant que Fable accède aux propriétés .message et .stack.

Comment voulons-nous d'ailleurs que la sélection de la langue soit dans Fable ? Aujourd'hui, nous pouvons sélectionner par exemple TypeScript en utilisant --typescript . Nous pourrions donc sélectionner Python en utilisant --python . Mais devrions-nous arrêter de générer le .js alors ? Nous devrions probablement le faire, car le JS généré pourrait ne pas être valide si, par exemple, Emit est utilisé. Mais parfois, nous voulons voir le JS. Devrions-nous avoir une option pour --javascript , ou devrions-nous exiger que l'utilisateur s'exécute sans --python pour obtenir JavaScript ? @alfonsogarciacaro

@dbrattli --typescript ne génère que des fichiers .ts , donc --python ne peut générer que des fichiers .py , si vous préférez.
Mais techniquement, ils sont tous mutuellement exclusifs, donc devrions-nous plutôt introduire un nouveau commutateur --language , avec des valeurs JavaScript/TypeScript/Python ?

@ncave Oui, je pense que c'est une bonne idée quelque chose comme [-lang|--language {"JavaScript"|"TypeScript"|"Python"}]JavaScript pourrait être par défaut. J'ai déjà commencé à ajouter une propriété de langage pour le compilateur, donc je peux aussi essayer de corriger les options de la ligne de commande ? https://github.com/fable-compiler/Fable/pull/2345/files#diff -9cb94477ca17c7556e6f79d71ed20b71740376f7f3b00ee0ac3fdd7e519ac577R12

Un certain statut. La prise en charge de Python s'améliore. De nombreuses fonctionnalités linguistiques sont désormais en place. L'énorme tâche restante est de porter fable-library. Pour rendre cela plus facile, la bibliothèque de fables pour Python a été déplacée dans le référentiel, afin que nous puissions convertir et réutiliser les nombreux fichiers de bibliothèque .fs fables

Construire une bibliothèque de fables pour Python : dotnet fsi build.fsx library-py
Exécutez des tests Python : dotnet fsi build.fsx test-py

@dbrattli
Idéalement, nous devrions essayer de convertir plus de fable-library en F#. List et Seq sont déjà là, Array utilise un petit nombre de méthodes natives, peut-être qu'elles peuvent être séparées.

Sur un sujet légèrement différent, à votre avis, quels sont les principaux avantages de l'implémentation d'un nouveau codegen de langage à partir de Babel AST, au lieu de directement à partir de Fable AST ?

@ncave Python et JavaScript sont assez proches (imo) il est donc relativement facile de réécrire Babel AST. J'ai déjà porté un peu de JS vers Python, par exemple RxJS vers RxPY. Nous bénéficions également de toutes les corrections (de bogues) apportées "en amont". Cela dit, nous pourrions réutiliser la plupart des Fable2Babel.fs et les transformer directement en Python en utilisant un Fable2Python.fs qui combine/réduit Fable2Babel.fs et Babel2Python.fs . En fait, j'y ai pensé plus tôt aujourd'hui. Cela ressemble alors davantage à un fork et des corrections de bugs devraient probablement être appliquées aux deux endroits. Pour l'instant, je pense que c'est ok de transformer Babel AST, mais éventuellement nous pourrions vouloir transformer directement Fable AST.

C'est super @dbrattli ! Merci beaucoup pour tout votre travail. En fait, ce que j'avais en tête, c'était de mettre la plupart du code de Fable dans une bibliothèque et de publier les différentes implémentations en tant qu'outils dotnet indépendants : fable(-js), fable-py (ou tout autre nom). Peut-être que cela nous donne plus de liberté dans les cycles de publication, mais je suis tout à fait d'accord également avec un seul outil dotnet qui peut compiler dans les deux langages. Cela peut être plus simple, surtout au début.

A propos de fable-library, oui, si possible au lieu de réécrire en python les fichiers .ts actuels, ce serait bien de les porter sur F# à la place. Nous devrions essayer d'isoler les parties en utilisant Emit et les adapter à Python soit en utilisant #if :

#if FABLE_COMPILER_PYTHON
    [<Emit("print($0)")>]
    let log(x: obj) = ()

 // Other native methods ...
#else
    [<Emit("console.log($0)")>]
    let log(x: obj) = ()

    // ...
#endif

Ou nous pouvons ajouter un nouvel attribut Emit "multi-langue":

type EmitLangAttribute(macros: string[]) =
    inherit Attribute()

[<EmitLang([|"js:console.log($0)"; "py:print($0)"|])>]
let log(x: obj) = ()

Merci @alfonsogarciacaro et @ncave , ce sont de bonnes idées. Je vais les essayer pour voir ce qui fonctionne. Je vois l'avantage de traduire fable-library en F#, donc j'essaierai d'aider ici une fois que le compilateur Python sera assez bon pour gérer les fichiers là-bas. L'utilisation de #if devrait beaucoup aider, et nous pourrions probablement éviter les #if s en déplaçant les émissions dans des fichiers de bibliothèque spécifiques au langage (puisque j'ai un fichier .fsproj séparé pour Python.

@alfonsogarciacaro pourquoi ne pas avoir deux attributs d'émission à la place ? Emit pour JS et EmitPy pour python. Le transpiler python doit signaler un déclin lorsqu'il rencontre des émissions pour JS, etc.

Pour mes 2 cents, j'aime la faible charge cognitive actuelle d'avoir un seul attribut d'émission qui émet simplement. Imo, ce que @dbrattli fait en ce moment a du sens (fichier de projet différent pour chaque version linguistique de fable-library ). Nous pouvons séparer les différentes émissions de langage dans leurs propres fichiers, qui implémentent des interfaces (ou modules) bien définies à appeler plus tard à partir de l'implémentation F# commune.

Un bon exemple peut être de convertir Array.Helpers en une interface (ou simplement de la laisser comme module) qui peut être implémentée pour chaque langue cible. Peut-être que cet effort de migration de plus de fable-library vers F# (ainsi que l'option de compilateur --language ) peut aller dans un PR séparé de celui-ci, donc il peut aller plus vite et être plus facilement contribué à .

C'est une bonne idée @ncave :+1: En fait, nous le faisons déjà lors de l'isolation du code natif dans les projets Fable multiplateformes (.net et js). Pour simplifier les choses, je voudrais probablement simplement ajouter un nouveau fichier Native.fs à Fable.Library et y avoir des sous-modules selon les besoins, comme :

namespace Fable.Library.Native            # or just Native

module Array = ..
module Map = ..

Nous devrions y déplacer tout ce qui utilise Emit ou des valeurs de Fable.Core.JS .

En travaillant sur async, j'essaie de porter Async.ts et AsyncBuilder.ts vers F#, c'est-à-dire avec Async.fs et AsyncBuilder.fs . Mais maintenant j'ai le problème que mon fichier de test avec le code :

async { return () }
|> Async.StartImmediate

génère du code Python (le code JS doit être très similaire) :

startImmediate(singleton.Delay(lambda _=None: singleton.Return()))

AsyncBuilder ne contient cependant aucune méthode mais il y a AsyncBuilder__Delay_458C1ECD . J'obtiens donc AttributeError : l'objet 'AsyncBuilder' n'a pas d'attribut 'Delay'.

class AsyncBuilder:
    def __init__(self):
        namedtuple("object", [])()

def AsyncBuilder__ctor():
    return AsyncBuilder()

def AsyncBuilder__Delay_458C1ECD(x, generator):
    def lifted_17(ctx_1):
        def lifted_16(ctx):
            generator(None, ctx)

        protectedCont(lifted_16, ctx_1)

    return lifted_17
...

Des conseils sur la façon dont je peux gérer cela? @ncave @alfonsogarciacaro. Existe-t-il un moyen de faire en sorte que les classes F# génèrent des classes avec des méthodes, ou que mes tests génèrent du code qui utilise le AsyncBuilder__Delay_458C1ECD statique au lieu de .Delay() ?

@dbrattli Habituellement, la réponse au démêlage consiste à utiliser des interfaces pour les méthodes que vous souhaitez ne pas déformer.
Sur une note connexe, le support asynchrone est-il l'une de ces fonctionnalités qui sont peut-être mieux adaptées pour une traduction en Python natif asyncio ?

@ncave Ok, c'est comme ça que ça marche 😄 Merci pour la perspicacité. Je vais essayer de mettre le constructeur derrière une interface et voir si cela aide (même si les constructeurs F# ne sont pas des interfaces, cela devrait probablement fonctionner correctement). En ce qui concerne async le plan était d'utiliser l'asyncio Python natif ( voir ici ), mais j'ai eu des problèmes avec python cannot reuse already awaited coroutine , je dois donc les paresser derrière une fonction. Quoi qu'il en soit, j'ai compris que je pouvais tout aussi bien utiliser un constructeur task ou asyncio pour les fichiers natifs Python. La simplicité de AsyncBuilder.ts m'a incité à essayer de le porter sur F#.

L'utilisation d'une interface pour éviter la mutilation devrait fonctionner comme le dit @ncave . Nous avons également essayé quelques autres approches :

  • L'attribut NoOverloadSuffix : cela empêche Fable d'ajouter le suffixe de surcharge. Évidemment, les surcharges ne fonctionneront pas dans ce cas.

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/fable-library/Map.fs#L524 -L527

Ensuite, nous pouvons référencer les méthodes en utilisant Naming.buildNameWithoutSanitation :

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/Fable.Transforms/Replacements.fs#L1956 -L1963

  • Mais après cela, @ncave a écrit une méthode pour créer des noms mutilés plus ou moins comme l'attend Fable (je ne me souviens pas s'il y a des limitations) afin que vous puissiez écrire du code F# normalement. Voir par exemple src/fable-library/System.Text.fs :

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/Fable.Transforms/Replacements.fs#L1314 -L1326

Dans ce cas, il vous suffit d'ajouter la classe au dictionnaire replacedModules :

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/Fable.Transforms/Replacements.fs#L3075


Donc, malheureusement, il n'y a pas encore d'approche cohérente pour lier le code de la bibliothèque de fables écrit en F# à partir de Replacements . Peut-être que nous profitons de cette occasion pour nous mettre d'accord sur un maintenant.

Merci pour les idées très intéressantes @ncave et @alfonsogarciacaro. Avec les interfaces, je suis tombé sur un autre problème. Comment gérer unit en Python ? Mon interface ressemble à ceci :

type IAsyncBuilder =
    abstract member Bind<'T, 'U> : IAsync<'T> * ('T -> IAsync<'U>) -> IAsync<'U>
    abstract member Combine<'T> : IAsync<unit> * IAsync<'T> -> IAsync<'T>
    abstract member Delay<'T> : (unit -> IAsync<'T>) -> IAsync<'T>
    abstract member Return<'T> : value: 'T -> IAsync<'T>
   ...

Le problème est que Return génère :

class AsyncBuilder:
    def Return(self, value):
        return protectedReturn(value)
    ....

Cela a l'air bien et est probablement bon pour JS, mais en python, vous devez donner un argument si la fonction prend un argument. Ainsi, vous obtiendrez une erreur si vous appelez x.Return() lorsque 'T vaut unit . Ma première réaction a été de faire une surcharge :

abstract member Return : unit -> IAsync<unit>

mais cela a ses propres problèmes. Ma solution actuelle (qui a l'air moche est):

abstract member Return<'T> : [<ParamArray>] value: 'T [] -> IAsync<'T>

...

member this.Return<'T>([<ParamArray>] values: 'T []) : IAsync<'T> =
    if Array.isEmpty values then
        protectedReturn (unbox null)
    else
        protectedReturn values.[0]

Mais cela m'oblige à avoir une gestion personnalisée pour chaque fonction générique à argument unique. Peut-être que je devrais plutôt faire en sorte que chaque fonction à argument unique accepte null (Aucun en Python) en entrée. Par exemple:

class AsyncBuilder:
    def Return(self, value=None):
        return protectedReturn(value)
    ....

Quelle serait la bonne façon de traiter ce problème?

IIRC, pour les méthodes avec la signature unit -> X les appels n'ont pas d'arguments (comme dans l'AST F#) mais pour les lambdas ou dans ce cas les arguments génériques remplis avec les appels/applications unit ont un unit argument dans le Fable AST (également dans le F# AST). Cet argument est supprimé dans l'assistant transformCallArgs de l'étape Fable2Babel. Peut-être pourrions-nous y ajouter une condition et laisser l'argument unit lorsque le langage cible est python :

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/Fable.Transforms/Fable2Babel.fs#L1083 -L1086

Salut @ncave , @alfonsogarciacaro , je pourrais utiliser quelques informations sur la gestion des paramètres [<Inject>] en Python. Comment cela se passe-t-il dans Php @thinkbeforecoding ? Par exemple, des fonctions comme (dans Array.fs ):

let map (f: 'T -> 'U) (source: 'T[]) ([<Inject>] cons: Cons<'U>): 'U[] =
    let len = source.Length
    let target = allocateArrayFromCons cons len
    for i = 0 to (len - 1) do
        target.[i] <- f source.[i]
    target

Mon code ne génère pas le paramètre cons et il est requis pour Python.

map(fn ar)

Est-ce facultatif pour JS ? Comment puis-je détecter l'attribut dans le code et le rendre facultatif ? Par exemple

def map(f, source, cons=None):
    ...

Php est également explicite sur les paramètres facultatifs. J'ai dû ajouter un indicateur IsOptional sur le modèle d'argument.

Il y a deux utilisations pour l'attribut Inject dans Fable. L'un est principalement expérimental (pour émuler d'une manière ou d'une autre des classes de types et aussi pour éviter d'avoir à insérer une fonction pour résoudre les informations de type générique) et vous ne devriez pas trop vous en soucier.

L'autre utilisation est interne aux méthodes de la bibliothèque de fables qui nécessitent des informations supplémentaires transmises par un argument résolu au moment de la compilation :

  • Les fonctions qui construisent un tableau doivent savoir s'il s'agit d'un tableau dynamique ou typé JS.
  • Définissez et mappez les constructeurs et autres assistants qui doivent recevoir un comparateur pour savoir comment un type doit être comparé (comme pour les fonctions qui effectuent des calculs de moyenne).

Cependant, ces fonctions ne sont pas appelées directement, donc Fable "ne peut pas voir" l'attribut Inject . Pour cette raison, nous utilisons le fichier ReplacementsInject.fs qui indique quelles fonctions de la bibliothèque nécessitent une injection. Voir comment il est utilisé : https://github.com/fable-compiler/Fable/blob/522f6aad211102271538798aeb90f4aed1f77dd6/src/Fable.Transforms/Replacements.fs#L988-L1019

Ce fichier a été créé automatiquement au début avec ce script qui détecte quelles fonctions dans Fable.Library ont le dernier argument décoré de Inject . Mais je pense qu'à un moment donné, nous avons arrêté la mise à jour et IIRC les dernières mises à jour des ReplacementInjects. fichier a été fait manuellement.

Sachant cela, je peux probablement me débarrasser de l'info IsOptional... Je vais vérifier

@alfonsogarciacaro Il semble y avoir un problème avec injectArg pour du code tel que :

type Id = Id of string

let inline replaceById< ^t when ^t : (member Id : Id)> (newItem : ^t) (ar: ^t[]) =
    Array.map (fun (x: ^t) -> if (^t : (member Id : Id) newItem) = (^t : (member Id : Id) x) then newItem else x) ar

let ar = [| {|Id=Id"foo"; Name="Sarah"|}; {|Id=Id"bar"; Name="James"|} |]
replaceById {|Id=Id"ja"; Name="Voll"|} ar |> Seq.head |> fun x -> equal "Sarah" x.Name
replaceById {|Id=Id"foo"; Name="Anna"|} ar |> Seq.head |> fun x -> equal "Anna" x.Name

Ici, le Array.map ne recevra pas le constructeur injecté. Le code sera transpilé en JS sans aucun argument injecté :

return map((x) => (equals(newItem.Id, x.Id) ? newItem : x), ar);

Est-ce un bug ? Ce code fonctionnera en JS (par chance ?) mais pas en Python.

Ah désolé! Totalement oublié à ce sujet mais nous avons une "optimisation" où le constructeur de tableau n'est pas injecté pour le tableau JS "standard": https://github.com/fable-compiler/Fable/blob/4ecab5549ab6fcaf317ab9484143420671ded43b/src/Fable.Transforms/ Remplacements.fs#L1005 -L1009

Nous utilisons ensuite par défaut le Array global lorsque rien n'est transmis : https://github.com/fable-compiler/Fable/blob/4ecab5549ab6fcaf317ab9484143420671ded43b/src/fable-library/Array.fs#L25 -L28

Je ne sais pas tout à fait pourquoi j'ai fait cela, mais je suppose que d'économiser quelques octets dans le bundle pour les cas les plus courants. Nous pouvons supprimer cette optimisation pour la branche next si cela vous aide.

OK merci. Je vais essayer de voir si je peux faire fonctionner ça. Je ne pense pas que nous ayons besoin de le désactiver, nous avons juste besoin de générer une valeur vide (nulle) pour le troisième argument afin que Python ne s'étouffe pas. c'est à dire:

            | Types.arrayCons ->
                match genArg with
                | Number(numberKind,_) when com.Options.TypedArrays ->
                    args @ [getTypedArrayName com numberKind |> makeIdentExpr]
                | _ -> args @ [ Expr.Value(ValueKind.Null genArg, None) ]

Pour JS, il générera alors :

map((x_3) => (equals(newItem_1.Id, x_3.Id) ? newItem_1 : x_3), ar, null))).Name);

et pour Python :

def lifted_53(x_2):
    return newItem_1 if (equals(newItem_1["Id"], x_2["Id"])) else (x_2)

return map(lifted_53, ar, None)

Est-ce que quelque chose comme ça serait une solution acceptable?

Cela devrait fonctionner. Peut-être que nous pouvons utiliser None place. À un moment donné, nous supprimions les arguments None en dernière position dans Fable2Babel, il semble que nous le fassions maintenant dans FSharp2Fable, mais nous pouvons ajouter une vérification supplémentaire ici : https://github.com/fable-compiler/ Fable/blob/c54668b42b46c0538374b6bb2e283af41a6e5762/src/Fable.Transforms/Fable2Babel.fs#L1082 -L1095

BTW, désolé de ne pas avoir vérifié ce PR en profondeur 😅 Mais s'il ne casse pas les tests, nous pourrions le fusionner bientôt en tant que POC comme nous l'avons fait avec PHP. C'est parce que je prévois (avec le temps) de faire une refactorisation pour faciliter le ciblage de plusieurs langues avec Fable, et ce sera probablement plus facile si Python est déjà dans la branche next pour cela. Nous évitons également d'avoir à synchroniser trop de branches divergentes (main, next, python).

Ok @alfonsogarciacaro , je vais voir si je peux générer None au lieu de null. Nous pourrions le fusionner bientôt. Il casse encore certains des tests Python existants, mais il n'en reste que quelques-uns maintenant et je devrais les régler d'ici une semaine environ. Vous devez également créer Fable.Core.PY.fs avec les émissions nécessaires. La réécriture de Babel2Python -> Fable2Python a été plus difficile que prévu mais je me rapproche enfin pour être de nouveau sur la bonne voie. Je vais nettoyer un peu le PR et le préparer à être fusionné.

@alfonsogarciacaro J'ai corrigé tous les tests sauf un. C'est cependant lié au même vieux problème que j'ai eu avec Babel 😱 J'ai besoin d'un moyen de savoir si Fable.Get est utilisé sur un type AnonymousRecord car ils se transforment en Python dict, et j'ai besoin d'utiliser un indice c'est- [] dire l'accès .dotted . Le problème est que le côté gauche peut être n'importe quoi, objet, appel de fonction, ... donc c'est difficile à savoir. Existe-t-il un meilleur moyen. Devrions-nous avoir un autre GetKind pour les enregistrements anonymes, ou quelle est la meilleure façon de le faire ?

Je vois celui-ci dans FSharp2Fable. Comment puis-je savoir plus tard que le FieldGet a été généré par un AnonRecord ?

    // Getters and Setters
    | FSharpExprPatterns.AnonRecordGet(callee, calleeType, fieldIndex) ->
        let r = makeRangeFrom fsExpr
        let! callee = transformExpr com ctx callee
        let fieldName = calleeType.AnonRecordTypeDetails.SortedFieldNames.[fieldIndex]
        let typ = makeType ctx.GenericArgs fsExpr.Type
        return Fable.Get(callee, Fable.FieldGet(fieldName, false), typ, r)

Comme nous en avons parlé dans Discord @dbrattli , je pense que vous pouvez vérifier le type de l'appelé pour voir s'il s'agit d'un enregistrement anonyme. Mais si vous avez besoin d'une approche plus systématique, il devrait être possible d'ajouter plus d'informations dans FieldGet , bien qu'à ce stade, nous devrions probablement déclarer un enregistrement séparé pour minimiser les changements de rupture et éviter toute confusion avec d'autres champs booléens (F# devrait avoir noms de champ de cas d'union plus stricts btw):

type FieldGetInfo =
    { Name: string
      IsMutable: bool
      IsAnonymousRecord: bool }

type GetKind =
    | FieldGet of info: FieldGetInfo
    | ...

Merci @alfonsogarciacaro. Ça a marché! ??

Prochain sujet:

testCase "Map.IsEmpty works" <| fun () ->
    let xs = Map.empty<int, int>
    xs.IsEmpty |> equal true
    let ys = Map [1,1; 2,2]
    ys.IsEmpty |> equal false

Pour JS, cela se compile en :

Testing_testCase("Map.isEmpty works", () => {
    Testing_equal(true, isEmpty_1(ofSeq([], {
        Compare: (x_1, y_1) => compare(x_1, y_1),
    })));
    Testing_equal(false, isEmpty_1(ofSeq([[1, 1]], {
        Compare: (x_2, y_2) => comparePrimitives(x_2, y_2),
    })));
})

Notez que ofSeq est appelé avec deux arguments. Cependant, ofSeq ne prend qu'un seul argument :

let ofSeq elements =
    Map<_, _>.Create elements
export function ofSeq(elements) {
    return FSharpMap_Create(elements);
}

Pourquoi cette expression d'objet avec Compare ajoutée lorsque ofSeq est appelé ?

Hum, il faut que je me penche sur ça. Cela ressemble à un bogue, ofSeq devrait accepter un comparateur (des fonctions similaires comme MapTree.ofSeq et Set.ofSeq font). La configuration est un peu compliquée, donc peut-être qu'à un moment donné, les choses se sont désynchronisées : nous avons un fichier nommé ReplacementsInject.fs qui est utilisé par Replacements pour indiquer quelles méthodes ont besoin d'une injection d'arguments. Il existe un script quelque part pour générer ce fichier automatiquement, mais cela fait un moment que nous ne l'avons pas utilisé et je ne sais pas si cela fonctionne avec la dernière version de FCS. Je vais vérifier, merci de l'avoir signalé !

J'ai ajouté un correctif temporaire. Prêt pour examen https://github.com/fable-compiler/Fable/pull/2345

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

Questions connexes

MangelMaxime picture MangelMaxime  ·  3Commentaires

stkb picture stkb  ·  3Commentaires

rommsen picture rommsen  ·  3Commentaires

krauthaufen picture krauthaufen  ·  3Commentaires

alfonsogarciacaro picture alfonsogarciacaro  ·  3Commentaires