Fable: Dukungan Python untuk Fabel

Dibuat pada 4 Jan 2021  ·  54Komentar  ·  Sumber: fable-compiler/Fable

Keterangan

Masalah ini adalah diskusi tentang dukungan Python untuk Fable. Babel AST yang saat ini digunakan oleh Fable cukup dekat untuk menghasilkan kode Python. Apalagi sekarang kelas didukung.

Sebuah POC telah dibuat untuk menunjukkan bahwa ini mungkin. Namun, cukup banyak pekerjaan yang diperlukan untuk:

  1. Tambahkan dukungan yang layak untuk Python
  2. Jadikan Fable lebih agnostik JS dan dukung beberapa bahasa target.

Pustaka Ekspresi memungkinkan pemrograman fungsional dengan Python yang terinspirasi oleh F#. Ini menyediakan implementasi tipe datanya sendiri seperti option , result , seq , map , list (disebut FrozenList), kotak surat prosesor, ... Jadi itu harus cocok sebagai setara Python dari perpustakaan Fable. Di Python, Anda menggunakan Ekspresi untuk melakukan kode F#'ish. Di Fable Anda menghasilkan kode Python menggunakan Expression.

Kasus penggunaan:

  • F# berjalan di notebook Jupyter dengan kernel Python, yaitu mirip dengan apa yang dilakukan dengan misalnya Hy dan Calysto Hy .
  • Skrip F# menggunakan Python untuk mengurangi gesekan
  • Lebih mudah mendukung lingkungan tertanam seperti micro:bit dan Rasberry PI .
  • Berbagi model data antara kode F# dan Python

Hal-hal yang perlu didiskusikan dan diputuskan:

  • Haruskah Python untuk F# lebih Pythonic daripada .NET? Sudah ada dukungan .NET untuk Jupyter yang tidak boleh kita coba ganti atau lawan. Fabel untuk Python mungkin harus menargetkan Python alih-alih .NET (jika mungkin) dan lebih cocok untuk pengembang Python yang mencari F#.
  • Haruskah kita mencoba agar kompatibel dengan Babel AST atau haruskah kita menyimpang ke dalam Python AST kita sendiri. Ini mungkin tak terelakkan, tetapi Babel memberi kita titik awal yang baik.
  • Haruskah kita untuk Fable (seperti dengan Peeble ) atau haruskah kita tetap berada di dalam Fable dan memiliki Fable yang mendukung banyak bahasa. Memiliki repo kami sendiri memberikan kebebasan dan kecepatan (dengan tidak perlu peduli dengan JS), tetapi kami akan berisiko tertinggal dan usang seiring waktu. Perasaan saya adalah bahwa kita harus menjadi bagian dari Fabel.
  • Tipe dasar Python juga berbeda dengan F#. Misalnya int memiliki panjang yang berubah-ubah. Haruskah kita meniru tipe .NET (mesin) agar kompatibel atau haruskah kita mengekspos tipe Python int sebagai F# int?

Memasang POC

Kode sumber POC saat ini tinggal di sini:

  1. Instal Python 3.9 terbaru dari https://www.python.org atau brew install [email protected] di Mac. Perhatikan bahwa python mungkin tersedia di sistem Anda sebagai python , python3 atau keduanya.
  2. Mengkloning kedua repo dan beralih ke cabang python .
  3. Bangun perpustakaan fabel untuk Python: dotnet fsi build.fsx library-py
  4. Untuk menjalankan tes: dotnet fsi build.fsx test-py
  5. Di Fable, edit QuickTest.fs ke beberapa kode F# yang sangat sederhana. Kemudian jalankan dotnet fsi build.fsx quicktest dari direktori teratas.

Sekarang Anda dapat mengedit kode Fabel dan Ekspresi dan kode contoh akan dikompilasi ulang bila diperlukan. Perhatikan bahwa Anda memerlukan penyimpanan tambahan ke QuickTest.fs untuk memicu kompilasi ulang setelah memodifikasi kode Fable. File python akan tersedia sebagai quicktest.py . Demo yang bagus adalah melihat F#, JS, dan Python dalam vscode secara bersamaan. Kemudian Anda akan melihat bagaimana kode diubah saat Anda menyimpan file F#:

Screenshot 2021-01-15 at 13 18 09

Anda dapat menjalankan kode Python yang dihasilkan di terminal:

$ python3 quicktest.py

Tautan

Beberapa tautan yang relevan:

Jangan ragu untuk berkontribusi dengan diskusi, komentar, ide, dan kode 😍

Komentar yang paling membantu

