Fable: Python-Unterstützung für Fable

Erstellt am 4. Jan. 2021  ·  54Kommentare  ·  Quelle: fable-compiler/Fable

Beschreibung

Dieses Thema ist eine Diskussion über die Python- Unterstützung für Fable. Der derzeit von Fable verwendete Babel AST ist nahe genug, um Python-Code zu generieren. Vor allem jetzt, wo Klassen unterstützt werden.

Ein POC wurde erstellt, um zu zeigen, dass dies möglich ist. Allerdings ist viel Arbeit nötig, um:

  1. Fügen Sie eine anständige Unterstützung für Python hinzu
  2. Machen Sie Fable JS-unabhängiger und unterstützen Sie mehrere Zielsprachen.

Die Expression- Bibliothek ermöglicht funktionale Programmierung in Python, inspiriert von F#. Es bietet eine eigene Implementierung von Datentypen wie option , result , seq , map , list (genannt FrozenList), Postfach Prozessor, ... Es sollte sich also als Python-Äquivalent der Fable-Bibliothek eignen. In Python verwenden Sie Expression, um F#'ischen Code zu erstellen. In Fable generieren Sie Python-Code mit Expression.

Anwendungsfälle:

  • F# läuft in einem Jupyter-Notebook mit Python-Kernel, dh ähnlich wie bei zB Hy und Calysto Hy .
  • F#-Skripterstellung mit Python für weniger Reibung
  • Einfachere Unterstützung von eingebetteten Umgebungen wie micro:bit und Rasberry PI .
  • Datenmodell zwischen F#- und Python-Code teilen

Dinge zu besprechen und zu entscheiden:

  • Sollte Python für F# pythonischer sein als .NET? Es gibt bereits .NET-Unterstützung für Jupyter, die wir nicht versuchen sollten, zu ersetzen oder mit ihr zu konkurrieren. Fable for Python sollte vielleicht auf Python anstatt auf .NET abzielen (wenn möglich) und eine natürlichere Lösung für Python-Entwickler sein, die F# suchen.
  • Sollten wir versuchen, mit Babel AST kompatibel zu sein oder sollten wir von unserem eigenen Python AST abweichen. Das ist vielleicht unvermeidlich, aber Babel gibt uns einen guten Ausgangspunkt.
  • Sollten wir für Fable (wie bei Peeble ) oder sollten wir innerhalb von Fable bleiben und Fable mehrere Sprachen unterstützen. Unser eigenes Repo gibt Freiheit und Geschwindigkeit (indem wir uns nicht um JS kümmern müssen), aber wir riskieren, im Laufe der Zeit zurückgelassen und überholt zu werden. Mein Gefühl ist, dass wir ein Teil von Fable werden sollten.
  • Python-Basistypen unterscheiden sich auch von F#. ZB int hat eine beliebige Länge. Sollten wir .NET-(Maschinen-)Typen emulieren, um kompatibel zu sein, oder sollten wir den Python-Typ int als F# int verfügbar machen?

Installation des POC

Der Quellcode des POC lebt derzeit hier:

  1. Installieren Sie das neueste Python 3.9 von https://www.python.org oder brew install [email protected] auf dem Mac. Beachten Sie, dass Python auf Ihrem System möglicherweise als python , python3 oder beides verfügbar ist.
  2. Klonen Sie beide Repositorys und wechseln Sie zum Zweig python .
  3. Fabelbibliothek für Python erstellen: dotnet fsi build.fsx library-py
  4. So führen Sie Tests durch: dotnet fsi build.fsx test-py
  5. Bearbeiten Sie in Fable QuickTest.fs in einen sehr einfachen F#-Code. Führen Sie dann dotnet fsi build.fsx quicktest aus dem obersten Verzeichnis aus.

Jetzt können Sie sowohl Fable- als auch Expression-Code bearbeiten und der Beispielcode wird bei Bedarf neu kompiliert. Beachten Sie, dass Sie nach dem Ändern des Fable-Codes eine zusätzliche Speicherung in QuickTest.fs benötigen, um die Neukompilierung auszulösen. Die Python-Datei wird als quicktest.py verfügbar sein. Eine schöne Demo ist es, F#, JS und Python gleichzeitig in vscode anzuzeigen. Dann sehen Sie, wie der Code transformiert wird, wenn Sie die F#-Datei speichern:

Screenshot 2021-01-15 at 13 18 09

Sie können den generierten Python-Code im Terminal ausführen:

$ python3 quicktest.py

Links

Ein paar relevante Links:

Fühlen Sie sich frei, mit Diskussionen, Kommentaren, Ideen und Code beizutragen 😍

Hilfreichster Kommentar

