Tipe statis dan sekop
(carefully.understood.systems)- Alasan popularitas tipe statis menurun dari tahun 2000-an hingga awal 2010-an lalu meningkat lagi pada pertengahan hingga akhir 2010-an dijelaskan oleh peningkatan kualitas sistem tipe statis
- Sistem tipe dinamis diibaratkan seperti menggali tanah dengan tangan kosong, karena manusia harus menilai sendiri status dan isi variabel serta field, sementara komputer tidak membantu maupun menghalangi
- Sistem tipe statis lama seperti Java awal atau C++98 diibaratkan sebagai sekop kertas, karena bahkan tidak membantu membedakan pointer nullable dan non-nullable, serta memaksa penulisan nama tipe berulang-ulang
- Sistem tipe modern seperti TypeScript, Haskell, MyPy, Swift, dan Rust lebih baik mendukung pemeriksaan kesalahan program dan representasi status melalui penanganan null, sum type·union type, dan type inference
- Seiring fitur IDE seperti pelengkapan otomatis nama method menjadi umum, informasi yang dimasukkan ke dalam sistem tipe statis menghasilkan keuntungan produktivitas selain pemeriksaan kesalahan
Klaim utama
- Popularitas tipe statis dipandang meningkat lagi bukan sekadar karena tren, melainkan karena kualitas sistem tipe statis yang bisa digunakan secara luas telah membaik
- Digunakan analogi bahwa jika ada sekop yang bagus, menggali tanah dengan sekop lebih baik daripada dengan tangan kosong, tetapi jika yang tersedia hanya sekop dari kertas, tangan kosong justru lebih baik
- Dalam sistem tipe dinamis, manusia harus memikirkan sendiri status dan isi variabel serta field dalam program, dan komputer tidak membantu penilaian itu
- Sistem tipe statis yang buruk bisa menjadi beban yang lebih besar daripada bantuan, dan ini diibaratkan seperti menggali tanah dengan sekop kertas
Keterbatasan sistem tipe statis di masa lalu
- Sistem tipe statis pada Java awal atau C++98 yang banyak digunakan pada tahun 90-an dan awal 2000-an bahkan tidak mampu membantu dengan baik untuk tugas sederhana seperti membedakan pointer nullable dan non-nullable
- Sistem tipe statis lama dijelaskan sebagai struktur yang tidak memiliki sum type dan hanya memiliki product type
- Sistem tipe statis lama memaksa penulisan nama tipe secara manual di banyak tempat
- Kode seperti
BufferedReader bufferedReader = new BufferedReader(new FileReader(filename));digambarkan sebagai bencana kecil
Perbaikan pada sistem tipe modern
- Sistem tipe modern seperti TypeScript, Haskell, MyPy, Swift, dan Rust menyediakan cara untuk membedakan tipe nullable dan tipe non-nullable
- Dicontohkan Haskell dengan
Maybe t, TypeScript denganT | null, Swift denganT?, dan Rust denganOptional<T>, sehingga sistem tipe dapat menunjukkan lokasi yang membutuhkan pemeriksaan null dan apakah ada yang terlewat - Dijelaskan bahwa dalam praktiknya, kita jadi hampir tidak pernah lagi melihat error null pointer saat runtime
- Sistem tipe modern menyediakan setidaknya salah satu dari sum type atau union type, dan memungkinkan penerapan "Make invalid states unrepresentable"
- Pendekatan ini memungkinkan objek yang merepresentasikan state machine mengekspresikan agar setiap field hanya ada ketika berada pada status yang relevan
- Sistem tipe modern menyediakan type inference, sehingga jika compiler dapat menilai
let x = 5;sebagai angka, tidak perlu menulislet x: number = 5;
Fitur IDE dan kesimpulan
- Seiring fitur IDE seperti pelengkapan otomatis nama method makin umum, kegunaan sistem tipe statis menjadi lebih besar
- Pada tahun 90-an, Intellisense adalah fitur inti Visual Studio, tetapi pada tahun 2020-an, fitur serupa tersedia di hampir semua IDE dan editor
- Informasi yang dimasukkan ke dalam sistem tipe statis tidak hanya menghasilkan pemeriksaan kesalahan program, tetapi juga keuntungan produktivitas tambahan
- Sistem tipe dinamis yang baik lebih baik daripada sistem tipe statis yang buruk, tetapi sekarang kita dapat menggunakan sistem tipe statis yang jauh lebih baik daripada di masa lalu
1 komentar
Opini di Lobste.rs
Tulisan ini bagus, tetapi saya tidak sepenuhnya setuju. Walaupun sistem tipe statis di awal 2000-an tidak hebat, menurut saya itu jauh lebih baik daripada tidak punya tipe statis sama sekali
Memang belum ada closed sum type, tetapi banyak hal masih bisa dimodelkan lewat subtipe, dan walaupun belum ada non-nullable type, referensi serta tipe non-pointer di C++, dan tipe primitif di Java, mengambil sebagian peran itu. Di Ruby atau JavaScript, semua tipe bukan hanya nullable, tetapi juga bisa diperlakukan seperti string, seperti integer, dan seperti semua tipe lain dalam program, jadi situasinya lebih buruk
Menurut saya, alasan besar mengapa arus pendapat soal tipe statis berubah adalah karena saat ledakan jejaring sosial Web 2.0, keunggulan sebagai yang pertama masuk pasar lebih penting daripada segalanya. Meskipun harus menumpuk utang teknis dengan Ruby atau Python, merilis cepat dan beriterasi tetap lebih baik daripada tersingkir seperti Friendster atau Digg, dan kalau lambat, saat itu tinggal membeli lebih banyak server dengan dana bunga rendah yang mudah didapat
Setelah itu datang ledakan mobile, di mana perangkat lunak harus berjalan di perangkat pengguna yang terbatas dan tidak bisa dikendalikan, aplikasi bertipe dinamis yang lambat memang benar-benar lambat, dan kalau terjadi type error tidak bisa dipulihkan dengan elegan lewat top-level response handler seperti di server. Dalam lingkungan seperti itu, keamanan dan performa dari tipe statis jadi jauh lebih meyakinkan
Di awal 2000-an saya juga setuju, karena saat itu sistem tipe sering memaksakan sifat-sifat yang nyaris tidak pernah salah sambil memberi batasan yang tidak membantu penataan kode. Khususnya, cara subtipe digabungkan dengan pewarisan implementasi terasa tidak fleksibel
Setelah memakai sistem tipe yang lebih modern, pandangan saya berubah. Di snmalloc, mesin status kepemilikan memori dipaksakan lewat sistem tipe C++, dan di codebase lain sistem tipe memeriksa perilaku overflow yang benar untuk penghitung ring buffer. Keduanya kalau salah merepotkan untuk di-debug dan merupakan sumber kesalahan yang umum, dan sistem ini benar-benar menggagalkan kompilasi pada kode yang tadinya saya kira benar, sehingga bug tidak sempat masuk ke tree
Di IDE, menekan
.lalu mengetik sedikit nama metode dan menekan Enter pada kandidat yang benar menghemat 2 detik setiap beberapa detik, dan juga menghemat 30 detik yang biasanya dipakai untuk mencari definisi kelas saat tidak tahu metode apa saja yang ada. Prinsip ini juga tergambar dengan baik di https://grugbrain.dev/#grug-on-type-systemsKita jauh lebih sering menulis baris kode yang memanggil metode daripada menulis tipe parameter fungsi, jadi pertukaran ini sangat merugikan tipe dinamis. Hal yang sebenarnya bernilai bukanlah membolehkan kode absurd yang akan gagal saat runtime, melainkan menghilangkan keharusan menuliskan tipe variabel lokal, dan sejak awal bahasa bertipe statis tidak perlu melarang itu
Codebase langka yang benar-benar memakai sistem tipe secara serius menumpuk halaman demi halaman kode yang tidak banyak mengatakan apa-apa, tetap dipenuhi kondisi runtime, dan pada Java, program juga benar-benar melambat seiring bertambahnya hierarki tipe. Sebagian besar codebase memakai tipe secara tambal sulam sambil menambahkan banyak kondisi runtime, dan dari sisi cakupan pengujian yang dibutuhkan, penghematannya tidak jauh berbeda dibanding sistem tipe dinamis
Bahasa bertipe dinamis memang tidak memberi kompensasi statis, tetapi ringkas sehingga lebih mudah dibaca, ditinjau, dan diuji. Ini особенно benar dalam lingkungan seperti framework dependency injection akhir 90-an hingga awal 2000-an, saat menambahkan layanan baru berarti harus mengubah beberapa berkas XML. Orang juga bisa bekerja tanpa IDE yang memakan setengah RAM
Karier awal saya persis seperti ini, jadi saya sepenuhnya setuju dengan tulisan tersebut. Rasio biaya-manfaat Java 1.4 sampai Java 6 begitu buruk sampai-sampai saya nyaris menyerah pada bahasa bertipe statis, lalu beberapa tahun kemudian saya mencoba Haskell sebagai hobi dan baru sadar bahwa tipe statis sebenarnya bisa punya rasio biaya-manfaat yang masuk akal, dan masalahnya adalah Java. Esai “python is not java” juga menggambarkan masa gelap itu dengan baik
Aku ragu apakah setelah tipe statis menjadi semangat zaman, kita benar-benar melihat keuntungan keandalan pada perangkat lunak kita
Menurutku kelebihan tipe statis jauh lebih terletak pada umpan balik pengembangan yang instan dan berkurangnya kegagalan runtime yang fatal; secara teori kegagalan seperti itu selalu mungkin terjadi, tetapi dalam praktiknya rasanya tidak terlalu sering terjadi
undefineddannullmenurun drastisPara junior dan beberapa senior awalnya skeptis dan mengira
@ts-ignoreakan bermunculan di mana-mana, tetapi kenyataannya hanya ada sekitar tiga, termasuk yang muncul karena tipe dependensi yang rusak. Dulu aplikasi di branch pengembangan kira-kira seminggu sekali mati karena kebingungan tipe dan menghambat pekerjaanku, sekarang aku bahkan tidak ingat kapan terakhir kali itu terjadiHanya dengan memuaskan
tsc, bug terkait tipe berkurang, termasuk saat aku sendiri tidak menulis kodenya. Sebaliknya, linter belakangan ini terlalu bersemangat, dan saat mencoba memuaskan alat seperti Sonar aku justru melihat kerusakan refactor yang nyata. 95% peringatannya palsu, 3% adalah bug pada alatnya, dan 2% yang membantu pun bukan penyebab bug yang sebenarnya. Alih-alih menghabiskan 1 minggu menyesuaikan codebase untuk menangkap satu bug, dalam prosesnya aku malah memasukkan dua bug lagiPekerjaan untuk memuaskan
tsckira-kira menghasilkan 2 perbaikan bug murni dan 1 regresi per hari, tetapi regresinya biasanya tidak sampai crash total, melainkan salah perilaku dengan tingkat keparahan yang lebih rendahMenambahkan property-based testing ke sini rata-rata butuh 2~4 jam dan selalu mengungkap setidaknya satu bug. Jika kode bisa diuji dengan property-based testing, lakukanlah
Dengan memperluas cakupan pengujian memakai model murah DeepSeek V4 Flash sambil berhati-hati agar tidak menghasilkan tes sampah, aku bisa memperbaiki sekitar 2~3 bug logika per hari, dan tidak ada crash. Hanya saja suite tesnya nyaris pada batas masih bisa dipelihara
Ketika junior kubiarkan membuat tes ala kadarnya dengan keluarga Sonnet dan Opus 4.5, 4.6, model-model itu hanya membuat tes yang “mendokumentasikan perilaku saat ini”, jadi efek perbaikannya kecil, dan suite tesnya tidak bisa dipelihara sehingga harus dibuang
Pengujian berbasis model sangat bagus untuk menangkap bug, tetapi penyiapannya rumit, dan sangat merepotkan untuk mengarahkannya agar menggali ke sudut-sudut alih-alih menghabiskan siklus pada fitur permukaan. Akan menarik kalau ada sesuatu seperti fuzzer berbasis model yang berbasis profil
Singkatnya, type checker sangat bagus dalam menangkap kegagalan fatal dan berbagai kebingungan, dan property-based testing itu luar biasa. Tes biasa memerlukan banyak disiplin agar terus memberi hasil yang konsisten
Bagian yang paling sulit kusetujui di sini adalah mengelompokkan TypeScript bersama sistem tipe yang bagus
awaitsudah beberapa kali menjebakku. Meski begitu, memang benar bahwa keadaannya membaik secara dramatisSejujurnya, type system struktural pun akhirnya kuterima, dan menurutku itu akan berdampak positif pada desain bahasa ke depan
Klaim ini kurang meyakinkan. Bahasa pemrograman yang layak dengan algebraic data types dan inferensi tipe sudah ada sejak pertengahan 90-an
Sistem tipe Java dan C++ memang sangat miskin, tetapi SML, OCaml, dan Haskell sudah ada dan rasanya tidak terlalu berbeda dengan sekarang. Jika orang-orang tidak memakai bahasa-bahasa itu, masalahnya adalah budaya, adopsi, dan kebutuhan yang tak terucapkan, bukan semata-mata karena “sistem tipe yang dapat dipakai belum cukup bagus”
Atau jika klaimnya adalah “sistem tipe pada bahasa populer saat itu buruk, dan sistem tipe pada bahasa populer saat ini lebih baik sehingga sistem tipe jadi lebih populer”, itu terdengar seperti penalaran melingkar
Ada juga banyak nuansa dalam perbedaan antara bahasa yang dirancang bersama sistem tipenya, dan bahasa yang awalnya dirancang tanpa tipe lalu belakangan ditempeli sistem tipe
Bahkan dari sudut pandang orang yang sejak awal lebih menyukai tipe dinamis, menurutku tulisan ini cukup adil. Sekarang aku bekerja dengan C#, untuk hobi aku memakai Lisp, dan dulu juga pernah memakai Python
Saat harus memakai Java 5, aku kebanyakan terus-menerus bertarung dengan sistem tipenya karena keputusan buruk dari pengembang library. Setelah pindah ke C# sekitar 2010, sistem tipenya tidak secara aktif merugikan, tetapi umumnya terasa redundan, dan bahkan tidak bisa mencegah null pointer exception, yang merupakan kebingungan tipe paling umum di Python
Sistem tipe C# baru benar-benar mulai membantu sekitar 2020, ketika non-nullable reference types diperkenalkan. Tahun ini native union type juga akan masuk, tetapi library union type yang memaksa exhaustiveness setidaknya sudah memungkinkan sejak 2016 dan aku mulai memakainya sejak 2020
Menurutku tren tetap punya peran, tetapi sebagian darinya tidak buruk. Bahasa-bahasa yang sedang tren dengan sistem tipe yang lebih ekspresif telah membawa perbaikan juga ke bahasa-bahasa biasa yang kita pakai untuk bekerja dan dibayar
Haskell dan sistem tipenya sudah ada sejak era 2000-an. Memang tidak digunakan seluas sekarang, tetapi jelas memang sudah ada, jadi klaim ini perlu melengkapi bagian itu
Secara pribadi, saya melihat TypeScript sebagai faktor besar yang membuat pengguna bahasa arus utama lebih akrab dengan sistem tipe yang lebih baik. Selain kualitas dan dukungan dari Microsoft, ada keunggulan bahwa ia diterapkan pada JavaScript, dan JavaScript lebih sangat membutuhkan tipe dibanding Python. Alasannya adalah “Undefined is not a function.” dan “The good parts.”
“Real World Haskell” terbit pada 2008, dan tujuannya adalah membuat Haskell terlihat lebih menarik bagi programmer arus utama. Saya tidak tahu seberapa besar itu membantu menyebarkan kabar baiknya
Di dunia Java, Scala membawa tipe yang keren pada 2004, dan di .NET, F# muncul pada 2005. Scala mungkin mendapatkan pengguna menonjol paling banyak, seperti Twitter, tetapi posisinya tidak seperti TypeScript yang bisa menyerap porsi besar pengguna platform tersebut, dan juga tidak cukup menarik untuk menarik pengguna bahasa lain dalam jumlah besar seperti Rust atau Go
Di paragraf berikutnya Haskell memang disebut sebagai “sistem tipe modern”, tetapi pada akhir 90-an dan awal 2000-an, orang yang punya pengalaman dengan Haskell, bahkan kalau menghitung yang hanya sekadar pernah menyentuhnya, secara praktis mendekati 0%. Tulisan itu membahas bagaimana mayoritas pengembang saat itu mengalami bahasa bertipe statis, dan mengapa mayoritas itu secara kolektif menghindari bahasa bertipe statis
Misalnya, untuk memakai
dunedi OCaml, Anda harus memahami berkasopam, berkasdune, sintaksocaml module, dan sintaksocaml. Ekstensi kompilator opsional di Haskell juga terasa sama menakutkannyaIni kontras dengan
cargo, di mana Anda cukup tahutomldan Rust