Ini kerja bagus @dbrattli! Ide awal saya dengan Fable adalah menyediakan cara untuk dengan mudah mengkompilasi F# ke bahasa lain. Baik atau buruk, Fable telah berevolusi dengan JS sebagai fokus utamanya sehingga akan memerlukan beberapa pekerjaan untuk membuatnya lebih agnostik bahasa tetapi saya akan terbuka untuk itu. Beberapa komentar untuk pertanyaan Anda:

  • repo yang sama atau berbeda? Saya akan mendukung untuk menyimpan sebanyak mungkin kode umum. Menggunakan repo yang berbeda kemungkinan akan membuat mereka menyimpang cukup cepat (sudah sulit untuk tetap menyinkronkan cabang dengan dukungan kutipan) tetapi kami juga perlu membuat kode cukup modular sehingga kami tidak memiliki kondisi di mana-mana tergantung pada bahasa target. Solusi perantara adalah mencoba mengekstrak kode sebanyak mungkin ke "Nukleus" Fabel (kami tidak dapat menggunakan Core ) dan kemudian memiliki repo yang berbeda untuk implementasi bahasa.

  • Babel AST: Ini digunakan karena awalnya kami mengandalkan Babel untuk mencetak kode. Di dunia yang ideal, sekarang kita memiliki printer kita, itu harus mungkin untuk menghapusnya. Tetapi ada banyak pekerjaan yang terjadi dalam langkah Fabel-ke-Babel sehingga akan sulit. Untuk alasan yang sama, akan lebih baik untuk menggunakan kembali sebanyak mungkin kode dari langkah ini, mungkin membuat Babel AST sesuatu yang lebih umum untuk bahasa non-fungsional. Beberapa contoh:

    • Pindah dari AST berbasis ekspresi ke AST ekspresi/pernyataan. Untuk JS, ini termasuk memindahkan deklarasi variabel ke atas fungsi bila perlu untuk menghindari terlalu banyak IIFE. Saya kira itu akan menjadi sesuatu yang mirip dengan Python.
    • Mengubah pencocokan pola (DecisionTree) menjadi pernyataan if/switch.
    • Pengoptimalan panggilan ekor. Ini dilakukan dengan menggunakan loop berlabel JS. Dalam Python kita perlu menggunakan teknik lain untuk memastikan kita tidak putus dari loop bersarang.
    • Mengonversi impor menjadi pengidentifikasi unik (ini bisa dilakukan langsung di Fable AST jika perlu).
  • Jadikan F# lebih Pythonic? Dengan Fable saya selalu berusaha menemukan keseimbangan di mana semantik .NET terkadang dikorbankan untuk menghindari overhead. Misalnya kita hanya menggunakan nomor JS alih-alih memiliki tipe khusus untuk int (walaupun kita melakukannya untuk waktu yang lama), ini akan berlaku untuk Python int Saya berasumsi. Namun, banyak kontribusi telah dilakukan untuk menghormati semantik .NET pada titik-titik tertentu untuk menghindari hasil yang tidak diharapkan, misalnya dalam pembagian bilangan bulat atau konversi numerik eksplisit.
    Bagaimanapun, ketika Anda mengatakan "lebih pythonic" maksud Anda gaya kode atau API asli? Fable tidak mencoba mendukung sebagian besar .NET BCL tetapi pada akhirnya kita perlu mendukung fungsi/operator umum karena inilah yang biasa digunakan oleh para pengembang (bahkan pendatang baru, karena itu akan mereka temukan di tutorial). Ada baiknya untuk memberikan opsi untuk menggunakan API asli dalam hal apa pun ( Fable.Extras adalah langkah yang baik dalam pengertian itu). Misalnya JS.console.log biasanya memformat objek lebih baik daripada printfn , tetapi kita perlu mendukung yang terakhir karena itulah yang paling sering digunakan oleh para pengembang.

Semua 54 komentar

Ini kerja bagus @dbrattli! Ide awal saya dengan Fable adalah menyediakan cara untuk dengan mudah mengkompilasi F# ke bahasa lain. Baik atau buruk, Fable telah berevolusi dengan JS sebagai fokus utamanya sehingga akan memerlukan beberapa pekerjaan untuk membuatnya lebih agnostik bahasa tetapi saya akan terbuka untuk itu. Beberapa komentar untuk pertanyaan Anda:

  • repo yang sama atau berbeda? Saya akan mendukung untuk menyimpan sebanyak mungkin kode umum. Menggunakan repo yang berbeda kemungkinan akan membuat mereka menyimpang cukup cepat (sudah sulit untuk tetap menyinkronkan cabang dengan dukungan kutipan) tetapi kami juga perlu membuat kode cukup modular sehingga kami tidak memiliki kondisi di mana-mana tergantung pada bahasa target. Solusi perantara adalah mencoba mengekstrak kode sebanyak mungkin ke "Nukleus" Fabel (kami tidak dapat menggunakan Core ) dan kemudian memiliki repo yang berbeda untuk implementasi bahasa.

  • Babel AST: Ini digunakan karena awalnya kami mengandalkan Babel untuk mencetak kode. Di dunia yang ideal, sekarang kita memiliki printer kita, itu harus mungkin untuk menghapusnya. Tetapi ada banyak pekerjaan yang terjadi dalam langkah Fabel-ke-Babel sehingga akan sulit. Untuk alasan yang sama, akan lebih baik untuk menggunakan kembali sebanyak mungkin kode dari langkah ini, mungkin membuat Babel AST sesuatu yang lebih umum untuk bahasa non-fungsional. Beberapa contoh:

    • Pindah dari AST berbasis ekspresi ke AST ekspresi/pernyataan. Untuk JS, ini termasuk memindahkan deklarasi variabel ke atas fungsi bila perlu untuk menghindari terlalu banyak IIFE. Saya kira itu akan menjadi sesuatu yang mirip dengan Python.
    • Mengubah pencocokan pola (DecisionTree) menjadi pernyataan if/switch.
    • Pengoptimalan panggilan ekor. Ini dilakukan dengan menggunakan loop berlabel JS. Dalam Python kita perlu menggunakan teknik lain untuk memastikan kita tidak putus dari loop bersarang.
    • Mengonversi impor menjadi pengidentifikasi unik (ini bisa dilakukan langsung di Fable AST jika perlu).
  • Jadikan F# lebih Pythonic? Dengan Fable saya selalu berusaha menemukan keseimbangan di mana semantik .NET terkadang dikorbankan untuk menghindari overhead. Misalnya kita hanya menggunakan nomor JS alih-alih memiliki tipe khusus untuk int (walaupun kita melakukannya untuk waktu yang lama), ini akan berlaku untuk Python int Saya berasumsi. Namun, banyak kontribusi telah dilakukan untuk menghormati semantik .NET pada titik-titik tertentu untuk menghindari hasil yang tidak diharapkan, misalnya dalam pembagian bilangan bulat atau konversi numerik eksplisit.
    Bagaimanapun, ketika Anda mengatakan "lebih pythonic" maksud Anda gaya kode atau API asli? Fable tidak mencoba mendukung sebagian besar .NET BCL tetapi pada akhirnya kita perlu mendukung fungsi/operator umum karena inilah yang biasa digunakan oleh para pengembang (bahkan pendatang baru, karena itu akan mereka temukan di tutorial). Ada baiknya untuk memberikan opsi untuk menggunakan API asli dalam hal apa pun ( Fable.Extras adalah langkah yang baik dalam pengertian itu). Misalnya JS.console.log biasanya memformat objek lebih baik daripada printfn , tetapi kita perlu mendukung yang terakhir karena itulah yang paling sering digunakan oleh para pengembang.

Saya suka ide ini. Terutama saya menantikan untuk menggunakan pustaka Python di F#. Idealnya kita harus memiliki opsi untuk menggunakan pustaka Python murni atau SciSharp - mereka cenderung memiliki API yang hampir sama persis dengan Python (kode F# yang sama dijalankan dalam konteks Python atau .NET!)