Das ist tolle Arbeit @dbrattli! 👏 👏 👏 Meine ursprüngliche Idee mit Fable war es, eine Möglichkeit zu bieten, F# einfach in andere Sprachen zu kompilieren. Im Guten wie im Schlechten hat sich Fable mit JS als Hauptfokus weiterentwickelt, so dass es einige Arbeit erfordern würde, es sprachunabhängiger zu machen, aber ich wäre dafür offen. Einige Anmerkungen zu Ihren Fragen:

  • Gleiches oder anderes Repo? Ich wäre dafür, so viel gemeinsamen Code wie möglich beizubehalten. Die Verwendung verschiedener Repos wird wahrscheinlich dazu führen, dass sie ziemlich schnell divergieren (es ist bereits schwierig, den Zweig mit der Angebotsunterstützung synchron zu halten), aber wir müssen den Code auch modular genug gestalten, damit wir nicht überall Bedingungen haben, die von der Zielsprache abhängen. Eine Zwischenlösung wäre zu versuchen, so viel Code wie möglich in einen Fable "Nucleus" zu extrahieren (wir können Core 😉 nicht verwenden) und dann unterschiedliche Repos für die Sprachimplementierungen zu verwenden.

  • Babel AST: Dies wurde verwendet, weil wir uns ursprünglich zum Drucken des Codes auf Babel verlassen haben. In einer idealen Welt sollte es jetzt, wo wir unseren Drucker haben, möglich sein, ihn zu entfernen. Aber es gibt eine Menge Arbeit in der Fable-to-Babel-Phase, also wäre es schwierig. Aus dem gleichen Grund wäre es vorzuziehen, so viel Code wie möglich aus diesem Schritt wiederzuverwenden, um den Babel AST vielleicht etwas allgemeiner für nicht funktionale Sprachen zu machen. Einige Beispiele:

    • Wechseln Sie von ausdrucksbasiertem AST zu Ausdrucks-/Anweisungs-AST. Für JS beinhaltet dies das Verschieben von Variablendeklarationen bei Bedarf an den Anfang der Funktion, um zu viele IIFEs zu vermeiden. Ich denke, mit Python wird es ähnlich sein.
    • Transformieren des Mustervergleichs (DecisionTree) in if/switch-Anweisungen.
    • Tail-Call-Optimierung. Dies geschieht mit JS-beschrifteten Schleifen. In Python müssten wir eine andere Technik verwenden, um sicherzustellen, dass wir nicht von einer verschachtelten Schleife abbrechen.
    • Konvertieren von Importen in eindeutige Identifikatoren (dies könnte bei Bedarf direkt im Fable AST erfolgen).
  • F# pythonischer machen? Bei Fable habe ich immer versucht, ein Gleichgewicht zu finden, bei dem die .NET-Semantik manchmal geopfert wird, um Overhead zu vermeiden. Zum Beispiel verwenden wir einfach die JS-Nummer, anstatt einen bestimmten Typ für ints zu haben (obwohl wir das für Longs tun), dies würde für Python int nehme ich an. Es wurden jedoch viele Beiträge geleistet, um die .NET-Semantik an bestimmten Stellen zu respektieren, um unerwartete Ergebnisse zu vermeiden, beispielsweise bei ganzzahligen Divisionen oder expliziten numerischen Konvertierungen.
    Wie auch immer, wenn Sie "pythonischer" sagen, meinen Sie Codestil oder native APIs? Fable versucht nicht, den größten Teil von .NET BCL zu unterstützen, aber am Ende müssen wir die allgemeinen Funktionen/Operatoren unterstützen, da Entwickler daran gewöhnt sind (auch Neulinge, weil sie das in Tutorials finden würden). Es ist gut, in jedem Fall die Möglichkeit zu geben, die nativen APIs zu verwenden ( Fable.Extras ist in diesem Sinne ein guter Schritt). JS.console.log zum Beispiel formatiert Objekte normalerweise besser als printfn , aber wir müssen letzteres unterstützen, weil es das ist, was Entwickler am häufigsten verwenden.

Alle 54 Kommentare

Das ist tolle Arbeit @dbrattli! 👏 👏 👏 Meine ursprüngliche Idee mit Fable war es, eine Möglichkeit zu bieten, F# einfach in andere Sprachen zu kompilieren. Im Guten wie im Schlechten hat sich Fable mit JS als Hauptfokus weiterentwickelt, so dass es einige Arbeit erfordern würde, es sprachunabhängiger zu machen, aber ich wäre dafür offen. Einige Anmerkungen zu Ihren Fragen:

  • Gleiches oder anderes Repo? Ich wäre dafür, so viel gemeinsamen Code wie möglich beizubehalten. Die Verwendung verschiedener Repos wird wahrscheinlich dazu führen, dass sie ziemlich schnell divergieren (es ist bereits schwierig, den Zweig mit der Angebotsunterstützung synchron zu halten), aber wir müssen den Code auch modular genug gestalten, damit wir nicht überall Bedingungen haben, die von der Zielsprache abhängen. Eine Zwischenlösung wäre zu versuchen, so viel Code wie möglich in einen Fable "Nucleus" zu extrahieren (wir können Core 😉 nicht verwenden) und dann unterschiedliche Repos für die Sprachimplementierungen zu verwenden.

  • Babel AST: Dies wurde verwendet, weil wir uns ursprünglich zum Drucken des Codes auf Babel verlassen haben. In einer idealen Welt sollte es jetzt, wo wir unseren Drucker haben, möglich sein, ihn zu entfernen. Aber es gibt eine Menge Arbeit in der Fable-to-Babel-Phase, also wäre es schwierig. Aus dem gleichen Grund wäre es vorzuziehen, so viel Code wie möglich aus diesem Schritt wiederzuverwenden, um den Babel AST vielleicht etwas allgemeiner für nicht funktionale Sprachen zu machen. Einige Beispiele:

    • Wechseln Sie von ausdrucksbasiertem AST zu Ausdrucks-/Anweisungs-AST. Für JS beinhaltet dies das Verschieben von Variablendeklarationen bei Bedarf an den Anfang der Funktion, um zu viele IIFEs zu vermeiden. Ich denke, mit Python wird es ähnlich sein.
    • Transformieren des Mustervergleichs (DecisionTree) in if/switch-Anweisungen.
    • Tail-Call-Optimierung. Dies geschieht mit JS-beschrifteten Schleifen. In Python müssten wir eine andere Technik verwenden, um sicherzustellen, dass wir nicht von einer verschachtelten Schleife abbrechen.
    • Konvertieren von Importen in eindeutige Identifikatoren (dies könnte bei Bedarf direkt im Fable AST erfolgen).
  • F# pythonischer machen? Bei Fable habe ich immer versucht, ein Gleichgewicht zu finden, bei dem die .NET-Semantik manchmal geopfert wird, um Overhead zu vermeiden. Zum Beispiel verwenden wir einfach die JS-Nummer, anstatt einen bestimmten Typ für ints zu haben (obwohl wir das für Longs tun), dies würde für Python int nehme ich an. Es wurden jedoch viele Beiträge geleistet, um die .NET-Semantik an bestimmten Stellen zu respektieren, um unerwartete Ergebnisse zu vermeiden, beispielsweise bei ganzzahligen Divisionen oder expliziten numerischen Konvertierungen.
    Wie auch immer, wenn Sie "pythonischer" sagen, meinen Sie Codestil oder native APIs? Fable versucht nicht, den größten Teil von .NET BCL zu unterstützen, aber am Ende müssen wir die allgemeinen Funktionen/Operatoren unterstützen, da Entwickler daran gewöhnt sind (auch Neulinge, weil sie das in Tutorials finden würden). Es ist gut, in jedem Fall die Möglichkeit zu geben, die nativen APIs zu verwenden ( Fable.Extras ist in diesem Sinne ein guter Schritt). JS.console.log zum Beispiel formatiert Objekte normalerweise besser als printfn , aber wir müssen letzteres unterstützen, weil es das ist, was Entwickler am häufigsten verwenden.

