1 poin oleh GN⁺ 2023-07-09 | 1 komentar | Bagikan ke WhatsApp
  • Mengemukakan perlunya TypeScript menghasilkan informasi tipe runtime, serta mengumpulkan daftar Type Mapping, Code Generation / External Tool, dan proyek Adapter yang digunakan untuk mengakali masalah ini
  • Masalah intinya adalah bahwa tanpa sistem tipe reflektif, menangani serialisasi dan validasi membutuhkan boilerplate tanpa akhir atau pembuatan kode khusus berbasis berkas skema
  • Sebagai solusi sementara, diajukan io-ts, zod, dan lain-lain, tetapi ini merepotkan karena tipe harus dideklarasikan ulang dengan cara masing-masing pustaka, dan pustaka tersebut juga tidak dapat mendukung semua fitur tipe TypeScript
  • Sambil mengakui bahwa penghapusan tipe di TypeScript punya keunggulan karena proyek JavaScript dapat memakai JavaScript hasil keluaran tanpa pengetahuan TypeScript, artikel ini berpendapat bahwa informasi tipe bisa dipancarkan dalam bentuk tabel lookup yang terpisah dari kode
  • Disertai permintaan agar jangan menyelesaikannya dengan dekorator, artikel ini mengusulkan pendekatan seperti fungsi orde tinggi yang dikenali compiler semacam typescript.generateRuntimeType<T>(), F# Type Providers, dan C# Source Generators untuk mendukung penggunaan interface dan tipe dari pustaka eksternal
  • Menghubungkan issue GitHub berusia 8 tahun sebagai diskusi terkait yang sudah ada, dan meminta agar jika ada proyek lain yang mengalami masalah serupa, silakan kirim PR untuk ditambahkan ke daftar, terlepas dari solusi yang dipakai