Kode F# tersebut dapat dikompilasi dengan Fable menjadi Python dan kemudian digunakan dalam kernel Notebook (Jupyter atau .NET Interactive) membuat alur kerja analitik menjadi fasilitas berbasis domain nyata (saya dapat menganggap semuanya sebagai "fitur mematikan").

.NET Interactive sangat dapat dikembangkan, menjalankan Fable di dalam F# Kernel sangat mudah (saya memiliki PoC yang berfungsi).

Hal lain adalah bahwa saya memprediksi bahwa MS akan segera mengungkapkan sesuatu yang sama tenang untuk seluruh .NET (prediksi hanya didasarkan pada aktivitas dan tujuan .NET Interactive mengatasi jadi saya mungkin salah). Bahkan jika mereka melakukannya, kami masih ingin Fable memilikinya secara terpisah untuk memiliki persenjataan lengkap JS/F#/Python di dunia fungsional. Pendekatan MS akan lebih seperti rasa Blazor (saya kira).

Terima kasih, ini adalah umpan balik yang bagus @alfonsogarciacaro , @PawelStadnicki . Yang saya maksud adalah jika kita ingin membuat programmer mendapatkan perasaan "Python" dengan mengekspos lebih banyak pustaka standar Python daripada mencoba mengimplementasikan kembali .NET (maka mereka mungkin harus menggunakan .NET saja).

Saya kira kita bisa mendapatkan yang terbaik dari kedua dunia, dan meminta pengguna memutuskan apa yang akan diimpor atau tidak (misalnya gunakan datetime dari Python atau DateTime dari .NET). Jadi tidak perlu memutuskan, tapi implementasikan saja apa yang paling kita inginkan (on-demand). Tapi saya menduga masalah yang sama relevan untuk Fable JS. Anda tahu bahwa Anda sedang mengimplementasikan aplikasi web, dan mungkin tidak mengharapkan semua .NET tersedia. Anda lebih suka memiliki akses yang lebih baik ke ekosistem JS.

Jadi tidak perlu memutuskan di muka. Mari menyingsingkan lengan baju dan memulai Akan sangat menyenangkan menjadi bagian dari Fable dan memiliki masalah ini untuk mengajukan pertanyaan di sepanjang jalan, misalnya tentang transformasi dll. Ada banyak yang harus dipelajari.

PS: Ekspresi btw juga memiliki dekorator panggilan ekor yang dapat kita gunakan (sinkronisasi dan asinkron). Berikut adalah contoh penggunaan di aioreactive.

Membuat saya berpikir tentang #1601.