Ich liebe diese Idee. Besonders freue ich mich darauf, Python-Bibliotheken in F# zu verwenden. Idealerweise sollten wir die Möglichkeit haben, entweder reine Python-Bibliotheken oder SciSharp zu verwenden - sie haben in der Regel eine fast exakte API wie Python (derselbe F#-Code wird in Python- oder .NET-Kontext ausgeführt!)

Ein solcher F#-Code kann mit Fable in Python kompiliert und dann in Notebooks-Kerneln (Jupyter oder .NET Interactive) verwendet werden, wodurch der Analyse-Workflow zu einer echten domänengesteuerten Einrichtung wird (ich könnte das alles als "Killing-Funktion" betrachten).

.NET Interactive ist sehr erweiterbar, das Ausführen von Fable im F#-Kernel ist sehr einfach (ich habe einen funktionierenden PoC).

Eine andere Sache ist, dass ich vorhersagen werde, dass MS bald etwas Ähnliches für das gesamte .NET enthüllen wird (die Vorhersage basiert nur auf Aktivitäten und Zielen, die .NET Interactive angehen, also kann ich mich irren). Selbst wenn dies der Fall ist, möchten wir, dass Fable es separat hat, um ein vollständiges JS/F#/Python-Arsenal in einer funktionalen Welt zu haben. MS-Ansatz wäre eher ein Blazor-Geschmack (glaube ich).

Danke, das ist ein tolles Feedback @alfonsogarciacaro , @PawelStadnicki . Was ich meinte war, wenn wir dem Programmierer das "Python"-Gefühl vermitteln lassen wollen, indem wir mehr von der Python-Standardbibliothek offenlegen, anstatt zu versuchen, .NET neu zu implementieren (dann sollten sie wahrscheinlich stattdessen einfach .NET verwenden).

Ich denke, wir könnten das Beste aus beiden Welten haben und den Benutzer entscheiden lassen, was importiert wird oder nicht (zB datetime aus Python oder DateTime aus .NET verwenden). Sie müssen sich also nicht entscheiden, sondern implementieren einfach das, was wir am meisten wollen (on-demand). Aber ich vermute, dass das gleiche Problem für Fable JS relevant ist. Sie wissen, dass Sie eine Web-App implementieren, und erwarten wahrscheinlich nicht, dass .NET vollständig verfügbar ist. Sie hätten lieber einen besseren Zugang zum JS-Ökosystem.

Sie müssen sich also nicht im Voraus entscheiden. Lasst uns die Ärmel hochkrempeln und loslegen 😄 Es wird toll, Teil von Fable zu sein und dieses Thema dabei zu haben, um Fragen zu stellen, zB zu Verwandlungen etc. Es gibt viel zu lernen.

PS: Expression hat übrigens auch einen Tail-Call-Decorator, den wir verwenden können (sowohl sync als auch async). Hier ist ein Beispiel Verwendung in aioreactive.

Lässt mich an #1601 denken.