1 komentar

 
GN⁺ 2023-07-09
Opini Hacker News
  • Saya harus membaca penjelasan masalah sekitar empat kali baru paham apa yang diminta, dan kasus seperti ini menunjukkan kenapa penulisan yang ringkas itu penting.
    Kebutuhan sebenarnya tampaknya adalah keamanan tipe saat runtime, tetapi TypeScript sejak lama menarik garis bahwa ia tidak akan menjadi runtime pengganti/tambahan untuk JavaScript, jadi kemungkinannya terlihat kecil. TypeScript berperan mengompilasi ke JS lalu selesai, dan apa pun yang terjadi setelah itu di V8 dan lain-lain berada di luar cakupannya.
    Permintaan agar “TypeScript mengekspor informasi tipe runtime” rasanya lebih dekat ke permintaan untuk produk baru yang cukup berbeda dari TypeScript saat ini.

    • Ini bukan semata meminta keamanan tipe runtime itu sendiri, melainkan lebih dekat ke permintaan agar tipe direfleksikan saat waktu kompilasi untuk menghasilkan nilai, lalu informasi itu dipakai saat runtime.
      Misalnya, akan bagus jika bisa membuat fungsi validate generik yang menerima interface dan objek arbitrer lalu memverifikasinya. Mungkin bisa dengan menghasilkan kode validasi JS per tipe lewat refleksi waktu kompilasi, atau dengan meneruskan T sebagai argumen saat runtime untuk dibandingkan, tetapi dalam filosofi TypeScript saat ini hal itu tampak sulit.
    • Hanya dari judulnya saja sudah langsung terlihat ini adalah fitur yang sudah lama saya inginkan, dan rasanya ini lebih dekat ke permintaan akan https://github.com/rbuckton/reflect-metadata yang lebih resmi dan didukung dengan lebih baik.
      TypeScript mengetahui banyak informasi tipe saat kompilasi, tetapi setelah kompilasi selesai informasi itu dibuang. Informasi ini sebenarnya bisa diekspor ke file atau disimpan sebagai metadata di Reflect, jadi sayang sekali jika dibuang, terutama mengingat sifat JavaScript di mana “semuanya adalah objek”.
      Meski tidak sampai memperoleh keamanan tipe TS yang benar-benar akurat, kita tetap bisa menambahkan sebagian pemeriksaan lewat getter/setter atau ES2015 Proxy, dan jika informasi tipe dibiarkan tetap ada saat runtime, banyak kemungkinan menarik akan terbuka sebagai metadata yang dapat dieksekusi.
    • Tidak benar kalau ini mustahil hanya karena TypeScript adalah compiler. Yang diperlukan hanyalah dukungan untuk library refleksi yang mengeluarkan informasi tipe sebagai objek JS dan membiarkannya di-query saat runtime.
      Saat ini enum TS memang sudah dikeluarkan sebagai objek JS sehingga bisa di-query saat runtime, tetapi union type literal string tidak demikian. Selain itu, TypeScript juga mendukung fungsi type guard, jadi tampaknya tidak terlalu sulit untuk menghasilkan fungsi semacam itu secara otomatis dengan memanfaatkan informasi di dalam sistem tipe.
    • Bahkan dengan sekilas membaca pun, permintaannya sudah cukup jelas. Yang diminta adalah agar TypeScript mengekspor informasi tipe yang ditemukannya selama proses type erasure ke kanal tambahan di samping JavaScript yang dihasilkan.
      Anggap saja analoginya seperti file PDB. Karena TypeScript sudah memiliki informasi itu lalu membuangnya, ini bukan berarti harus membuat produk yang sepenuhnya baru.
    • Di bagian atas README ada tautan ke “issue GitHub berusia 7 tahun”, dan di sana masalahnya dijelaskan dengan lebih langsung.
      https://github.com/microsoft/TypeScript/issues/3628
  • Dari sudut pandang PM TypeScript, saya paham keinginan ini. Untuk validasi data, pemeriksaan tipe runtime memang sering dibutuhkan, dan ada banyak library untuk menutup celah itu.
    Namun, fakta bahwa ada banyak library dengan keputusan desain yang berbeda-beda justru menandakan bahwa ini bukan masalah yang sudah selesai dengan satu jawaban yang jelas. Saat perancangan awal TypeScript, hal ini juga sudah dipahami, dan menurut saya prinsip itu bertahan dengan baik.
    Sebagai gantinya, TypeScript kini sudah cukup kuat untuk mengekspresikan secara akurat dalam bentuk tipe apa yang sebenarnya dilakukan library pemeriksaan tipe runtime, dan pengguna bisa menyusun logika validasi runtime dari tipe melalui API. Tingkat fleksibilitas seperti ini terasa masuk akal.

    • Saya baru belum lama mulai belajar pemrograman, dan saya penasaran kenapa TypeScript tidak bisa membuat tipe data buatan pengguna yang lebih canggih.
      Misalnya, saya membayangkan bahasa yang memungkinkan mendefinisikan tipe sebagai angka dalam rentang tertentu atau string yang cocok dengan pola kode pos tertentu, lalu compiler memeriksa validitasnya dengan memakai fungsi validasi yang ditulis seperti fungsi biasa. Saya penasaran apakah ketiadaan fitur seperti ini adalah keputusan desain untuk menghindari kompleksitas yang tidak perlu, atau karena kendala teknis seperti performa.
    • Akan membantu jika compiler TypeScript punya plugin preprocessor resmi.
      Saat ini pun orang yang membutuhkannya bisa membuat preprocessor yang menghasilkan objek runtime dari informasi tipe sebelum diteruskan ke tsc, tetapi upayanya tersebar dan tidak seragam. Jika ada preprocessor model plugin resmi beserta ekosistemnya, mungkin solusi yang baik bisa ditemukan.
    • Jika Microsoft meng-hosting MacroScript sebagai plugin TypeScript atau wrapper tingkat atas, masalah ini bisa dipecahkan.
      Begitu ada pemicunya, komunitas kemungkinan akan membantu pemeliharaannya, dan itu bisa menyelesaikan berbagai kebutuhan code generation, dari pembuatan client sampai assertion tipe runtime.
    • Saya penasaran bagaimana pendapat orang tentang usulan lain untuk mempertahankan informasi tipe setelah kompilasi pada objek Class
  • Ada alasan mengapa ini tidak dilakukan. TypeScript akan menjadi semacam runtime di atas JavaScript, dan berubah menjadi bahasa baru yang dikompilasi ke JS
    Saat ini TypeScript lebih dekat ke JavaScript yang diberi anotasi tipe. Sudah banyak bahasa yang dikompilasi ke JS, jadi pakai saja salah satunya. Orang yang menginginkan TypeScript dengan tipe runtime tampaknya ingin menulis kode bergaya Java/OOP alih-alih JavaScript, tetapi JavaScript adalah bahasa bertipe dinamis, dan itu juga merupakan kelebihannya

    • Tidak juga. Jika sebuah macro seperti generateTypeInfo!() diperluas menjadi objek JS yang mengenkode tipe Foo, itu tetap akan dikompilasi menjadi JavaScript yang mudah dibaca
      Hanya saja macro ini merusak sifat “TS = JS dengan anotasi tipe dan kompilasi cukup dengan menghapus anotasi”. Karena struktur tipe Foo harus benar-benar dihitung.
      Selain itu, tipe TypeScript bersifat struktural, jadi dengan memeriksa struktur objek saat runtime kita bisa mengetahui tipe sampai taraf tertentu, tetapi jika kita meminta informasi nominal yang sudah dihapus seperti apakah sesuatu termasuk dalam union string atau nama tipenya, itu cepat menjadi masalah sulit karena sistem tipe TypeScript yang Turing-complete dan konversi struktural implisit
    • Keduanya bisa dimiliki. Kita bisa membayangkan dunia seperti homoikonisitas Lisp, tempat kode dan data saling berdekatan
      Bicara serius, bahkan tanpa runtime pun Anda bisa mendapatkan refleksi dengan mengekspos tipe sebagai data. Melihat banyaknya pengembang yang berusaha meniru ini, tidak melakukannya justru terlihat kurang bijak
    • Saya kurang paham logika itu. TypeScript sudah merupakan bahasa superset baru yang dikompilasi ke JS
      Tipe runtime menyelesaikan masalah harus memelihara secara terpisah dan berulang sistem tipe untuk kompilasi dan sistem tipe untuk validasi data, dan tampaknya tidak berhubungan langsung dengan OOP. io-ts, yang sering disukai sebagai library workaround untuk masalah ini, juga sangat bertumpu pada pemrograman fungsional
    • Dalam proyek yang cukup besar, bahasa bertipe dinamis tidaklah bagus dan lebih mirip bencana kusut yang terus membuat orang tersandung nilai tak terduga seperti NaN
      Dalam alur kerja yang saya lihat, TypeScript sudah merupakan bahasa yang dikompilasi ke JS, jadi sekalian saja memanfaatkan kelebihan itu semaksimal mungkin
    • TypeScript bukan sekadar JavaScript dengan anotasi tipe. Itu bisa menjadi salah satu modenya, tetapi ia juga punya kemampuan mengeluarkan struktur JS lama sehingga hasilnya bisa terlihat sangat berbeda dari kode TS asal
      Ia lebih dekat ke anotasi tipe + Babel, dan menambahkan satu fitur yang sangat berguna di sini adalah ide bagus. Aneh rasanya tidak bisa dengan aman mengubah string hasil JSON.parse menjadi struktur bertipe, sementara bahasa lain umumnya bisa
  • Sebelum isi di bawah ini, ini adalah topik yang valid dan cukup layak diperdebatkan, dan saya tidak menganggap ada satu jawaban yang objektif benar
    TypeScript adalah lapisan opsional di atas JavaScript, kecuali pengecualian bahwa Enum mengekspor objek, dan kode TS menjadi JS jika tipenya saja dihapus. Selama tidak terlalu menyimpang dari prinsip ini, kebutuhan nyatanya lebih mirip library yang membuat serializer/validator dari tipe. Library seperti itu sudah banyak, dan pada akhirnya tuntutannya terlihat seperti menjadikan salah satunya sebagai opsi standar resmi.
    Secara pribadi saya tidak ingin refleksi runtime masuk ke bahasa inti TypeScript. Pada runtime seharusnya hanya ada JavaScript, dan saya menghargai bahwa output JS tetap mudah dibaca dan bisa di-debug bahkan tanpa source map

    • Selain Enum, ada pengecualian lain yang menghasilkan kode runtime, dan kebanyakan juga dianggap sebagai kekeliruan. Contoh utamanya adalah module dan namespace yang dulu merupakan struktur runtime
      Namun dalam beberapa tahun terakhir itu terutama dipakai di dalam TypeScript sendiri, dan belakangan TypeScript juga mulai meninggalkannya. Ada juga pengecualian yang cukup populer, yaitu parameter property, sintaks untuk mendefinisikan tipe anggota kelas dari parameter konstruktor, dan tampaknya lolos dari kontroversi karena mengurangi boilerplate yang berulang
    • Pernyataan “kode TS menjadi JS tanpa transformasi” hanya mungkin jika bahasa secara keseluruhan dan notasi tipenya dipandang seperti itu
  • Daripada memohon pada dewa-dewa TypeScript, mungkin lebih baik membuat kesepakatan di sisi JavaScript agar pemeriksaan tipe masuk ke JS
    Jika butuh tipe runtime, gunakan type guard, dan jika sering dibutuhkan, gunakan io-ts atau zod untuk menulis tipe sebagai validator/codec/schema. Sampai ada cara yang disepakati di JavaScript, saya tidak berpikir spesifikasi TS, type checker, dan komunitasnya harus menanggung validasi runtime

    • Tidak sempurna, tetapi saya cukup puas dengan pendekatan memakai zod untuk menulis schema dan validator lalu menurunkan tipe darinya. Saya suka bahwa validasi ikut berevolusi bersama perubahan tipe
      Ada yang merasa fitur seperti ini harus masuk ke bahasa, tetapi karena tiap library punya banyak pilihan desain yang bisa diperdebatkan, mungkin justru lebih baik bisa memilih implementasi sesuai kebutuhan proyek.
      Namun pola seperti validasi runtime zod dan penurunan tipe berbasis schema tidak selalu cocok mulus dengan pattern matching milik ts-pattern. Definisi tipe library-library ini seperti labirin, dan kadang ada kode yang terasa seharusnya bekerja tetapi tidak berjalan seperti yang diharapkan. Akan sangat bagus jika keamanan runtime dan pattern matching dengan pemeriksaan exhaustiveness bisa digabungkan dengan mulus
    • Masuknya fitur seperti itu ke JavaScript sendiri pun sangat saya ragukan apakah benar ide yang bagus
      Promise, decorator, dan operator pipe baru semuanya terasa diarahkan ke implementasi yang keliru
  • Saya ingat salah satu pengembang TypeScript pernah mengatakan bahwa jika mulai lagi dari awal, mereka tidak akan memasukkan enum. Karena enum adalah satu-satunya fitur yang menghasilkan kode runtime
    TypeScript tidak mengubah perilaku runtime, dan juga tidak punya sesuatu seperti {#if} khusus TypeScript

    • Itu memang benar. Saya tidak tahu apakah itu satu-satunya alasan, tetapi jelas itu salah satu alasannya
      Meski begitu, TypeScript memang punya beberapa fitur yang melampaui “JavaScript + anotasi tipe”. Ada namespace, sintaks untuk mendefinisikan properti kelas dari argumen konstruktor, decorator eksperimental lama, dan sintaks parameter this yang hilang saat kompilasi tetapi terlihat seperti lebih dari sekadar menambahkan anotasi tipe pada fungsi JS
  • Judul yang lebih baik mungkin lebih dekat ke “TypeScript, tolong sediakan refleksi/tipe runtime”
    Solusi terbaik saat ini mungkin adalah emitDecoratorMetadata. https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata

  • Penulis tampaknya salah memahami tujuan desain TypeScript. Tujuannya bukan menghasilkan output JS yang bersih tanpa kompleksitas, melainkan memastikan semantik runtime TypeScript tetap sama dengan JavaScript.
    Kecuali enum yang merupakan pengecualian yang disayangkan, TypeScript menjadi JavaScript hanya dengan menghapus anotasi tipe. Ekosistem di sekitar TypeScript bergantung pada penghapusan tipe secara penuh, dan jika tuntutan ini diterima, dukungan TS di ESBuild, Deno, dan Bun bisa menjadi hampir mustahil. Karena masing-masing harus mengimplementasikan ulang seluruh tsc dalam bahasa mereka sendiri.
    Sebaliknya, library kompleks yang dikeluhkan OP diimplementasikan di ruang pengguna, sehingga kompatibel dengan alat-alat tersebut

    • Ada banyak cara untuk mengekspor informasi tipe runtime secara statis tanpa runtime. keyof bisa diubah menjadi daftar kunci kelas, atau kelas TS bisa dibuat menginisialisasi kunci ke undefined seperti kelas ES6
      Saat ini kelas TypeScript menghapus semua kunci yang tidak didefinisikan secara eksplisit, jadi jika Object.keys() dipanggil pada instance baru, tidak ada apa pun yang muncul. Jika saja ada opsi tsconfig.json untuk mentransformasikan kelas TS seperti kelas ES6, atau sekadar mengizinkan kelas ES6 berdampingan di dalam TS, pembuatan kode akan menjadi jauh lebih mudah.
      Selain itu, sintaks statis seperti tstypeof Foo::bar akan sangat bagus jika dikompilasi menjadi "string". Bahkan RTTI dasar saja bisa langsung menghilangkan banyak kode boilerplate TypeScript yang berantakan
  • Senang melihat tulisan ini. Saya mulai memakai TypeScript pada 2018, dan setelah sekitar dua tahun mulai percaya bahwa ini pada dasarnya bukan solusi yang lengkap
    Saya datang dari Haskell/C#/F#, dan meskipun TS punya sistem tipe yang kuat, di luar sebagian masa pengembangan ia tidak banyak memberi keunggulan yang diberikan bahasa-bahasa tersebut. Saat menangani dunia nyata, secara praktis ia jauh lebih terbatas daripada C#. Jika tidak menyadari bahwa setelah kompilasi ia turun menjadi JS tanpa pemeriksaan, Anda harus selalu mewaspadai abstraksi yang bocor untuk menghindari bug yang seharusnya tidak mungkin terjadi pada alat yang mengklaim punya tipe statis

    • Tujuan TypeScript memang selalu untuk berjalan di web, bukan memberi keunggulan bahasa dibanding C#
      Jika perlu mengembangkan .NET, gunakan C#; jika perlu mengembangkan web, gunakan TypeScript. C# bertipe nominal dan punya generic yang tereifikasi yang cukup baik, sementara TypeScript bertipe struktural dan mengorbankan soundness sebagai ganti sistem tipe yang kuat untuk mengekspresikan relasi tipe yang kompleks.
      Diragukan apakah bahasa yang menggabungkan keduanya akan bagus; kedua bahasa itu bekerja baik ke arah yang berbeda, tetapi arah tersebut tidak terlalu cocok satu sama lain
  • Saya sangat puas menggunakan https://zod.dev untuk validasi data runtime
    Cukup keren karena kita bisa mengekspresikan maksud langsung di tempatnya dengan API yang fasih, tanpa perlu mendefinisikan tipe nominal terpisah

    • Tipe nominal punya kegunaan lain
      Contoh sederhananya, ini diperlukan untuk membedakan angka 2 dan nilai mata uang 2 dalam domain