Saya ingin tahu apakah ada cara yang baik untuk membuat Fable cukup kuat sehingga ini dapat dilakukan sebagai "plugin" backend pihak ketiga (pikirkan LLVM tetapi khusus untuk F #). Kemungkinan tanpa akhir.

Kita dapat mencoba membuat Babel AST lebih umum (dan diketik) sehingga dapat dengan mudah diubah/dicetak ke dalam bahasa seperti C. Namun, jika saya telah mempelajari sesuatu saat mengembangkan Fable adalah bahwa menulis kompiler F#-ke-bahasa lain yang sederhana (kurang lebih) mudah, tetapi memberikan pengalaman pengembangan dan integrasi yang baik sehingga keuntungan F# dikurangi gesekan dengan ekosistem asing berbobot lebih dari berkembang dalam bahasa asli cukup sulit.

Apa yang dikatakan @alfonsogarciacaro . Bagian dari tantangan itu adalah memigrasikan lebih banyak implementasi Fable BCL ke F# sehingga tidak harus ditulis ulang untuk setiap bahasa, sambil mempertahankan kinerja asli yang dapat diterima untuk kode yang dihasilkan, sesuatu yang tidak sepele bahkan dengan JavaScript.

Menarik. Saya tidak menyadari bahwa sebagian besar implementasi Fable BCL ditulis dalam (mungkin) Javascript. Saya berasumsi itu sudah lebih F#. Saya tidak ragu ada alasan bagus untuk ini -- mengapa ternyata lebih mudah seperti itu?

@jwosty Jawaban (tidak terlalu pendek) adalah: untuk alasan kinerja, dan untuk menjembatani perbedaan antara tipe F# vs tipe yang didukung secara asli oleh browser, dan untuk integrasi yang lebih baik dari kode yang dihasilkan dalam ekosistem JavaScript.

Juga .NET BCL memiliki permukaan API yang sangat besar, di atas FSharp.Core , jadi sulit untuk mempertahankan upaya ini tanpa tim yang lebih besar (walaupun @alfonsogarciacaro entah bagaimana berhasil mempertahankan dan mengembangkannya terutama oleh dirinya sendiri, yang luar biasa dan terpuji).

Saya kira yang ingin saya katakan adalah, kontribusi (dan ide) sangat disambut dan dihargai.

Jawaban (tidak terlalu pendek) adalah: untuk alasan kinerja, dan untuk menjembatani perbedaan antara tipe F# vs tipe yang didukung secara asli oleh browser, dan untuk integrasi yang lebih baik dari kode yang dihasilkan dalam ekosistem JavaScript.

Saya bisa melihat itu.

Saya kira yang ingin saya katakan adalah, kontribusi (dan ide) sangat disambut dan dihargai.

Tentunya! Saya mengerti betapa banyak upaya yang dilakukan untuk mempertahankan proyek sebesar dan sepenting ini, dan saya sangat menghargai keringat darah dan air mata yang telah kalian curahkan ke dalamnya. Jelas saya membuka mata saya untuk tempat-tempat untuk berkontribusi, karena Fable akhir-akhir ini sangat sangat berguna bagi saya dan saya ingin memberi kembali.

Saya bisa menggunakan sedikit bantuan. Saat ini saya menggunakan proyek QuickTest untuk pengembangan (yaitu dotnet fsi build.fsx quicktest ). Bagaimana cara mencetak Babel AST saat mengembangkan? ( Perbarui : Saya menemukan ASTViewer ).

Masalah saya saat ini adalah bahwa Python tidak mendukung lambda multiline (fungsi panah) sehingga lambda perlu diubah dan diekstraksi ke fungsi bernama dan panggil fungsi itu sebagai gantinya. Setiap tips di sini akan dihargai. Misalnya:

let fn args cont  =
    cont args

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

Akan perlu diubah menjadi:

let fn args cont  =
    cont args

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

Bagaimana saya harus berpikir ketika mengganti satu ekspresi panggilan dengan beberapa pernyataan karena ketika mengubah panggilan fungsi dengan args saya perlu menggantinya di tingkat yang lebih tinggi, atau menggunakan semacam pernyataan blok misalnya if true then ... Ada ide?

@dbrattli Lihat juga Printer ini , yang juga mencetak beberapa properti.

Saya tidak menyadari bahwa sebagian besar implementasi Fable BCL ditulis dalam (mungkin) Javascript. Saya berasumsi itu sudah lebih F#. Saya tidak ragu ada alasan bagus untuk ini -- mengapa ternyata lebih mudah seperti itu?

Ya, seperti yang dikatakan @ncave , alasan utamanya adalah untuk menghasilkan JS yang lebih standar. Misalnya, di Funscript semua pengganti FSharp.Core ditulis dalam F#, tetapi dengan Fable saya ingin mengintegrasikan dengan standar JS bila memungkinkan (misalnya menggunakan iterator JS alih-alih .NET IEnumerable) yang membawa banyak masalah ayam dan telur sehingga lebih mudah untuk menulisnya di JS/TypeScript di awal. Pada awalnya saya juga memiliki ide untuk menerbitkan perpustakaan dongeng sebagai paket terpisah yang dapat digunakan dalam proyek JS, tetapi saya segera membuangnya.

Dalam Fable 2 kami memulai upaya untuk mem-port beberapa modul ke F#. Ini sangat bermanfaat karena meningkatkan dogfooding dan mari kita dengan mudah menggunakan kembali peningkatan di FSharp.Core (seperti peta dan set baru-baru ini). Namun, masih ada beberapa peretasan di sana-sini untuk mengatasi masalah ayam dan telur. Dan jangan bicara tentang monster besar yang merupakan modul Pengganti :)

Juga seperti yang dikatakan @ncave , memelihara perpustakaan ini adalah tugas besar, itu sebabnya saya selalu enggan meningkatkan dukungan untuk BCL untuk mengurangi permukaan pemeliharaan, tetapi saya sangat beruntung bahwa ada banyak kontribusi besar untuk perpustakaan dari awal.

@dbrattli Tentang mencetak AST, sudah lama saya tidak menggunakan alat ASTViewer di repo Fable. Opsi yang lebih baik adalah yang ditunjukkan oleh @ncave atau visualisator Fantomas yang baru-baru ini saya gunakan ketika saya perlu memeriksa F# AST (pastikan untuk memilih Show Typed AST ). Sayangnya kami belum memiliki printer khusus untuk Fable atau Bable AST. Dalam kasus Fable AST, karena hanya serikat pekerja dan catatan sederhana printfn "%A" berfungsi, meskipun info lokasi agak bising.

Tentang mengekstraksi lambda. Itu mungkin tetapi membutuhkan beberapa pekerjaan. Saya kira kita dapat melakukan sesuatu yang mirip dengan impor, yaitu, mengumpulkannya saat melintasi AST , dan memberi mereka pengidentifikasi unik. Nanti kita tinggal mendeklarasikan fungsi-fungsi di bagian atas file . Tantangan utama kemungkinan adalah nilai yang ditangkap, saya tidak ingat apakah F# AST memiliki informasi tentang ini, tetapi meskipun memilikinya, sayangnya kami tidak menyimpannya di AST, jadi saya rasa kami harus melakukannya temukan semua ident yang digunakan di badan lambda yang tidak sesuai dengan argumen (atau binding di dalam lambda) dan ubah menjadi argumen tambahan dari lambda (dan saya kira mereka harus pergi di awal untuk menghindari gangguan dengan kari).

Terima kasih atas info yang bagus @alfonsogarciacaro , PoC pertama saya baru saja mengubah Babel.fs / Fable2Babel.fs , BabelPrinter.fs dll. Sekarang saya mulai dari awal dengan Python AST terpisah yaitu Python.fs , PythonPrinter.fs . Kemudian saya akan mencoba menambahkan Babel2Python.fs untuk mengubah dari Babel AST ke Python AST karena itu mungkin lebih mudah daripada mengonversi dari Fable AST (tetapi juga mungkin dengan Fable2Python.fs jika mengonversi dari Babel akhirnya menjadi sulit). Dengan cara ini saya tidak akan terlalu banyak menyentuh basis kode yang ada, dan memiliki transformasi saya sendiri di mana saya akan mencoba melakukan penulisan ulang lambda.

Itu masuk akal. Salah satu kesulitannya adalah Babel AST tidak terbuat dari DU sehingga sedikit lebih sulit untuk melintasinya dengan pencocokan pola. Mungkin #2158 bisa membantu?

@alfonsogarciacaro Saya pikir saya berhasil memperbaiki penulisan ulang fungsi panah dan ekspresi fungsi dengan cara yang menurut saya (berharap) benar-benar berfungsi. Idenya adalah bahwa setiap ekspresi ( TransformAsExpr ) juga mengembalikan daftar pernyataan (yang perlu digabungkan dan diteruskan hingga daftar pernyataan terakhir (tingkat pernyataan terakhir). pernyataan yang dikembalikan (func-def) akan diangkat dan ke atas dan ditulis di depan pernyataan lain Jadi ekspresi panah atau fungsi hanya akan mengembalikan name-expression, [statement ] mana ekspresi panah/fungsi ditulis ulang menjadi pernyataan dan dikembalikan bersama dengan ekspresi nama. Ini berarti bahwa:

module QuickTest

let fn args cont  =
    cont args

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

Menghasilkan JS berikut:

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

Yang pada gilirannya menghasilkan Python berikut:

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)

Saya pikir saya mungkin memiliki masalah dalam menangani penutupan, tetapi dalam sebagian besar (semua?) kasus penutupan juga dicabut misalnya:

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

Akan ditulis ulang menjadi:

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)

Bagaimanapun, ini awal yang baik

Saya melakukan hal yang sama untuk PHP, (dan menjalankan CrazyFarmers di BGA ) jadi mungkin kita bisa menggabungkan upaya di sini ?

https://github.com/thinkbeforecoding/peeble