Ich frage mich, ob es eine gute Möglichkeit gibt, Fable so leistungsfähig zu machen, dass diese als Backend-"Plugins" von Drittanbietern ausgeführt werden können (denken Sie an LLVM, aber spezifisch für F#). Endlose Möglichkeiten.

Wir könnten versuchen, den Babel AST allgemeiner (und typisierter) zu machen, damit er leicht in C-ähnliche Sprachen umgewandelt/gedruckt werden kann. Wenn ich jedoch bei der Entwicklung von Fable etwas gelernt habe, ist, dass das Schreiben eines einfachen F#-Compilers in eine andere Sprache (mehr oder weniger) einfach ist, aber eine gute Entwicklungserfahrung und Integration bietet, also die Vorteile von F# abzüglich der Reibung mit dem fremde Ökosysteme wiegen mehr als die Entwicklung in der Muttersprache ist ziemlich schwer.

Was @alfonsogarciacaro sagte. Ein Teil dieser Herausforderung besteht darin, einen größeren Teil der Fable-BCL-Implementierung auf F# zu migrieren, damit sie nicht für jede Sprache neu geschrieben werden muss, während gleichzeitig eine akzeptable native Leistung für den generierten Code beibehalten wird, was selbst mit JavaScript nicht trivial ist.

Interessant. Ich wusste nicht, dass wesentliche Teile der Fable BCL-Implementierung in (vermutlich) Javascript geschrieben wurden. Ich nahm an, es wäre schon mehr F#. Ich bezweifle nicht, dass es dafür gute Gründe gibt – warum war es so einfacher?

@jwosty Die (nicht so kurz) Antwort lautet: aus Leistungsgründen, und die Differenz zwischen F # -Typen vs Typen nativ vom Browser unterstützt zu überbrücken, und für eine bessere Integration des generierten Codes innerhalb des JavaScript - Ökosystems.

Auch .NET BCL hat eine sehr große API Oberfläche, auf der Oberseite des FSharp.Core , so ist es schwer , diese Bemühungen ohne größeres Team zu halten (obwohl @alfonsogarciacaro irgendwie zu halten verwaltet und entwickeln sie in erster Linie von sich selbst, das ist erstaunlich und lobenswert).

Ich denke, was ich damit sagen möchte, ist, dass Beiträge (und Ideen) sehr willkommen sind und geschätzt werden.

Die (nicht so kurze) Antwort lautet: aus Leistungsgründen und um den Unterschied zwischen F#-Typen und nativ vom Browser unterstützten Typen zu überbrücken und um den generierten Code besser in das JavaScript-Ökosystem zu integrieren.

Ich kann sehen, dass.

Ich denke, was ich damit sagen möchte, ist, dass Beiträge (und Ideen) sehr willkommen sind und geschätzt werden.

Mit Sicherheit! Ich verstehe, wie viel Mühe darauf verwendet wird, so große und wichtige Projekte aufrechtzuerhalten, und ich weiß den Schweiß und die Tränen zu schätzen, die ihr da hineingesteckt habt. Ich halte auf jeden Fall meine Augen offen für Orte, an denen ich etwas beitragen kann, da Fable in letzter Zeit sehr sehr sehr nützlich für mich war und ich gerne etwas zurückgeben würde.

Ich könnte ein wenig Hilfe gebrauchen. Ich verwende derzeit das QuickTest-Projekt für die Entwicklung (dh dotnet fsi build.fsx quicktest ). Wie drucke ich den Babel AST während der Entwicklung? ( Update : Ich habe ASTViewer gefunden).

Mein aktuelles Problem ist, dass Python kein mehrzeiliges Lambda (Pfeilfunktionen) unterstützt, sodass das Lambda transformiert und in eine benannte Funktion extrahiert und stattdessen diese Funktion aufgerufen werden muss. Alle Tipps hier wären dankbar. Z.B:

let fn args cont  =
    cont args

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

Muss umgewandelt werden in:

let fn args cont  =
    cont args

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

Wie soll ich denken, wenn ich einen Aufrufausdruck durch mehrere Anweisungen ersetze, da ich beim Umwandeln des Funktionsaufrufs mit Argumenten ihn auf einer höheren Ebene ersetzen oder eine Art Blockanweisung verwenden muss, z. B. if true then ... Irgendwelche Ideen?

@dbrattli Siehe auch diesen Printer , der auch einige der Eigenschaften druckt.

Ich wusste nicht, dass wesentliche Teile der Fable BCL-Implementierung in (vermutlich) Javascript geschrieben wurden. Ich nahm an, es wäre schon mehr F#. Ich zweifle nicht daran, dass es dafür gute Gründe gibt – warum war es so einfacher?

Ja, wie @ncave sagte, war der Hauptgrund, mehr Standard-JS zu generieren. Zum Beispiel wurden in Funscript alle FSharp.Core-Ersetzungen in F# geschrieben, aber mit Fable wollte ich wenn möglich in JS-Standards integrieren (zB mit JS-Iteratoren anstelle von .NET IEnumerable), was viele Hühner-und-Ei-Probleme mit sich brachte war am Anfang einfacher in JS/Typescript zu schreiben. Am Anfang hatte ich auch die Idee, fable-library als separates Paket zu veröffentlichen, das in JS-Projekten verwendet werden könnte, aber ich habe es schnell verworfen.

In Fable 2 haben wir versucht, einige der Module auf F# zu portieren. Dies war sehr vorteilhaft, da es das Dogfooding erhöht und uns die Verbesserungen in FSharp.Core (wie in letzter Zeit bei Karten und Sets) leicht wiederverwenden lässt. Es gibt jedoch hier und da noch einige Hacks, um die Henne-Ei-Probleme zu umgehen. Und lass uns nicht über das große Monster sprechen, das das Ersatzmodul ist :)

Auch wie @ncave sagt, ist die Pflege dieser Bibliothek ein riesiges Unterfangen, deshalb zögere ich immer, die Unterstützung für BCL zu erhöhen, um die Wartungsfläche zu reduzieren, aber ich hatte großes Glück, dass es mehrere großartige Beiträge zur Bibliothek gegeben hat von Anfang an.

@dbrattli Über das Drucken des AST habe ich das ASTViewer-Tool in Fable Repo schon eine Weile nicht mehr verwendet. Bessere Optionen sind die von @ncave oder der Fantomas-Visualisierer, die ich kürzlich verwende, wenn ich den F# AST überprüfen muss (stellen Sie einfach sicher, dass Sie Typed AST anzeigen auswählen). Leider haben wir noch keine spezifischen Drucker für Fable oder Bable AST. Im Fall von Fable AST funktioniert ein einfaches printfn "%A" , da es nur Vereinigungen und Aufzeichnungen sind, obwohl die Standortinformationen etwas verrauscht sind.

Über das Extrahieren der Lambdas. Es ist möglich, erfordert aber etwas Arbeit. Ich denke, wir können etwas Ähnliches wie bei Importen machen, das heißt, sie beim Durchlaufen des AST sammeln und ihnen eine eindeutige Kennung zuweisen. Später können wir die Funktionen einfach

Danke für tolle Infos @alfonsogarciacaro , mein erster PoC änderte gerade Babel.fs / Fable2Babel.fs , BabelPrinter.fs usw. Ich habe jetzt mit einem separaten Python AST von vorne angefangen, dh Python.fs , PythonPrinter.fs . Dann werde ich versuchen, Babel2Python.fs hinzuzufügen, um von Babel AST in Python AST umzuwandeln, da dies möglicherweise einfacher ist als die Konvertierung von Fable AST (aber auch mit Fable2Python.fs wenn die Konvertierung von Babel endet schwierig). Auf diese Weise werde ich die vorhandene Codebasis nicht zu sehr berühren und meine eigene Transformation haben, bei der ich versuchen werde, das Lambda-Rewrite durchzuführen.

Das macht Sinn. Eine Schwierigkeit besteht darin, dass Babel AST nicht aus DUs besteht, daher ist es etwas schwieriger, es mit Musterabgleich zu durchlaufen. Vielleicht könnte #2158 helfen?

@alfonsogarciacaro Ich glaube, ich habe es geschafft, das Umschreiben von korrigieren , dass ich denke (hoffe), dass es tatsächlich funktionieren könnte. Die Idee ist, dass jeder Ausdruck ( TransformAsExpr ) auch eine Liste von Anweisungen zurückgibt (die verkettet und bis zur letzten Liste von Anweisungen (last-statement-level) weitergegeben werden müssen zurückgegebene Anweisungen (func-def) werden herausgehoben und vor andere Anweisungen geschrieben, so dass ein Pfeil- oder Funktionsausdruck einfach ein name-expression, [statement ] zurückgibt, wobei der Pfeil/Funktionsausdruck in a umgeschrieben wird -Anweisung und wird zusammen mit dem Namensausdruck zurückgegeben.

module QuickTest

let fn args cont  =
    cont args

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

Erzeugt das folgende JS:

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;
});

Was wiederum das folgende Python generiert:

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)

Ich dachte, ich könnte Probleme bei der Handhabung von Schließungen haben, aber in den meisten (allen?) Fällen werden die Schließungen auch aufgehoben, zB:

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

Wird umgeschrieben in:

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)

Wie auch immer, es ist ein guter Anfang 😄

