Karena ini tahun 2023, tentu saja saya belajar Common Lisp
(log.schemescape.com)- Dari alur pemikiran tentang bahasa yang masih bisa dipakai di masa depan, pilihan kali ini lebih dekat ke keinginan untuk bereksperimen langsung dengan workflow pengembangan yang tua tetapi modern daripada soal kepraktisan
- Janet adalah bahasa skrip kecil beraliran Lisp/Clojure yang membidik ranah mirip Lua, dan menarik perhatian karena pustaka standarnya serta metaprogramming berbasis macro
- Portabilitas, kemampuan untuk di-embed, dan Parsing Expression Grammars yang tampak sebagai kelebihan Janet ternyata hanya terbatas kaitannya dengan kebutuhan saat ini, dan opsi yang sudah ada seperti CHICKEN Scheme, CLISP, SBCL, dan Packrat juga terlihat sudah memadai
- Pengembangan berbasis REPL di Common Lisp menonjol karena memungkinkan kita melihat dan memperbaiki stack, variabel, dan data live saat terjadi kesalahan runtime, lalu melanjutkan eksekusi yang sempat terhenti
- Meski ada biaya untuk mempelajari bahasa baru dan pustaka standarnya, diputuskan bahwa workflow seperti ini layak dialami langsung dalam lingkungan yang memang dirancang sejak awal untuk itu
Mengapa minat bergeser dari Janet ke Common Lisp
- Saya sudah lama memikirkan future-proof programming languages karena ingin menulis kode yang tetap bisa dipakai di masa depan, tetapi saat membuat dan membagikan sesuatu saya cenderung bersikap pragmatis dengan memilih bahasa yang populer, portabel, dan nyaman digunakan
- Di sisi lain, saya juga melihat-lihat bahasa pemrograman dan alat lain untuk bersenang-senang dan bereksperimen
- Baru-baru ini saya menemukan buku gratis Janet for Mortals dan mulai tertarik pada Janet
- Janet adalah bahasa skrip yang relatif kecil, terinspirasi Lisp/Clojure, dan membidik ceruk yang mirip dengan Lua
- Janet menyediakan pustaka standar yang sungguhan, metaprogramming ala Lisp, dan eksekusi saat compile time melalui macro
- Hal yang paling menarik dari Janet, selain sifat Lisp-nya secara umum, adalah portabilitas, kemampuan untuk di-embed, dan Parsing Expression Grammars
- Saat ini saya tidak perlu meng-embed bahasa, jadi kemampuan embed bukan alasan penentu
- Untuk portabilitas, CHICKEN Scheme, CLISP, dan Steel Bank Common Lisp juga terlihat cukup baik
- Untuk parsing, Packrat tampak seperti pilihan yang masuk akal
- Pada akhirnya, alasan untuk memilih Janet dibanding Lisp yang sudah ada jadi semakin berkurang
Workflow berbasis REPL yang membuat saya belajar Common Lisp
- Sebuah artikel tentang workflow berbasis REPL yang hampir unik di Common Lisp menjadi motivasi untuk belajar
- Contoh situasinya adalah ketika fungsi
foomemanggil fungsibaryang belum didefinisikan, lalu terjadi error saatfoodijalankan- Di lingkungan Lisp lama atau Smalltalk, penghentian
fooakan membawa kita ke breakloop - breakloop adalah REPL penuh yang berada di dalam lingkungan dinamis dari fungsi yang terhenti
- Kita bisa berpindah di call stack yang terhenti dan memeriksa variabel yang terlihat pada setiap stack frame
- Kita bisa memeriksa dan mengubah data live dari program yang sedang berjalan
- Jika penyebabnya adalah nilai variabel atau field yang salah, kita bisa mengubah nilainya lalu melanjutkan fungsi yang terhenti
- Kita juga bisa mendefinisikan fungsi
baryang belum ada di dalam REPL lalu melanjutkanfoountuk memperoleh hasil yang masuk akal
- Di lingkungan Lisp lama atau Smalltalk, penghentian
- Saya memang sudah pernah melihat berbagai fitur “edit and continue”, tetapi terasa baru bahwa Common Lisp dibangun dengan workflow seperti ini sebagai pertimbangan sejak awal
- Pengalaman debugging saya sebelumnya bergerak dari
printf debuggingke penggunaan debugger yang sesungguhnyaprintf debuggingadalah cara menyisipkan kode log sementara, lalu compile ulang, dan memeriksa hasil eksekusinya- Dalam situasi ketika program tidak bisa diubah atau dijalankan ulang, saya harus belajar memakai debugger
- Debugger yang sesungguhnya memang alat yang benar, tetapi menyiapkan lingkungan debugging bisa terasa menyakitkan atau bahkan kadang mustahil
- Common Lisp tampak seperti lingkungan yang mendorong debugging satu langkah lebih jauh
- Saya pernah mengubah memori dari debugger untuk menguji kemungkinan perbaikan
- Tetapi kemampuan untuk menulis ulang dan menambal kode dari dalam proses yang sedang berjalan terasa sangat menarik
- Tujuan belajar Common Lisp adalah membangun program secara interaktif sambil melihat langsung apakah cara kerja seperti ini memang menyenangkan
- Saya tidak sepenuhnya yakin apakah mempelajari bahasa baru beserta pustaka standarnya adalah pilihan terbaik hanya untuk menjelajahi workflow baru, tetapi juga tidak ada alternatif yang jelas lebih baik
- Bahasa lain mungkin juga punya integrasi REPL-editor yang serupa
- Hanya saja, saya menduga fitur seperti itu lebih sering merupakan tambahan belakangan, bukan dukungan dari awal, sehingga mungkin lebih banyak bug
- Bahkan kalau akhirnya tidak memuaskan, setidaknya saya sudah mencobanya dalam kondisi terbaik dengan alat standar
- Materi yang membantu untuk belajar Common Lisp dirangkum dalam tulisan terpisah Common Lisp resources
1 komentar
Komentar Hacker News
Hal yang sejauh ini saya sukai dari Common Lisp adalah condition system-nya rapi, dan restart membuat kode yang letaknya berjauhan pun mudah dikendalikan. Pengembangan berbasis REPL juga nyaman ketika belum terlalu tahu apa yang akan terjadi, misalnya saat mem-parse sumber data yang asing, karena kita bisa memperbaiki kode dan melanjutkan tanpa kehilangan konteks
Implementasinya banyak dan interoperabilitasnya juga bagus; dalam satu kasus saya bisa berganti antara SBCL untuk kecepatan dan CLISP untuk mengurangi penggunaan memori. Salah satu alasan saya lebih condong ke CL daripada Scheme untuk belajar Lisp juga karena ada beberapa implementasi yang kompatibel
Meski saya pemula di Emacs, integrasi Common Lisp-nya sangat bagus, dan berjalan baik bahkan di netbook yang sangat lambat yang saya pakai untuk pengembangan. Di era komputer cepat, VS Code, dan language server sekarang, ini bukan keunggulan sebesar dulu, tapi jelas ada nuansa retro-future
Ada juga hal yang disayangkan. QuickLisp, package manager yang paling populer, cukup baik, tetapi belum mencapai tingkat fitur yang biasa kita lihat di ekosistem bahasa modern. Bahasanya sendiri seperti membeku dalam waktu, sehingga banyak hal seperti thread, sinkronisasi, dan argumen command-line membutuhkan library interoperabilitas. Dan akan sangat bagus kalau SBCL mendukung build statis penuh agar bisa mendistribusikan binary juga untuk distro Linux yang bukan glibc
Jika pekerjaan penting seperti mengambil source code tidak mendukung enkripsi atau autentikasi, menurut saya QL sulit menjadi pilihan bagi orang yang peduli keamanan
Selain itu, SBCL di-host di SourceForge juga bermasalah. SourceForge pernah menyisipkan malware ke arsip unduhan di masa lalu, jadi saya menganggap ini juga isu keamanan. Saya tidak melihat alasan yang masuk akal untuk tetap memakai SourceForge hari ini, dan saya kurang paham mengapa proyek penting masih bertahan di sana
Saya sangat menyukai Lisp, khususnya Common Lisp, tetapi hal-hal seperti ini membuat saya menjauh, dan tampaknya tidak banyak minat untuk memperbaikinya
https://www.timmons.dev/posts/static-executables-with-sbcl.h...
https://www.timmons.dev/posts/static-executables-with-sbcl-v...
https://cons.io
Gerbil/Gambit Scheme bisa menghasilkan binary statis penuh sebagai alternatif CL
Bisa dipakai sebagai library Quicklisp biasa, sebagai core image yang langsung mulai, maupun sebagai binary. Skrip juga bisa dijalankan nyaris seketika, sehingga ada kemiripan dengan Babashka. Detailnya masih saya poles dan belum v1.0
Bagian blog tentang “menangani error runtime dengan memperbaiki kode yang rusak di tempat tanpa restart” seharusnya ditunjukkan di sini: https://www.youtube.com/watch?v=jBBS4FeY7XM
Ketika menjalankan komputasi panjang dan berat lalu error muncul di tahap terakhir, alih-alih menjalankan ulang dari awal, kita masuk ke debugger interaktif, berpindah ke baris yang error, mengompilasi fungsi yang sudah diperbaiki, lalu kembali ke debugger dan melanjutkan eksekusi dari satu titik di stack frame. Setelah itu kita bisa melihat programnya berhasil melewati bagian tersebut
M-terasa jauh lebih baik daripadatap>yang disediakan Clojure. Bahkan dengan tool seperti Portal, menurut sayatap>lebih mirip pretty printing yang dipolesDari sisi performa juga, Common Lisp bisa diberi penandaan tipe statis, dan SBCL dapat menghasilkan kode yang sangat efisien jika begitu. Menjalankan
DISASSEMBLEpada kode yang kita tulis sendiri untuk melihat apa yang benar-benar dihasilkan lalu mengoptimalkannya itu membantu. Khususnya paket seperti SB-SIMD dan Loopus benar-benar sangat membantu untuk aplikasi komputasi numerik:quackbernilai true, perlakukan seperti bebek”, cocok untuk desain game bergaya entity-component-systemNamun cara pengembangan Common Lisp, dan tentu saja condition system, benar-benar saya rindukan selama setahun terakhir. Padahal saya bukan hacker CL yang begitu mahir, tetapi itu cepat sekali terasa alami. Andai CLOS dan tipe data primitif CL berpadu lebih baik daripada yang terlihat
Di Cider hal itu jelas mudah dilakukan, dan tool utama di editor lain tampaknya juga cukup mampu melakukan hal serupa
DISASSEMBLEAda proyek pendukung seperti https://github.com/Bronsa/tools.decompiler, dan ada juga JitWatch milik OpenJDK (https://github.com/AdoptOpenJDK/jitwatch). JVM lain juga punya alat serupa
Memang tidak seintuitif Lisp, tapi tetap bisa dilakukan
Bagus kalau berhasil membuat sesuatu yang esoteris dan berjalan menarik, tapi sekarang rasanya tidak ada yang mau menyentuhnya kecuali orang-orang yang gajinya bisa ditulis dengan banyak angka nol
https://www.digitalocean.com/community/tutorials/java-repl-j...
https://github.com/waf/CSharpRepl
https://pub.dev/packages/interactive
Belum lagi Ruby, Python, PHP, Lua; bahkan C++ punya REPL meski agak kikuk: https://github.com/root-project/cling
Setiap kali saya bilang REPL di Lisp bukan lagi pembeda, saya kena downvote
https://github.com/kaveh808/kons-9
Namun redefinisi dan penimpaan seperti ini pada akhirnya mengotori namespace, dan pada titik tertentu ada sesuatu yang rusak sehingga server harus dimuat ulang
Kekurangannya memang banyak, tapi tidak lebih buruk daripada kekurangan teknis murni dari hampir semua bahasa dan lingkungan pengembangan lain. Python dan JS lebih luas dipakai dan punya lebih banyak library, tapi pengalaman pengembangannya terasa jauh lebih buruk. Selama bertahun-tahun saya banyak memakai C#, TS, Py, Hs, sampai bahasa-bahasa yang lebih esoteris, tetapi setiap kali saya terjebak di bahasa-bahasa itu atau benar-benar kesal karena hal-hal yang jelas buruk, saya kembali ke CL(SBCL + Emacs + Slime). Lalu saya merasa lega dan yakin bahwa masih ada hal baik di dunia
Saat ini kami sedang menggalang investasi untuk produk yang secara bodoh kami bootstrap dengan TypeScript, tetapi versi rilisnya akan kami buat ulang dengan CL. Artinya selama 3–5 tahun ke depan sebelum rilis, saya akan bekerja dengan CL, di dalam CL, dan senang karena akan mengerjakan hal-hal menarik seperti implementasi DSL, generasi kode, pekerjaan macro, dan implementasi interpreter tipe statis
Saat bekerja dengan C#, apa yang kamu rindukan dari Lisp?
Pendapat saya yang agak ekstrem: alasan pemrograman fungsional tidak benar-benar populer luas adalah karena meskipun jelas cukup baik untuk menulis program, sebagian besar perangkat lunak sekarang bukan lagi “program yang dijalankan dari command line di PC/server saya lalu berjalan sampai selesai”, melainkan “program yang dimulai, bereaksi terhadap input pengguna, lalu ditutup oleh pengguna” atau “program yang dimulai, lalu merespons jaringan atau I/O otomatis lain, dan berhenti ketika perangkat lunak lain menyuruhnya berhenti”
Pekerjaan seperti ini jauh lebih sulit dilakukan dengan gaya fungsional murni, atau setidaknya begitu dalam implementasi fungsional yang sangat beropini yang pernah saya pakai. Sebab ini bukan lagi sekadar mengevaluasi suatu ekspresi, melainkan menginisialisasi state, bereaksi terhadap I/O, memperbarui state atau melakukan I/O tambahan, bahkan memakai paralelisasi untuk monitoring, menerima input, I/O tambahan, dan pembaruan state
Tentu saja bukan berarti hal seperti ini mustahil dilakukan dengan Lisp, tetapi berdasarkan beberapa semester mengenal pemrograman fungsional saat S1 dan pengalaman profesional memakai fitur fungsional di C++/Scala untuk menyelesaikan masalah seperti ini, membuat pemrograman fungsional bekerja dengan baik pada aplikasi semacam ini terasa cukup sulit, dan ketidakcocokan itu membuat saya ragu untuk mendalami pemrograman fungsional lebih jauh
Terlepas dari Common Lisp, saya sering perlu menulis kode yang menghasilkan kode. Di area ini Lisp tampaknya akan bersinar, tetapi saya belum sempat mencobanya. Contoh proyek terbaru yang saya kerjakan sebelum mempertimbangkan Common Lisp antara lain menghasilkan kode untuk memvalidasi JSON Schema tertentu di static site generator, serta menghasilkan JSX dari Markdown untuk digunakan sebagai konten cerita dalam game pemrograman
Belum lagi tak terhitung banyaknya makro C yang selama ini saya pakai
Kita juga tidak perlu membuang semua fitur bagus dari gaya lain. Sebagian pemrograman fungsional juga makin umum di bahasa-bahasa biasa. Misalnya Rust pun dalam banyak kasus memakai pola fungsional
Dan Lisp bisa dipakai dengan gaya berorientasi objek atau imperatif jika diinginkan. Ini bukan Haskell
Menurut saya Lisp adalah titik awal, dan Haskell-lah yang sebenarnya. Saya menyarankan mempelajari gaya itu meski tidak menulis kode produksi sungguhan
(let ((pair (cons 1 nil)))(setf (cdr pair) pair)(list (first pair) (second pair) (third pair)));; => (1 1 1)Jika ingin sesuatu yang “akan dikompilasi selamanya”, tulislah dengan ANSI C
Jika ingin sesuatu yang “akan bertahan hidup selamanya”, tulislah dengan Python 2.7 dan jadikan tulang punggung template infrastruktur seluruh organisasi. Nilai plus kalau itu modul Ansible kustom
Yang saya harapkan adalah Lisp meminimalkan atau mengabstraksikan hal-hal remeh dan boilerplate sehingga lebih mudah mengekspresikan pikiran sebagai kode. Bagi saya, ekspresi Lisp tampak seperti cara paling alami untuk mengekspresikan pikiran
Saya terutama tertarik menulis aplikasi GUI, jadi saya ingin mempelajari Clog atau menemukan maupun mengembangkan wrapper yang bagus di atas suatu toolkit GUI
(do-it (do-it first))(do-it first)berjalan baik, tetapi apa yang terjadi jika pemanggilan(do-it (do-it first))gagal?Akan bagus jika kita mendapat kendali di titik yang rusak dan bisa memperbaiki definisi
do-it. Namun jika diperbaiki, hasil(do-it first)juga berubahMaka titik tempat mesin berada sekarang menjadi titik yang tidak lagi bisa dicapai dengan kode saat ini
Jika perubahan yang dibuat agar bisa melanjutkan justru mengubah keadaan yang memberi kita kendali untuk melakukan perubahan itu, saya benar-benar tidak tahu bagaimana ini bekerja