@ncave Apakah Anda punya tips tentang menggunakan Fable untuk penggunaan interaktif seperti notebook Jupyter? Masalahnya adalah Jupyter akan memberi Anda sebuah fragmen kode (sel) dan kernel perlu mengkompilasi fragmen ini bersama dengan deklarasi lokal yang ada dari pernyataan sebelumnya.

Fable tidak optimal untuk ini, tetapi saya telah membuat PoC di mana saya menyimpan kamus yang dipesan dari pernyataan/deklarasi sebelumnya (biarkan, ketik, buka) dan buat program F# yang valid menggunakannya secara berurutan bersama dengan fragmen kode terakhir. Deklarasi apa pun dalam fragmen kode dihapus dari dict sebelum dieksekusi dan ditambahkan setelahnya. Ini sepertinya berhasil, tetapi saya bertanya-tanya apakah mungkin ada cara yang lebih baik?

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

Kemudian saya memiliki cli Fable yang berjalan di latar belakang yang mengkompilasi ulang setiap kali Fable.fs diperbarui.

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

@dbrattli Kedengarannya masuk akal, jika fragmennya kecil. Untuk yang lebih besar, mungkin menggunakan file terpisah untuk setiap sel (fragmen) dapat mempercepat, karena Fable menggunakan beberapa caching untuk melewati pekerjaan yang tidak perlu pada file yang tidak berubah.

Sekadar informasi, jika Anda membutuhkan lebih banyak kontrol, Anda juga dapat menggunakan Fable sebagai library , bukan hanya CLI (lihat fable-standalone , dan contoh penggunaan ).

1) Masalah yang saya miliki adalah Fable AST tidak memiliki Throw / Raise. Jadi ini akan dibuat menjadi ekspresi Emit yang sulit untuk saya uraikan. Atau setidaknya tampaknya tidak perlu ketika Babel memiliki ThrowStatement. Apakah ada alasan bagus mengapa itu tidak digunakan, atau haruskah saya mencoba memperbaikinya? @alfonsogarciacaro

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

let result1 = divide1 100 0

Menghasilkan:

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

export const result1 = divide1(100, 0);

Di mana throw matchValue adalah EmitExpression .

2) Haruskah kita menambahkan ThisExpr ke Fable AST, jadi saya dapat mengidentifikasi apakah this digunakan sebagai Kata Kunci ini. Saya tidak yakin dengan Fable.IdentExpr diatur ke this apakah ini kata kunci ini atau tidak karena itu perlu diterjemahkan ke self dengan Python.
https://github.com/fable-compiler/Fable/blob/nagareyama/src/Fable.Transforms/Fable2Babel.fs#L792

Ini lucu, sebenarnya throw itu ada di Fable 2 AST tapi saya hapus untuk Fable 3 dalam upaya menyederhanakan AST karena hanya digunakan di satu tempat, tapi saya rasa kita bisa menambahkannya lagi.

Tentang "ini", itu rumit. Saya perlu memeriksanya lagi karena dalam F# AST ini diwakili secara berbeda dalam konstruktor atau metode (ada beberapa komentar tentang ini yang tersebar melalui kode). Ada properti IsThisArgument untuk pengidentifikasi di Fable AST, tetapi izinkan saya memeriksa apakah ini hanya untuk argumen pertama dari anggota yang terpisah atau apakah itu juga berfungsi untuk "ini sebenarnya".

@alfonsogarciacaro Saya dapat menggunakan sedikit bantuan dengan cara menangani kode yang dikompilasi "internal" vs "pengguna" misalnya Pengecualian memiliki misalnya .message , tetapi bagaimana saya harus menerjemahkan ini? Bisakah saya yakin bahwa kode yang dikompilasi pengguna tidak akan pernah memiliki .message dan akan selalu berakhir sebagai misalnya Test__Message_229D3F39(x, e) sehingga mereka tidak akan pernah mengganggu? Atau apakah saya harus membungkus pengecualian yang ditangkap dalam misalnya objek JsException dan menambahkan properti .message hanya untuk memastikan? Bagaimana Fable melakukan ini dari F# ke Babel? Apakah Anda melacak jenisnya, atau?

Misalnya: bagaimana ex.Message.StartsWith di F# menjadi ex.message.indexOf di JS? Saya perlu menerjemahkan ini ke str(ex).index dengan Python (atau membungkus objek).

Sebenarnya kami memiliki masalah dengan pengecualian F# (seperti dalam: pengecualian Foo dari int * int) Saya tidak memperbaikinya sebelum rilis Fable 3 System.Exception diterjemahkan sebagai Kesalahan JS sehingga mereka memiliki bidang .message. Tetapi pengecualian F# tidak berasal dari Kesalahan JS karena alasan kinerja (saya pikir saat ini mencoba mengakses .Message gagal). Ada diskusi tentang ini di suatu tempat. Untuk kasus Anda dan untuk pengecualian umum, saya pikir Anda dapat berasumsi untuk saat ini Fable mengakses properti .message dan .stack.

Bagaimana kita menginginkan pemilihan bahasa dalam Fabel? Hari ini kita dapat memilih misalnya TypeScript menggunakan --typescript . Jadi kita bisa memilih Python menggunakan --python . Tapi haruskah kita berhenti menghasilkan .js ? Kami mungkin harus karena JS yang dihasilkan mungkin tidak valid jika misalnya Emit digunakan. Tapi terkadang kami ingin melihat JS. Haruskah kita memiliki opsi untuk --javascript , atau haruskah kita meminta pengguna untuk menjalankan tanpa --python untuk mendapatkan JavaScript? @alfonsogarciacaro

@dbrattli --typescript hanya menghasilkan file .ts , jadi --python hanya dapat menghasilkan file .py , jika Anda mau.
Tetapi secara teknis mereka semua saling eksklusif, jadi haruskah kita memperkenalkan sakelar --language , dengan nilai JavaScript/TypeScript/Python ?

@ncave Ya, saya pikir itu ide yang bagus seperti [-lang|--language {"JavaScript"|"TypeScript"|"Python"}] mana JavaScript bisa menjadi default. Saya sudah mulai menambahkan properti bahasa untuk kompiler, jadi saya dapat mencoba untuk memperbaiki opsi baris perintah juga? https://github.com/fable-compiler/Fable/pull/2345/files#diff -9cb94477ca17c7556e6f79d71ed20b71740376f7f3b00ee0ac3fdd7e519ac577R12