Ich habe das gleiche für PHP gemacht (und es läuft CrazyFarmers auf BGA ), also könnten wir die Bemühungen hier vielleicht kombinieren?

https://github.com/thinkbeforecoding/peeble

@ncave Haben Sie übrigens Tipps zur Verwendung von Fable für die interaktive Nutzung wie Jupyter-Notebooks? Das Problem ist, dass Jupyter Ihnen ein Codefragment (Zelle) gibt und der Kernel dieses Fragment zusammen mit allen vorhandenen lokalen Deklarationen aus früheren Anweisungen kompilieren muss.

Fable ist dafür nicht optimal, aber ich habe einen PoC erstellt, in dem ich ein geordnetes Wörterbuch früherer Anweisungen/Deklarationen (let, type, open) aufbewahre und ein gültiges F#-Programm erstelle, indem ich sie zusammen mit dem letzten Codefragment in der richtigen Reihenfolge verwende. Alle Deklarationen im Codefragment werden vor der Ausführung aus dem Diktat entfernt und danach hinzugefügt. Das scheint zu funktionieren, aber ich frage mich, ob es vielleicht einen besseren Weg gibt?

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

Dann habe ich eine Fable-Cli, die im Hintergrund läuft, die das neu kompiliert, wenn Fable.fs aktualisiert wird.

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

@dbrattli Klingt vernünftig, wenn die Fragmente klein sind. Bei größeren kann die Verwendung einer separaten Datei für jede Zelle (Fragment) die Dinge möglicherweise beschleunigen, da Fable Caching verwendet, um unnötige Arbeit an Dateien zu überspringen, die sich nicht geändert haben.

Nur FYI, wenn Sie mehr Kontrolle benötigen, können Sie auch Fable als verwenden Bibliothek , nicht nur CLI (siehe fable-standalone , und ein Verwendungsbeispiel ).

1) Ein Problem, das ich habe, ist, dass das Fable AST kein Throw / Raise hat. Dies wird also in einen Emit-Ausdruck umgewandelt, der für mich schwer zu analysieren ist. Oder zumindest scheint es unnötig, wenn Babel ein ThrowStatement hat. Gibt es einen guten Grund, warum es nicht verwendet wird, oder sollte ich versuchen, es zu beheben? @alfonsogarciacaro

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

let result1 = divide1 100 0

Erzeugt:

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

export const result1 = divide1(100, 0);

Wobei throw matchValue ein EmitExpression .

2) Sollten wir Fable AST ein ThisExpr hinzufügen, damit ich feststellen kann, ob this als dieses Schlüsselwort verwendet wird. Ich kann nicht sicher sein, dass Fable.IdentExpr auf this ob es das this-Schlüsselwort ist oder nicht, da diese in Python in self .
https://github.com/fable-compiler/Fable/blob/nagareyama/src/Fable.Transforms/Fable2Babel.fs#L792

Das ist lustig, Throw war eigentlich in Fable 2 AST, aber ich habe es für Fable 3 entfernt, um das AST zu vereinfachen, da es nur an einer Stelle verwendet wurde, aber ich denke, wir können es wieder hinzufügen.

Bei "diesem" ist es schwierig. Ich muss es noch einmal überprüfen, da dies in F# AST in Konstruktoren oder Methoden anders dargestellt wird (es gibt einige Kommentare dazu im Code verstreut). Es gibt eine IsThisArgument-Eigenschaft für Bezeichner in Fable AST, aber lassen Sie mich überprüfen, ob dies nur für das erste Argument von getrennten Mitgliedern gilt oder ob es auch für "tatsächlich dies" funktioniert.

@alfonsogarciacaro Ich könnte ein wenig Hilfe bei benutzen , wie „intern“ vs „user“ kompiliert zu behandeln Code zB Exception haben zB .message , aber wie soll ich das übersetzen? Kann ich sicher sein, dass vom Benutzer kompilierter Code niemals ein .message und stattdessen immer zB als Test__Message_229D3F39(x, e) enden wird, damit er sich nie einmischt? Oder muss ich eine abgefangene Ausnahme zB in ein JsException Objekt einschließen und eine .message Eigenschaft hinzufügen, nur um sicher zu gehen? Wie macht Fable das von F# nach Babel? Behalten Sie die Typen im Auge, oder?

Beispiel: Wie wird aus ex.Message.StartsWith in F# ex.message.indexOf in JS? Ich muss dies in Python in str(ex).index (oder die Objekte umhüllen).

Tatsächlich haben wir ein Problem mit F#-Ausnahmen (wie in: Ausnahme Foo of int * int), die ich vor der Veröffentlichung von Fable 3 nicht behoben habe 😅 System.Exception wird als JS-Fehler übersetzt, daher haben sie ein .message-Feld. Aber F#-Ausnahmen leiten sich aus Leistungsgründen nicht von JS Error ab (ich denke, der Versuch, auf .Message zuzugreifen, schlägt gerade fehl). Irgendwo wird darüber diskutiert. Für Ihren Fall und für allgemeine Ausnahmen können Sie davon ausgehen, dass Fable vorerst auf die Eigenschaften .message und .stack zugreift.

Wie soll die Sprachauswahl in Fable sein? Heute können wir zB TypeScript mit --typescript auswählen. Wir könnten also Python mit --python auswählen. Aber sollten wir dann aufhören, die .js generieren? Das sollten wir wahrscheinlich tun, da das generierte JS möglicherweise nicht gültig ist, wenn zB Emit verwendet wird. Aber manchmal wollen wir den JS sehen. Sollten wir eine Option für --javascript haben oder sollte der Benutzer ohne --python , um JavaScript zu erhalten? @alfonsogarciacaro

@dbrattli --typescript generiert nur .ts Dateien, also kann --python nur .py Dateien generieren, wenn Sie es vorziehen.
Aber technisch schließen sie sich alle gegenseitig aus. Sollten wir stattdessen einen neuen --language Schalter mit den Werten JavaScript/TypeScript/Python einführen?

@ncave Ja, ich denke, es ist eine gute Idee, etwas wie [-lang|--language {"JavaScript"|"TypeScript"|"Python"}] wobei JavaScript Standard sein könnte. Ich habe bereits damit begonnen, eine Spracheigenschaft für den Compiler hinzuzufügen, damit ich auch versuchen kann, die Befehlszeilenoptionen zu korrigieren? https://github.com/fable-compiler/Fable/pull/2345/files#diff -9cb94477ca17c7556e6f79d71ed20b71740376f7f3b00ee0ac3fdd7e519ac577R12

