Fable: Serialisasi dalam Fabel 2

Dibuat pada 2 Mar 2018  ·  17Komentar  ·  Sumber: fable-compiler/Fable

Kelanjutan diskusi dimulai di sini https://github.com/SaturnFramework/Saturn/issues/33

Ada juga beberapa komentar menarik tentang serialisasi implisit vs eksplisit di utas Twitter ini .

Mungkin saya tidak cukup baik mengungkapkan niat saya dalam masalah yang terkait dan (seperti perubahan apa pun yang melanggar) ini telah memulai beberapa kontroversi. Saya akan mencoba mengekspos pemikiran saya saat ini di bawah ini untuk memiliki dasar yang lebih baik untuk diskusi :)

  • Fable 2 alpha akan dikirimkan pada awalnya tanpa dukungan refleksi. Asumsi saya adalah ini terutama akan mempengaruhi ofJson/toJson karena saya pikir tidak banyak refleksi lain saat ini di Fable. Harap perhatikan bahwa versi alfa jelas tidak dimaksudkan untuk produksi, tetapi bagi pengguna untuk mencoba dan memberikan umpan balik.

  • Mengapa menjatuhkan dukungan refleksi? Nah, pada akhirnya saya menulis ulang sebagian besar kode untuk (semoga) membuatnya lebih bersih, lebih mudah dipelihara, dan menarik bagi kontributor. Dalam refactoring saya perhatikan model refleksi di Fable tidak konsisten dan banyak mencemari baik kode JS yang dihasilkan (mengurangi ukuran bundel adalah salah satu tujuan utama untuk Fable 2) dan basis kode Fable. Itu sebabnya saya ingin _memulai yang baru_ dengan Fable 2 alpha untuk melihat kebutuhan nyata pengguna dan mengimplementasikannya kembali dari awal (atau tidak, jika kita tidak membutuhkannya).

  • Bagaimana refleksi akan diterapkan kembali? Saat ini info serialisasi disematkan dalam jenis. Ini membuat tipe terlihat lebih gemuk dan itu masalah ketika orang membandingkan alternatif di REPL karena mereka akan melihat Fable menghasilkan lebih banyak kode untuk tipe sederhana (itu sudah terjadi). Untuk Fabel 2 saya telah mempertimbangkan dua opsi:

    • Buat info refleksi tersedia melalui metode statis dengan harapan akan dihilangkan dengan guncangan pohon saat membangun untuk produksi. Ini mungkin cara termudah tetapi masih akan menampilkan kode di REPL.
    • Hasilkan info refleksi di situs panggilan untuk mengganti typeof<Foo> misalnya. Saya pikir ini akan bagus untuk sebagian besar kasus, tetapi ini dapat menghukum aplikasi yang menggunakan refleksi secara ekstensif, karena kemungkinan akan ada kode yang digandakan.

      • Saya juga mempertimbangkan opsi ketiga: menyuntikkan file tambahan dengan info refleksi sehingga tetap tersembunyi bagi pengguna dan hanya diambil jika diperlukan. Tetapi cara Fable berinteraksi sekarang dengan bundler dan alat JS (Webpack...) membuat ini rumit.

  • Bagaimana serialisasi otomatis bekerja di Fable 2? Catatan akan menjadi objek JS biasa, dan gabungan, array JS. Jadi dalam kebanyakan kasus, hanya memanggil JSON.parse/stringify akan berhasil. Masalahnya adalah hal-hal yang tidak kompatibel dengan browser JSON api, seperti Maps, Sets, tanggal, longs, dll... Jadi Fable masih perlu mengetahui informasi tentang field saat runtime agar benar mengembang / mengempis mereka.

  • Apa yang saya tidak suka tentang serialisasi saat ini? Ada beberapa hal

    • Ini berfungsi untuk sebagian besar kasus tetapi tidak semua kasus, dan ini dapat memberi Anda kejutan saat runtime, yang bukan sesuatu yang baik jika kami menjual bahasa _safe_.
    • Entah bagaimana _mirror_ Newtonsoft.Json di sisi frontend, dan beberapa orang mengharapkannya untuk mendukung hal-hal seperti atribut, menyematkan info tipe di json, dll.

      • Bahasa lain yang saya tahu (termasuk F#) tidak memiliki serialisasi yang tertanam dalam sistem intinya. Ini meningkatkan biaya pemeliharaan basis kode Fable dan membuatnya lebih sulit untuk difaktorkan ulang (seperti yang terjadi pada Fable 2). Itu akan membuat saya sangat senang untuk memindahkan serialisasi ke perpustakaan eksternal.

  • Apa saja alternatifnya? Seperti yang dikomentari dalam masalah di atas, alternatif utama saat ini yang harus segera bekerja dengan Fable 2 alpha adalah Thot.Json . Pustaka ini memberi Anda lebih banyak kontrol atas JSON yang dihasilkan dan validasi yang jauh lebih baik. Satu-satunya kelemahan adalah Anda harus menulis sendiri dekoder, tetapi sudah ada pekerjaan untuk membuatnya secara otomatis .

dev2.0 discussion

Komentar yang paling membantu

Salah satu alasan saya menyukai Fable adalah karena dukungan JSON-nya. Harus pergi dan menambahkan fungsi encoder/decoder di semua tempat akan menjadi penjualan yang sulit. Saya ingat dalam rilis yang sangat, sangat awal, tipe F# di mana sangat dekat dengan objek JS dan sebagian besar waktu Anda hanya bisa JSON.parse/stringify dan mengetahui batasan itu berarti saya bisa membuatnya bekerja. Sayangnya ketika Fable menjadi lebih baik, saya mulai menggunakan Daftar dan DateTimes di JSON saya, jadi jika mereka pergi, itu akan menjadi sedikit proyek penulisan ulang: S

Jika pembuatan kode Thot.Json dapat menjadi bagian dari rantai pembuatan untuk klien dan server (di net46x - ya saya tahu, suatu hari saya harus memutakhirkan), mungkin sebagai semacam acara pra-pembuatan yang menyebut palsu (yang saya gunakan untuk menyebarkan database sql untuk FSharp.Data.SqlClient) maka bisa diterapkan? Atau apakah tugas/target pembuatan MS masih menjadi masalah... Bagaimana cara paket ke pemulihan otomatis?

_Saya berselisih dengan Newtonsoft.Json bertahun-tahun yang lalu._

Semua 17 komentar

Salah satu alasan saya menyukai Fable adalah karena dukungan JSON-nya. Harus pergi dan menambahkan fungsi encoder/decoder di semua tempat akan menjadi penjualan yang sulit. Saya ingat dalam rilis yang sangat, sangat awal, tipe F# di mana sangat dekat dengan objek JS dan sebagian besar waktu Anda hanya bisa JSON.parse/stringify dan mengetahui batasan itu berarti saya bisa membuatnya bekerja. Sayangnya ketika Fable menjadi lebih baik, saya mulai menggunakan Daftar dan DateTimes di JSON saya, jadi jika mereka pergi, itu akan menjadi sedikit proyek penulisan ulang: S

Jika pembuatan kode Thot.Json dapat menjadi bagian dari rantai pembuatan untuk klien dan server (di net46x - ya saya tahu, suatu hari saya harus memutakhirkan), mungkin sebagai semacam acara pra-pembuatan yang menyebut palsu (yang saya gunakan untuk menyebarkan database sql untuk FSharp.Data.SqlClient) maka bisa diterapkan? Atau apakah tugas/target pembuatan MS masih menjadi masalah... Bagaimana cara paket ke pemulihan otomatis?

_Saya berselisih dengan Newtonsoft.Json bertahun-tahun yang lalu._

Hanya untuk menghasilkan titik tandingan, saat ini saya menggunakan refleksi dalam aplikasi node Fable 1 produksi baik untuk deserializing jenis pesan di DU:

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

dan untuk secara otomatis menentukan jenis pengkodean aliran simpul sehingga saya dapat membuat transformasi bersama dan membuatnya diketik/dikonfigurasi seperti yang diharapkan saat runtime:

https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L99 -L120

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

Konsumen layanan ini diharapkan untuk mengirim pesan ke daemon untuk mendapatkan data streaming: https://github.com/intel-hpdd/device-scanner/tree/master/IML.DeviceScannerDaemon (jadi menyimpannya sebagai catatan / string sebagai gantinya array sangat ideal) tapi saya pikir saya bisa mencocokkan string sebelum mencoba membuat serial untuk menyiasatinya.

Masalah yang lebih besar bagi saya adalah kehilangan kemampuan untuk mengonfigurasi aliran simpul secara otomatis berdasarkan kurangnya info refleksi saat runtime, tetapi saya mungkin dapat mengatasi ini juga.

@davidtme Saya sedang mencari cara untuk mengintegrasikan generasi decoder/encoder Thot.Json dalam rantai build atau melalui dukungan TP dll.

Saya akan membuka masalah di Thot.Json untuk melacak kemajuan masa depan saya tentang hal ini.

Untuk info Anda, saya hanya menerbitkan Thot.Json.Net yang menyediakan API yang sama dengan Thot.Json untuk Fable.

Idenya adalah Anda dapat menggunakan arahan kompiler untuk membagikan kode:

// By adding this condition, you can share you code between your client and server 
#if FABLE_COMPILER
open Thot.Json
#else
open Thot.Json.Net
#endif

Dokumentasi

@alfonsogarciacaro Apakah mungkin untuk mendeteksi info jenis pada waktu kompilasi dan mengadaptasi proses serialization tergantung pada itu?

Info tipe tersedia di tipe kompilasi, ya. Meskipun jika kita ingin _both_ mengakses info tipe tanpa refleksi dan memindahkan serialisasi di luar kompilator, kita akan memerlukan beberapa jenis plugin (yang juga tidak tersedia di Fable 2 alpha :wink :). Tapi apa yang Anda maksud dengan "menyesuaikan proses serialisasi"?

TBH, saya merasa aneh untuk mengecualikan fungsionalitas hanya untuk membuat javascript yang dihasilkan lebih menarik bagi orang yang mengevaluasi Fable. Saya tahu ini adalah moto Fable untuk menghasilkan javascript yang bagus dan saya sangat menyukainya, tetapi IMO, nilai jual terkuat yang dimiliki Fable sebagai kompiler untuk javascript, menggunakan F#, interoperabilitas yang luar biasa dengan ekosistem JS dan dapat diterapkan ke javascript apa pun waktu berjalan. Javascript yang dihasilkan bagus hanya itu: bagus untuk dimiliki. (Bagaimana kalau benar-benar membuat polling untuk bertanya kepada pengguna?)

itu berfungsi untuk sebagian besar kasus tetapi tidak semua kasus, dan ini dapat memberi Anda kejutan saat runtime, yang bukan sesuatu yang baik jika kami menjual bahasa yang aman.

Kemudian kami menerapkan kasus luar biasa yang gagal. Kegagalan serialisasi/deserialisasi otomatis tampaknya terjadi di tempat kami tidak memiliki cukup metadata saat runtime.

Entah bagaimana itu mencerminkan Newtonsoft.Json di sisi frontend, dan beberapa orang mengharapkannya untuk mendukung hal-hal seperti atribut, menyematkan info jenis di json, dll.

Saya akan mengatakan itu memberikan kemudahan yang sama dari Newtonsoft.Json dan pengguna sudah sangat senang menggunakannya sebagai default. Jika orang menginginkan lebih banyak kontrol dan penyesuaian maka Thot.Json atau Fable.SimpleJson memberikan tingkat kontrol yang diperlukan.

Bahasa lain yang saya tahu (termasuk F#) tidak memiliki serialisasi yang tertanam dalam sistem intinya. Ini meningkatkan biaya pemeliharaan basis kode Fable dan membuatnya lebih sulit untuk difaktorkan ulang (seperti yang terjadi pada Fable 2). Itu akan membuat saya sangat senang untuk memindahkan serialisasi ke perpustakaan eksternal.

Saya setuju bahwa biaya pemeliharaan meningkat dengan ofJson<'a> dan 'toJson` tetapi itu sangat berharga. Jika kita ingin membuat perpustakaan eksternal maka refleksi harus diterapkan dengan baik agar konsumen dapat menulis konverter seperti itu

@Zaid-Ajaj Dari sudut pandang saya, itu tidak hanya untuk membuat JavaScript yang dihasilkan terlihat bagus tetapi juga untuk mengurangi ukuran bundel.

Kami melihat banyak orang mengeluh tentang itu terutama di negara di mana koneksi internet masih lambat :)

@alfonsogarciacaro Saya sedang berpikir tentang membuat Fable menyesuaikan panggilan serializer untuk mendukung Maps, Sets, dll. Dengan menggunakan nama properti sebagai kunci tapi itu ide yang buruk. Ini berarti banyak duplikasi kode dan juga setiap panggilan serializer tidak akan sama. Mari kita lupakan ide ini :)

@alfonsogarciacaro Apakah mungkin untuk menyematkan info tipe ke dalam "modul tipe".

Modul dapat berisi semua jenis info aplikasi dan jika tidak digunakan, Webpack harus dapat menghapusnya bukan?

Terima kasih banyak atas komentar Anda. Saya mengerti bahwa helper ofJson/toJson ini sangat nyaman dan tidak boleh diganti kecuali kami menyediakan sesuatu yang hampir sama mudahnya untuk digunakan. Terima kasih juga untuk sampelnya @jgrund , sangat berguna untuk melihat bagaimana Fable digunakan dalam produksi. Untuk apa yang saya lihat refleksi hanya digunakan untuk ofJson , apakah ada tempat lain di mana Anda menggunakan typeof<'T> atau serupa?

Terima kasih juga atas wawasan Anda @Zaid-Ajaj. Seperti yang dikatakan Maxime, ini bukan hanya masalah membuat kode lebih mudah dibaca (sebenarnya di beberapa tempat mungkin menjadi _less_ dapat dibaca tetapi lebih dioptimalkan di Fable 2), tetapi mengurangi ukuran bundel dan membuat kode yang dihasilkan lebih cocok untuk alat pengoptimalan JS. Ini juga masalah kelangsungan hidup karena sekarang kami memiliki perpustakaan yang lebih matang dan kompleks seperti Fulma jika ukuran bundel terlalu besar, pengguna dapat mempertimbangkan opsi lain (dan kami tahu persaingan ketat di antara bahasa fungsional yang dikompilasi ke JS). Fable mencoba untuk mengkompilasi bahasa F# dengan _sebagian besar_ fitur-fiturnya dan FSharp.Core dan _some_ dari BCL dan memutuskan apakah refleksi adalah fitur bahasa F# atau bagian dari runtime BCL/.NET adalah topik lain. Tentu saja, ini adalah sesuatu yang sangat berguna, tetapi saya ingin menggunakan rilis alfa Fable 2 untuk menilai biaya/manfaatnya dan apakah kami memiliki alternatif yang layak.

@MangelMaxime Ya, itu opsi ketiga yang saya pertimbangkan di atas. Saya ragu dengan kompilasi jam tangan, karena modul Types mungkin menjadi tidak konsisten. Tapi saya tidak yakin, saya kira kita bisa mencoba.

Sekali lagi, terima kasih atas semua komentar Anda, itu sangat berguna dan pastikan itu adalah niat saya untuk melakukan apa pun yang dapat menyakiti pengguna Fable saat ini. Saya akan mencoba untuk merilis versi alfa Fable 2 sehingga lebih mudah untuk membandingkan alternatif dan efeknya :+1:

Satu-satunya alasan terpenting saya untuk memilih Fable daripada Elm adalah dapat menggunakan F# di kedua sisi dan menggunakan kembali tipe di seluruh tingkatan. Saya tidak membutuhkan refleksi itu sendiri, tetapi seperti yang dikatakan @davidtme , implementasi sebelumnya "berhasil". Saya akan merasa nyaman dengan menyerah pada beberapa aspek sistem tipe untuk tujuan serialisasi JSON, tetapi itu berlaku untuk hampir semua format serialisasi lintas platform dan saya baik-baik saja dengan itu.

Serialisasi yang diekstraksi ke dalam plugin kompiler akan menjadi kompromi yang bagus, jika Anda bisa melakukannya;)

apakah ada tempat lain di mana Anda menggunakan typeof<'T> atau serupa?

Maaf, tidak menyoroti bagian yang relevan. https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L144

Dengan melakukan ini, saya dapat mengatur pengodean aliran dan status sisi yang dapat dibaca hanya dari kompilasi informasi waktu, yang menyelamatkan saya dari mengonfigurasi setiap aliran secara manual dengan informasi yang sudah dikodekan oleh tipe.

Berdasarkan itu, saya dapat melakukan hal-hal seperti menulis aliran bersama tanpa mengkhawatirkan opsi hardcoding sebelumnya, yang cukup bagus: https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/test/ Stream.Test.fs#L221 -L232

Penggunaan refleksi pintar lainnya di sini:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L70

dan di sini:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L169

Seluruh ide proyek Fable.Remoting didasarkan pada ketersediaan refleksi baik di server maupun klien.

Mungkin bukan permintaan yang populer - kecuali komunikasinya sensitif terhadap latensi atau kinerja - tetapi bagaimana dengan mendukung jenis nilai mentah untuk transportasi (dengan atau tanpa dukungan nol-salinan)? Misalnya mendukung jenis nilai .NET yang dikemas dan tidak dikelola ("blittable")? Menurut definisi itu memiliki representasi yang sama di mana-mana baik di asli dan keduanya di .NET. Representasi mentah ini bisa sangat penting, karena terkadang serialisasi/deserialisasi json terlalu intensif cpu/memori/gc/latensi di server (terutama dengan banyak klien dan/atau kecepatan pesan tinggi). Salinan nol pada klien juga dapat didukung dengan menggunakan dukungan ArrayView/TypedArray/DataView (bidang akan membaca/menulis langsung dari/ke buffer).
Mungkin ini juga bisa digunakan untuk interop asli NodeJS juga (dengan ffi).

[<Struct>]
[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type Vector3 =
    val X: int32
    val Y: int32
    val Z: int32
    new(x,y,z) = {
      X=x;
      Y=y;
      Z=z;
    }
    new(dataview: DataView,offset) = {
    //TODO
    }

Terima kasih atas komentar Anda @zpodlovics. Saya harus mengatakan bahwa saya tidak akrab dengan serialisasi semacam ini. Apakah Anda berbicara tentang JSON atau serialisasi biner? (Saya telah bermain sedikit dengan serialisasi biner menggunakan protokol flatbuffers Google dalam satu proyek, tetapi melibatkan banyak kode boilerplate.) Dapatkah Anda memberikan contoh bagaimana data serial akan terlihat? Mungkin agak sulit untuk memiliki representasi yang sama persis baik di .NET dan JS karena Fable menghapus beberapa data dari tipe untuk mengoptimalkannya dalam kode JS dan runtime. Selain keterbatasan dalam JS, seperti tidak dapat mendefinisikan tipe nilai (struct).

Juga, seperti yang dikomentari di atas, saya lebih suka memisahkan serialisasi dari kode inti kompiler. Dalam Fable 2 kita mungkin akan menyimpan semacam serialisasi JSON default seperti sekarang untuk kenyamanan pengguna, tetapi hal-hal yang lebih rumit sebaiknya dilakukan dalam paket terpisah.

@alfonsogarciacaro Terima kasih atas komentarnya!

Yah, ini sebenarnya bukan format serialisasi, tetapi representasi memori langsung seperti yang akan direpresentasikan dalam struct C asli. Format "berseri" akan persis seperti ac struct (asli) [1]. Ini juga diperlukan untuk interop asli, karena menggunakan representasi memori yang sama dengan aplikasi asli. Contoh sebelumnya akan menggunakan wilayah memori 3 * 4 = 12 byte. Struct dapat "ditiru" sebagai objek dengan metode yang menggantikan wilayah memori pendukung (.Wrap).

Struct sebelumnya dapat diterjemahkan ke sesuatu seperti pseudocode ini menggunakan JS DataView API (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32):

misalnya.:

type Vector3 =
    val mutable view: DataView
    val mutable offset: int32
    static member XOffset=0
    static member YOffset=4
    static member ZOffset=8
    new(v: DataView,o:int32) = {
      view=v;
      offset=o;
    }
    member __.Wrap(v: DataView,o:int32) = {
      view=v;
      offset=o;
    }
    member __.X
        with get() = __.view.getInt32(__.offset+Vector3.XOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.XOffset,v)
    member __.Y
        with get() = __.view.getInt32(__.offset+Vector3.YOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.YOffset,v)
    member __.Z
        with get() = __.view.getInt32(__.offset+Vector3.ZOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.ZOffset,v)       

Ya, ini dapat dibuat secara manual, tetapi melakukannya secara manual akan memakan waktu dan rawan kesalahan. Terutama jika saya kompiler sudah memiliki semua informasi yang diperlukan: jenis, tata letak memori, offset bidang, dll. Beberapa jenis plugin ekstensi kompiler/api/ast juga akan baik-baik saja dengan codegen yang dibuat khusus. Akhirnya semua orang akan menghadapi masalah biner - bagaimana harus membaca/menulis biner misalnya: image/audio/video/document/network packets/etc.

Representasi memori mentah bawaan sebagai format serialisasi untuk interop (untuk mendukung ffi) dapat memiliki nilai yang luar biasa bagi semua orang. Di setiap platform, pengembang memiliki dua opsi untuk memanipulasi platform: 1) menulis kode interop sebagai kode asli platform (kompiler platform, rantai alat yang berbeda, proses pembuatan/pengujian/penyebaran yang berbeda, dll.) 2) menulis kode yang mewakili memori mentah untuk interop + ff Bukan kebetulan bahwa .NET memiliki dukungan pinvoke bawaan.

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

Terima kasih banyak atas penjelasan lebih detailnya @zpodlovics! Saya menjelajahi menggunakan tampilan data untuk meniru struct di masa lalu tetapi saya tidak berbuat banyak karena masih ada batasan (seperti hanya dapat menggunakan angka, struct bersarang sulit, menyalin struct dari array juga rumit, dll ). Juga, sudah mungkin untuk mengontrol memori dengan lebih baik dengan Fable menggunakan array numerik, karena mereka akan diterjemahkan ke Array Ketik, tetapi sejauh ini belum banyak menarik di antara pengguna.

Kami mungkin harus membuka masalah baru untuk ini karena, seperti yang Anda katakan, ini tidak sepenuhnya terkait dengan serialisasi. Saya tidak dapat menganggapnya sebagai prioritas saat ini, tetapi akan sangat menyenangkan melihat kontribusi dalam hal ini. Cerita plugin untuk Fable 2 masih TBD tapi kami bisa membuatnya sehingga memungkinkan hal seperti ini jika Anda tertarik untuk bekerja di plugin seperti itu.

Satu hal yang perlu dipertimbangkan adalah dukungan WebAssembly akan datang ke .NET/F# dan representasi memori dalam kasus itu akan lebih dekat dengan model .NET. Ketika ini terjadi, Fable mungkin tetap menjadi cara untuk mengintegrasikan F# lebih dekat ke ekosistem JS (seperti sekarang) dan memanfaatkan perkakas/lib saat ini yang tersedia untuk membangun frontend aplikasi Anda.

@alfonsogarciacaro Bisakah kita menutup masalah ini?

Apakah halaman ini membantu?
0 / 5 - 0 peringkat