Beberapa status. Dukungan Python semakin baik. Banyak fitur bahasa sekarang tersedia. Tugas besar yang tersisa adalah mem-port perpustakaan dongeng. Untuk mempermudah ini, pustaka-fable untuk Python telah dipindahkan ke dalam repo , sehingga kita dapat mengonversi dan menggunakan kembali banyak file .fs fable-library ke Python. Namun, banyak dari mereka berisi kode yang dipancarkan JS yang perlu dicegat dan ditangani "secara manual". Penyiapan pengujian untuk Python (berdasarkan pytest) sudah ada saat ini dengan 43 pengujian yang lulus, jadi ini membuatnya jauh lebih mudah untuk mengembangkan dan menambahkan fitur baru (dan menunjukkan apa yang berfungsi).

Bangun perpustakaan fabel untuk Python: dotnet fsi build.fsx library-py
Jalankan tes Python: dotnet fsi build.fsx test-py

@dbrattli
Idealnya kita harus mencoba mengonversi lebih banyak fable-library ke F#. List dan Seq sudah ada, Array menggunakan sejumlah kecil metode asli, mungkin itu dapat dipisahkan.

Pada topik yang sedikit berbeda, menurut Anda apa manfaat utama penerapan codegen bahasa baru dari Babel AST, daripada langsung dari Fable AST?

@ncave Python dan JavaScript cukup dekat (imo) sehingga relatif mudah untuk menulis ulang Babel AST. Saya telah mem-porting sedikit JS ke Python sebelumnya, misalnya RxJS ke RxPY. Kami juga mendapatkan manfaat dari semua perbaikan (bug-) yang dilakukan "upstream". Karena itu, kita dapat menggunakan kembali sebagian besar Fable2Babel.fs dan mengubah langsung ke Python menggunakan Fable2Python.fs yang menggabungkan/menciutkan Fable2Babel.fs dan Babel2Python.fs . Aku benar-benar memikirkannya sebelumnya hari ini. Itu kemudian menjadi lebih seperti garpu dan perbaikan bug kemungkinan besar perlu diterapkan di kedua tempat. Untuk saat ini saya pikir tidak apa-apa untuk mengubah Babel AST, tetapi pada akhirnya kita mungkin ingin mengubah Fable AST secara langsung.

Ini bagus @dbrattli! Terima kasih banyak untuk semua pekerjaan Anda. Sebenarnya, yang ada dalam pikiran saya adalah meletakkan sebagian besar kode Fable di perpustakaan dan merilis implementasi yang berbeda sebagai alat dotnet independen: fable(-js), fable-py (atau nama lain). Mungkin itu memberi kita lebih banyak kebebasan dalam siklus rilis, tetapi saya juga setuju dengan memiliki alat dotnet tunggal yang dapat dikompilasi ke kedua bahasa. Itu bisa lebih sederhana, khususnya di awal.

Tentang fable-library, ya, jika memungkinkan alih-alih menulis ulang dengan python file .ts saat ini, alangkah baiknya untuk memindahkannya ke F# sebagai gantinya. Kita harus mencoba mengisolasi bagian-bagian tersebut menggunakan Emit dan mengadaptasinya ke Python dengan menggunakan #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

Atau kita dapat menambahkan atribut Emit "multi-bahasa" baru:

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

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

Terima kasih @alfonsogarciacaro dan @ncave , ini adalah ide bagus. Saya akan mencobanya untuk melihat apa yang berhasil. Saya melihat manfaat dari menerjemahkan fable-library ke F# jadi saya akan mencoba membantu di sini setelah kompiler Python cukup baik untuk menangani file di sana. Menggunakan #if akan banyak membantu, dan kita mungkin dapat menghindari #if s dengan memindahkan emisi ke file perpustakaan khusus bahasa (karena saya memiliki file .fsproj untuk Python.

@alfonsogarciacaro mengapa tidak memiliki dua atribut

Untuk 2 sen saya, saya suka beban kognitif rendah saat ini karena memiliki satu atribut Emit yang hanya memancarkan. Imo apa yang @dbrattli lakukan sekarang masuk akal (file proyek berbeda untuk setiap versi bahasa fable-library ). Kita dapat memisahkan bahasa yang berbeda yang dipancarkan dalam file mereka sendiri, yang mengimplementasikan antarmuka (atau modul) yang terdefinisi dengan baik untuk dipanggil nanti dari implementasi F# yang umum.

Contoh yang baik adalah mengubah Array.Helpers menjadi antarmuka (atau membiarkannya sebagai modul) yang dapat diimplementasikan untuk setiap bahasa target. Mungkin upaya migrasi lebih dari fable-library ke F# (serta opsi kompiler --language ) dapat masuk ke PR terpisah dari yang ini, sehingga dapat berjalan lebih cepat dan lebih mudah dikontribusikan .

Itu ide yang bagus @ncave :+1: Sebenarnya kami sudah melakukan ini saat mengisolasi kode asli dalam proyek Fable multiplatform (.net dan js). Untuk menyederhanakan hal-hal, saya mungkin hanya akan menambahkan file Native.fs ke Fable.Library dan memiliki submodul di sana sesuai kebutuhan, seperti:

namespace Fable.Library.Native            # or just Native

module Array = ..
module Map = ..

Kita harus memindahkan ke sana apa pun yang menggunakan Emit atau nilai dari Fable.Core.JS .

Bekerja di async, saya mencoba mem-port Async.ts dan AsyncBuilder.ts ke F# yaitu memiliki Async.fs dan AsyncBuilder.fs . Tetapi sekarang saya memiliki masalah bahwa file pengujian saya dengan kode:

async { return () }
|> Async.StartImmediate

menghasilkan kode Python (kode JS harus sangat mirip):

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

Namun AsyncBuilder tidak mengandung metode tetapi ada AsyncBuilder__Delay_458C1ECD . Jadi saya mendapatkan AttributeError: objek 'AsyncBuilder' tidak memiliki atribut '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
...