Ein gewisser Status. Die Python-Unterstützung wird besser. Viele Sprachfunktionen sind jetzt vorhanden. Die große verbleibende Aufgabe besteht darin, die Fable-Library zu portieren. Um dies zu vereinfachen, wurde die Fable-Library für Python in das Repo verschoben, damit wir die vielen Fable-Library .fs Dateien in Python konvertieren und wiederverwenden können. Viele von ihnen enthalten jedoch von JS ausgegebenen Code, der abgefangen und "manuell" behandelt werden muss. Ein Test-Setup für Python (basierend auf pytest) ist derzeit mit 43 bestandenen Tests vorhanden. Dies macht es viel einfacher, neue Funktionen zu entwickeln und hinzuzufügen (und zeigt, was funktioniert).

Fabelbibliothek für Python erstellen: dotnet fsi build.fsx library-py
Python-Tests ausführen: dotnet fsi build.fsx test-py

@dbrattli
Idealerweise sollten wir versuchen, mehr von fable-library in F# umzuwandeln. List und Seq sind bereits vorhanden, Array verwendet eine kleine Anzahl nativer Methoden, vielleicht können diese getrennt werden.

Zu einem etwas anderen Thema, was sind Ihrer Meinung nach die Hauptvorteile der Implementierung eines neuen Sprachcodegens von Babel AST statt direkt von Fable AST?

@ncave Python und JavaScript liegen ziemlich nah beieinander (imo), daher ist es relativ einfach, Babel AST umzuschreiben. Ich habe früher ein bisschen JS nach Python portiert, zB RxJS nach RxPY. Wir profitieren auch davon, dass alle (Bug-)Fixes "upstream" gemacht werden. Das heißt, wir könnten den größten Teil von Fable2Babel.fs wiederverwenden und direkt in Python transformieren, indem wir ein Fable2Python.fs , das Fable2Babel.fs und Babel2Python.fs kombiniert/reduziert. Darüber habe ich mir heute tatsächlich Gedanken gemacht. Es wird dann eher wie eine Gabelung und Bugfixes müssten höchstwahrscheinlich an beiden Stellen angewendet werden. Im Moment denke ich, dass es in Ordnung ist, Babel AST zu transformieren, aber irgendwann möchten wir vielleicht Fable AST direkt transformieren.

Das ist großartig @dbrattli! Vielen Dank für Ihre ganze Arbeit. Eigentlich hatte ich vor, den größten Teil des Fable-Codes in eine Bibliothek zu packen und die verschiedenen Implementierungen als unabhängige Dotnet-Tools freizugeben: fable(-js), fable-py (oder jeder andere Name). Vielleicht gibt uns das mehr Freiheit in den Release-Zyklen, aber ich bin auch völlig in Ordnung mit einem einzigen Dotnet-Tool, das in beide Sprachen kompilieren kann. Das kann einfacher sein, gerade am Anfang.

Über fable-library, ja, wenn möglich, anstatt die aktuellen .ts-Dateien in Python neu zu schreiben, wäre es schön, sie stattdessen nach F# zu portieren. Wir sollten versuchen, die Teile mit Emit zu isolieren und sie entweder mit #if an Python anzupassen:

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

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

    // ...
#endif

Oder wir können ein neues "mehrsprachiges" Emit-Attribut hinzufügen:

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

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

Danke @alfonsogarciacaro und @ncave , das sind tolle Ideen. Ich werde sie ausprobieren, um zu sehen, was funktioniert. Ich sehe den Vorteil der Übersetzung von fable-library in F#, also werde ich versuchen, hier zu helfen, sobald der Python-Compiler gut genug ist, um die Dateien dort zu verarbeiten. Die Verwendung von #if sollte viel helfen, und wir könnten wahrscheinlich #if s vermeiden, indem wir die Emits in sprachspezifische Bibliotheksdateien verschieben (da ich eine separate .fsproj Datei für Python habe.

@alfonsogarciacaro warum nicht stattdessen zwei Emissionsattribute haben? Emit für JS und EmitPy für Python. Der Python-Transpiler sollte ein Abklingen melden, wenn er auf Emit für JS usw. stößt.

Für meine 2 Cent mag ich die derzeit geringe kognitive Belastung, ein einziges Emit-Attribut zu haben, das einfach emittiert. Imo, was @dbrattli gerade macht, macht Sinn (andere Projektdatei für jede fable-library Sprachversion). Wir können die verschiedenen Sprachausgaben in ihren eigenen Dateien trennen, die gut definierte Schnittstellen (oder Module) implementieren, die später von der üblichen F#-Implementierung aufgerufen werden.