Adakah petunjuk tentang bagaimana saya bisa menangani ini? @ncave @alfonsogarciacaro. Apakah ada cara saya dapat membuat kelas F# menghasilkan kelas dengan metode, atau meminta pengujian saya menghasilkan kode yang menggunakan AsyncBuilder__Delay_458C1ECD statis alih-alih .Delay() ?

@dbrattli Biasanya jawaban untuk de-mangling adalah menggunakan antarmuka untuk metode yang Anda inginkan agar tidak hancur.
Pada catatan terkait, apakah async mendukung salah satu fitur yang mungkin lebih cocok untuk terjemahan ke Python asli asyncio ?

@ncave Ok, jadi begitu cara kerjanya Terima kasih atas wawasannya. Saya akan mencoba menempatkan pembuat di belakang antarmuka dan melihat apakah itu membantu (bahkan jika pembuat F# bukan antarmuka, itu mungkin berfungsi dengan baik). Adapun async rencananya adalah menggunakan asli Python asyncio ( lihat di sini ), tetapi mendapat masalah dengan python cannot reuse already awaited coroutine , jadi saya perlu menundanya di belakang suatu fungsi. Bagaimanapun saya tahu saya juga bisa menggunakan task atau asyncio builder untuk menunggu asli Python. Penyederhanaan AsyncBuilder.ts menarik saya untuk mencoba dan port ke F#.

Menggunakan antarmuka untuk menghindari mangling harus berfungsi seperti yang dikatakan @ncave . Kami juga mencoba beberapa pendekatan lain:

  • Atribut NoOverloadSuffix : ini mencegah Fable menambahkan akhiran yang berlebihan. Jelas kelebihan beban tidak akan berfungsi dalam kasus ini.

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

Kemudian kita dapat mereferensikan metode menggunakan Naming.buildNameWithoutSanitation :

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

  • Tetapi setelah itu @ncave menulis metode untuk membuat nama yang rusak kurang lebih seperti yang diharapkan Fable (saya tidak ingat apakah ada beberapa batasan) sehingga Anda dapat menulis kode F# secara normal. Lihat misalnya src/fable-library/System.Text.fs :

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

Dalam hal ini Anda hanya perlu menambahkan kelas ke kamus replacedModules :

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


Jadi sayangnya belum ada pendekatan yang konsisten untuk menautkan ke kode perpustakaan dongeng yang ditulis dalam F# dari Replacements . Mungkin kita menggunakan kesempatan ini untuk menyepakatinya sekarang.

Terima kasih atas wawasan yang sangat menarik @ncave dan @alfonsogarciacaro. Dengan antarmuka saya menemukan masalah lain. Bagaimana cara menangani unit dengan Python? Antarmuka saya terlihat seperti ini:

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

Masalahnya adalah Return menghasilkan:

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

Ini terlihat bagus dan mungkin bagus untuk JS, tetapi dalam python Anda harus memberikan argumen jika fungsi membutuhkan argumen. Dengan demikian Anda akan mendapatkan kesalahan jika Anda memanggil x.Return() ketika 'T adalah unit . Reaksi pertama saya adalah membuat kelebihan:

abstract member Return : unit -> IAsync<unit>

tapi itu punya masalah sendiri. Solusi saya saat ini (yang terlihat jelek adalah):

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]

Tapi itu mengharuskan saya untuk memiliki penanganan khusus untuk setiap fungsi argumen tunggal generik. Mungkin saya harus membuat setiap fungsi argumen tunggal menerima null (Tidak ada dalam Python) sebagai input. Misalnya:

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

Apa cara yang tepat untuk mengatasi masalah ini?