Ein gutes Beispiel könnte sein, die Array.Helpers in eine Schnittstelle umzuwandeln (oder einfach als Modul zu fable-library nach F# zu migrieren (sowie die --language Compileroption) in eine separate PR von dieser gehen, damit sie schneller gehen und leichter dazu beigetragen werden kann .

Das ist eine gute Idee @ncave :+1: Tatsächlich tun wir dies bereits, wenn wir nativen Code in Multiplattform-Fable-Projekten (.net und js) isolieren. Um die Dinge zu vereinfachen, würde ich wahrscheinlich einfach eine neue Native.fs Datei zu Fable.Library hinzufügen und dort nach Bedarf Untermodule haben, wie zum Beispiel:

namespace Fable.Library.Native            # or just Native

module Array = ..
module Map = ..

Wir sollten alles dorthin verschieben, was Emit oder Werte von Fable.Core.JS .

Bei der Arbeit an async versuche ich, Async.ts und AsyncBuilder.ts auf F# zu portieren, dh mit Async.fs und AsyncBuilder.fs . Aber jetzt habe ich das Problem, dass meine Testdatei mit Code:

async { return () }
|> Async.StartImmediate

generiert Python-Code (JS-Code sollte sehr ähnlich sein):

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

AsyncBuilder enthält jedoch keine Methoden, aber es gibt AsyncBuilder__Delay_458C1ECD . Also bekomme ich AttributeError: 'AsyncBuilder'-Objekt hat kein 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
...

Irgendwelche Hinweise, wie ich damit umgehen kann? @ncave @alfonsogarciacaro. Gibt es eine Möglichkeit, F#-Klassen Klassen mit Methoden generieren zu lassen oder meine Tests Code generieren zu lassen, der das statische AsyncBuilder__Delay_458C1ECD anstelle von .Delay() ?

@dbrattli Normalerweise besteht die Antwort auf das
Ist die asynchrone Unterstützung eine dieser Funktionen, die vielleicht besser für eine Übersetzung in natives Python asyncio geeignet sind?

@ncave Ok, so geht's 😄 Danke für den Einblick. Ich werde versuchen, den Builder hinter eine Schnittstelle zu stellen und zu sehen, ob es hilft (selbst wenn die F#-Builder keine Schnittstellen sind, sollte es wahrscheinlich gut funktionieren). Was async betrifft, war der Plan, natives Python-Asyncio zu verwenden ( siehe hier ), aber es gab Probleme mit python cannot reuse already awaited coroutine , also muss ich sie hinter einer Funktion faulenzen. Wie auch immer, ich habe herausgefunden, dass ich genauso gut einen task oder asyncio Builder für Python-native Waitables verwenden könnte. Die Vereinfachung von AsyncBuilder.ts hat mich dazu gebracht, es auf F# zu portieren.

Die Verwendung einer Schnittstelle zur Vermeidung von Mangel sollte funktionieren, wie @ncave sagt. Wir haben auch ein paar andere Ansätze ausprobiert:

  • Das NoOverloadSuffix Attribut: Dies verhindert, dass Fable das Überladungssuffix hinzufügt. Offensichtlich funktionieren Überladungen in diesem Fall nicht.

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

Dann können wir die Methoden mit Naming.buildNameWithoutSanitation referenzieren:

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

  • Aber danach hat @ncave eine Methode geschrieben, um mehr oder weniger verstümmelte Namen zu erstellen, wie Fable erwartet (ich erinnere mich nicht, ob es einige Einschränkungen gibt), damit Sie F#-Code normal schreiben können. Siehe zum Beispiel src/fable-library/System.Text.fs :

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

In diesem Fall müssen Sie die Klasse nur zum Wörterbuch replacedModules hinzufügen:

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


Leider gibt es noch keinen konsistenten Ansatz, um von Replacements aus in F# geschriebenen Fable-Library-Code zu verlinken. Vielleicht nutzen wir diese Gelegenheit, um uns jetzt auf eine zu einigen.

Danke für sehr interessante Einblicke @ncave und @alfonsogarciacaro. Bei Schnittstellen bin ich auf ein anderes Problem gestoßen. Wie gehe ich mit unit in Python um? Meine Schnittstelle sieht so aus:

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

Das Problem ist, dass Return Folgendes generiert:

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

Das sieht gut aus und ist wahrscheinlich gut für JS, aber in Python müssen Sie ein Argument angeben, wenn die Funktion ein Argument akzeptiert. Daher erhalten Sie eine Fehlermeldung, wenn Sie x.Return() aufrufen, wenn 'T unit . Meine erste Reaktion war eine Überladung:

abstract member Return : unit -> IAsync<unit>

aber das hat seine eigenen probleme. Meine aktuelle Lösung (die hässlich aussieht ist):

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]

Aber das erfordert, dass ich eine benutzerdefinierte Behandlung für jede generische einzelne Argumentfunktion habe. Vielleicht sollte ich stattdessen einfach jede Einzelargumentfunktion dazu bringen, null (None in Python) als Eingabe zu akzeptieren. Z.B:

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

Was wäre der richtige Weg, um mit diesem Problem umzugehen?