IIRC, untuk metode dengan tanda tangan unit -> X panggilan tidak memiliki argumen (seperti dalam F# AST) tetapi untuk lambda atau dalam hal ini argumen umum yang diisi dengan panggilan/aplikasi unit memiliki unit argumen transformCallArgs dari langkah Fable2Babel. Mungkin kita bisa menambahkan kondisi di sana dan meninggalkan argumen unit ketika bahasa target adalah python:

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

Hai @ncave , @alfonsogarciacaro , Saya dapat menggunakan beberapa masukan untuk menangani parameter [<Inject>] dengan Python. Bagaimana ini dilakukan di Php @thinkbeforecoding? Misalnya fungsi seperti (dalam 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

Kode saya tidak menghasilkan parameter cons dan itu diperlukan untuk Python.

map(fn ar)

Apakah ini opsional untuk JS? Bagaimana saya bisa mendeteksi atribut dalam kode dan menjadikannya optimal? Misalnya

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

Php juga eksplisit pada parameter opsional. Saya harus menambahkan flag IsOptional pada model argumen.

Ada dua kegunaan atribut Inject dalam Fabel. Salah satunya terutama eksperimental (untuk meniru entah bagaimana kelas tipe dan juga untuk menghindari keharusan inline fungsi untuk menyelesaikan info tipe generik) dan Anda tidak perlu terlalu peduli tentang hal itu.

Penggunaan lainnya adalah internal dalam metode fabel-library yang memerlukan beberapa info tambahan yang diteruskan oleh argumen yang diselesaikan pada waktu kompilasi:

  • Fungsi yang membangun larik perlu mengetahui apakah itu larik dinamis atau diketik JS.
  • Tetapkan dan petakan konstruktor dan pembantu lain yang perlu menerima pembanding untuk mengetahui bagaimana suatu tipe harus dibandingkan (mirip dengan fungsi yang melakukan beberapa perhitungan rata-rata).

Namun, fungsi ini tidak dipanggil secara langsung sehingga Fable "tidak dapat melihat" atribut Inject . Untuk alasan ini, kami menggunakan file ReplacementsInject.fs yang mengatakan fungsi mana di perpustakaan yang memerlukan injeksi. Lihat cara penggunaannya: https://github.com/fable-compiler/Fable/blob/522f6aad211102271538798aeb90f4aed1f77dd6/src/Fable.Transforms/Replacements.fs#L988-L1019

File ini dibuat secara otomatis pada awalnya dengan skrip ini yang mendeteksi fungsi mana di Fable.Library memiliki argumen terakhir yang dihiasi dengan Inject . Tapi saya pikir pada satu titik kami berhenti memperbarui dan IIRC pembaruan terakhir ke ReplacementInjects. file telah dilakukan secara manual.

Mengetahui itu, saya mungkin bisa menyingkirkan info IsOptional ... Saya akan memeriksanya

@alfonsogarciacaro Sepertinya ada masalah dengan injectArg untuk kode seperti:

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

Di sini Array.map tidak akan menyuntikkan konstruktor. Kode akan berubah menjadi JS tanpa arg yang disuntikkan:

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

Apakah ini bug? Kode ini akan berfungsi di JS (beruntung?) Tetapi tidak untuk Python.

Ah maaf! Benar-benar lupa tentang ini tetapi kami memiliki "optimasi" di mana konstruktor array tidak disuntikkan untuk array JS "standar": https://github.com/fable-compiler/Fable/blob/4ecab5549ab6fcaf317ab9484143420671ded43b/src/Fable.Transforms/ Replacements.fs#L1005 -L1009

Kami kemudian hanya default ke global Array ketika tidak ada yang dilewatkan: https://github.com/fable-compiler/Fable/blob/4ecab5549ab6fcaf317ab9484143420671ded43b/src/fable-library/Array.fs#L25 -L28

Tidak sepenuhnya yakin mengapa saya melakukan ini, tetapi saya kira untuk menyimpan beberapa byte dalam bundel untuk kasus yang paling umum. Kami dapat menghapus pengoptimalan ini untuk cabang next jika itu membantu Anda.

Ok terima kasih. Saya akan mencoba untuk melihat apakah saya bisa membuat ini berfungsi. Saya tidak berpikir kita perlu mematikannya, kita hanya perlu menghasilkan nilai kosong (null) untuk argumen ketiga sehingga Python tidak tersedak. yaitu:

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

Untuk JS kemudian akan menghasilkan:

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

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

Apakah sesuatu seperti itu akan menjadi perbaikan yang dapat diterima?

Itu harus bekerja. Mungkin kita bisa menggunakan None sebagai gantinya. Pada satu titik kami menghapus None args di posisi terakhir di Fable2Babel, sepertinya kami melakukannya sekarang di FSharp2Fable, tetapi kami dapat menambahkan pemeriksaan tambahan di sini: https://github.com/fable-compiler/ Fabel/gumpalan/c54668b42b46c0538374b6bb2e283af41a6e5762/src/Fable.Transforms/Fable2Babel.fs#L1082 -L1095

BTW, maaf saya belum memeriksa PR ini secara mendalam Tetapi jika tidak melanggar tes, kami dapat segera menggabungkannya ke POC berikutnya seperti yang kami lakukan dengan PHP. Ini karena saya berencana (memungkinkan waktu) untuk melakukan beberapa refactoring agar lebih mudah menargetkan beberapa bahasa dengan Fable, dan Mungkin akan lebih mudah jika Python sudah ada di cabang next untuk itu. Kami juga menghindari keharusan untuk menyinkronkan terlalu banyak cabang yang berbeda (utama, berikutnya, python).

Ok @alfonsogarciacaro , saya akan melihat apakah saya dapat menghasilkan None alih-alih null. Kita bisa segera menggabungkannya. Itu masih melanggar beberapa tes Python yang ada tetapi hanya ada beberapa yang tersisa sekarang dan saya harus memperbaikinya dalam waktu sekitar satu minggu. Juga perlu membuat Fable.Core.PY.fs dengan emisi yang dibutuhkan. Penulisan ulang dari Babel2Python -> Fable2Python lebih sulit dari yang saya harapkan, tetapi saya akhirnya semakin dekat untuk kembali ke jalurnya lagi. Saya akan membersihkan PR sedikit dan membuatnya siap untuk digabungkan.

@alfonsogarciacaro Saya telah memperbaiki semua tes kecuali satu. Namun itu terkait dengan masalah lama yang sama yang saya miliki dengan Babel Saya perlu cara untuk mengetahui apakah Fable.Get digunakan pada tipe AnonymousRecord karena mereka berubah menjadi Python dict, dan saya perlu menggunakan subskrip yaitu [] akses dan bukan .dotted . Masalahnya adalah sisi kiri bisa apa saja, objek, panggilan fungsi, ... jadi sulit untuk mengetahuinya. Apakah ada cara yang lebih baik. Haruskah kita memiliki GetKind untuk catatan anonim, atau apa cara terbaik untuk melakukan ini?

Saya melihat yang ini di FSharp2Fable. Bagaimana nanti saya tahu bahwa FieldGet dihasilkan oleh 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)

Seperti yang kita bicarakan di Discord @dbrattli , saya pikir Anda dapat memeriksa jenis panggilan untuk melihat apakah itu catatan anonim. Tetapi jika Anda memerlukan pendekatan yang lebih sistematis, tidak apa-apa untuk menambahkan lebih banyak info di FieldGet , meskipun pada titik ini kita mungkin harus mendeklarasikan catatan terpisah untuk meminimalkan perubahan yang melanggar dan menghindari kebingungan dengan bidang boolean lainnya (F# seharusnya memiliki nama bidang kasus serikat pekerja yang lebih ketat btw):

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

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

Terima kasih @alfonsogarciacaro. Itu berhasil! 🎉

Masalah berikutnya:

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

Untuk JS ini dikompilasi ke:

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

Perhatikan bahwa ofSeq dipanggil dengan dua argumen. Namun, ofSeq hanya membutuhkan satu argumen:

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

Mengapa ekspresi objek dengan Compare ditambahkan ketika ofSeq dipanggil?

Hmm, saya perlu melihat ini. Sepertinya bug, ofSeq harus menerima pembanding (fungsi serupa seperti MapTree.ofSeq dan Set.ofSeq dilakukan). Penyiapannya agak rumit jadi mungkin di beberapa titik ada yang tidak sinkron: Kami memiliki file bernama ReplacementsInject.fs yang digunakan oleh Replacements untuk menunjukkan metode mana yang memerlukan injeksi argumen. Ada skrip di suatu tempat untuk menghasilkan file ini secara otomatis tetapi sudah lama kami tidak menggunakannya dan saya tidak yakin apakah itu berfungsi dengan versi FCS terbaru. Saya akan memeriksanya, terima kasih telah menunjukkannya!

Saya menambahkan perbaikan suhu. Siap untuk ditinjau https://github.com/fable-compiler/Fable/pull/2345

Apakah halaman ini membantu?
0 / 5 - 0 peringkat