IIRC, für Methoden mit unit -> X Signatur haben die Aufrufe keine Argumente (wie im F# AST), aber für Lambdas oder in diesem Fall generische Argumente, die mit unit gefüllt sind, haben Aufrufe/Anwendungen ein unit Argument im Fable-AST (auch im F#-AST). Dieses Argument wird im transformCallArgs Helper des Fable2Babel-Schritts entfernt. Vielleicht könnten wir dort eine Bedingung hinzufügen und das Einheitenargument belassen, wenn die Zielsprache Python ist:

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

Hallo @ncave , @alfonsogarciacaro , ich könnte einige Eingaben zur Handhabung von [<Inject>] Parametern in Python gebrauchen. Wie wird das in PHP @thinkbeforecoding gemacht? ZB Funktionen wie (in 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

Mein Code generiert den Parameter cons und ist für Python erforderlich.

map(fn ar)

Ist es optional für JS? Wie kann ich das Attribut im Code erkennen und optional machen? Z.B

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

PHP ist auch bei optionalen Parametern explizit. Ich musste dem Argumentmodell ein IsOptional-Flag hinzufügen.

Es gibt zwei Verwendungen für das Inject Attribut in Fable. Einer ist hauptsächlich experimentell (um irgendwie Typklassen zu emulieren und auch um zu vermeiden, dass eine Funktion inline eingebunden werden muss, um generische Typinformationen aufzulösen) und Sie sollten sich nicht zu sehr darum kümmern.

Die andere Verwendung ist intern in Fable-Library-Methoden, die einige zusätzliche Informationen benötigen, die von einem zur Kompilierzeit aufgelösten Argument übergeben werden:

  • Funktionen, die ein Array erstellen, müssen wissen, ob es sich um ein dynamisches JS-Array oder ein typisiertes Array handelt.
  • Set- und Map-Konstruktoren und andere Helfer, die einen Vergleicher erhalten müssen, um zu wissen, wie ein Typ verglichen werden muss (ähnlich für Funktionen, die einige Durchschnittsberechnungen durchführen).

Diese Funktionen werden jedoch nicht direkt aufgerufen, sodass Fable das Attribut Inject "nicht sehen" kann. Aus diesem Grund verwenden wir die Datei ReplacementsInject.fs , die

Diese Datei wurde am Anfang automatisch mit diesem Skript erstellt , das erkennt, welche Funktionen in Fable.Library das letzte Argument mit Inject verziert haben. Aber ich denke, wir haben irgendwann aufgehört zu aktualisieren und IIRC die letzten Updates für die ReplacementInjects. Datei wurden manuell erstellt.

Wenn ich das weiß, kann ich die IsOptional-Informationen wahrscheinlich loswerden ... Ich werde nachsehen

@alfonsogarciacaro Es scheint ein Problem mit injectArg für Code wie diesen zu geben:

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

Hier wird Array.map den Konstruktor nicht injiziert. Der Code wird ohne injiziertes Argument in JS transpiliert:

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

Ist das ein Fehler? Dieser Code funktioniert in JS (zufällig?), aber nicht für Python.

Oh, tut mir leid! Völlig vergessen, aber wir haben eine "Optimierung", bei der der Array-Konstruktor nicht für das "Standard"-JS-Array injiziert wird: https://github.com/fable-compiler/Fable/blob/4ecab5549ab6fcaf317ab9484143420671ded43b/src/Fable.Transforms/ Ersatz.fs#L1005 -L1009

Wir verwenden dann einfach global Array wenn nichts übergeben wird: https://github.com/fable-compiler/Fable/blob/4ecab5549ab6fcaf317ab9484143420671ded43b/src/fable-library/Array.fs#L25 -L28

Ich bin mir nicht ganz sicher, warum ich dies getan habe, aber ich denke, um für die häufigsten Fälle ein paar Bytes im Bundle zu sparen. Wir können diese Optimierung für den next Zweig entfernen, wenn es Ihnen hilft.

OK danke. Ich werde versuchen zu sehen, ob ich das zum Laufen bekomme. Ich glaube nicht, dass wir es ausschalten müssen, wir müssen nur einen leeren (Null-)Wert für das dritte Argument generieren, damit Python nicht erstickt. dh:

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

Für JS wird dann Folgendes generiert:

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

und für 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)

Wäre so etwas eine akzeptable Lösung?

Das sollte funktionieren. Vielleicht können wir stattdessen None verwenden. An einem Punkt haben wir None Argumente an letzter Position in Fable2Babel entfernt, es scheint, dass wir dies jetzt in FSharp2Fable tun, aber wir können hier eine zusätzliche Prüfung hinzufügen: https://github.com/fable-compiler/ Fable/blob/c54668b42b46c0538374b6bb2e283af41a6e5762/src/Fable.Transforms/Fable2Babel.fs#L1082 -L1095

Übrigens, es tut mir leid, dass ich diese PR nicht gründlich überprüft habe 😅 Aber wenn sie die Tests nicht bricht, könnten wir sie bald als POC zusammenführen, wie wir es mit PHP gemacht haben. Dies liegt daran, dass ich plane (wenn es die Zeit erlaubt), einige Refactorings durchzuführen, um es einfacher zu machen, mehrere Sprachen mit Fable anzusprechen, und es wird wahrscheinlich einfacher, wenn Python dafür bereits im next Zweig ist. Wir vermeiden auch, zu viele divergierende Zweige synchron halten zu müssen (main, next, python).

Ok @alfonsogarciacaro , ich werde sehen, ob ich None anstelle von null generieren kann. Wir könnten es bald zusammenführen. Es bricht immer noch einige der bestehenden Python-Tests, aber es sind jetzt nur noch ein paar übrig und ich sollte das innerhalb einer Woche oder so behoben haben. Außerdem müssen Sie Fable.Core.PY.fs mit den erforderlichen Emits erstellen. Das Umschreiben von Babel2Python -> Fable2Python war schwieriger als ich erwartet hatte, aber ich komme endlich wieder näher, um wieder auf dem richtigen Weg zu sein. Ich werde die PR ein wenig aufräumen und sie für die Zusammenführung vorbereiten.

@alfonsogarciacaro Ich habe alle Tests bis auf einen behoben. Es hängt jedoch mit dem gleichen alten Problem zusammen, das ich mit Babel hatte 😱 Ich brauche eine Möglichkeit zu wissen, ob Fable.Get für einen AnonymousRecord Typ verwendet wird, da sie sich in Python dict verwandeln, und ich muss ein tiefgestelltes Zeichen verwenden dh [] Zugriff und nicht .dotted . Das Problem ist, dass die linke Seite alles sein kann, Objekt, Funktionsaufruf, ... also ist es schwer zu wissen. Gibt es einen besseren Weg. Sollten wir weitere GetKind für anonyme Aufzeichnungen haben, oder wie geht das am besten?

Ich sehe dieses in FSharp2Fable. Woher weiß ich später, dass das FieldGet von einem AnonRecord generiert wurde?

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

Wie wir in Discord @dbrattli gesprochen haben , denke ich, dass Sie den Typ des Angerufenen überprüfen können, um festzustellen, ob es sich um einen anonymen Datensatz handelt. Wenn Sie jedoch einen systematischeren Ansatz benötigen, sollte es in Ordnung sein, weitere Informationen in FieldGet hinzuzufügen Strengere Union Case-Feldnamen übrigens):

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

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

Danke @alfonsogarciacaro. Das hat funktioniert! 🎉

Nächste Ausgabe:

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

Für JS kompiliert dies zu:

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),
    })));
})

Beachten Sie, dass ofSeq mit zwei Argumenten aufgerufen wird. ofSeq nur ein einziges Argument:

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

Warum wird dieser Objektausdruck mit Compare hinzugefügt, wenn ofSeq aufgerufen wird?

Hm, das muss ich mir mal anschauen. Sieht nach einem Fehler aus, ofSeq sollte einen Vergleich akzeptieren (ähnliche Funktionen wie MapTree.ofSeq und Set.ofSeq tun). Die Einrichtung ist etwas kompliziert, daher sind die Dinge möglicherweise irgendwann nicht mehr synchron: Wir haben eine Datei namens ReplacementsInject.fs, die von Replacements verwendet wird, um anzugeben, welche Methoden eine Argumentinjektion benötigen. Es gibt irgendwo ein Skript, um diese Datei automatisch zu generieren, aber wir haben es schon eine Weile nicht mehr verwendet und ich bin mir nicht sicher, ob es mit der neuesten FCS-Version funktioniert. Werde ich prüfen, danke für den Hinweis!

Ich habe einen temporären Fix hinzugefügt. Bereit zur Überprüfung 🎉 https://github.com/fable-compiler/Fable/pull/2345

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen