3 poin oleh GN⁺ 2023-07-28 | 1 komentar | Bagikan ke WhatsApp
  • 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
    • Saya mengikuti bahasa-bahasa menarik dengan nama singkat seperti Nim dan Zig
    • Eksperimen semacam ini kadang membantu menemukan cara pemrograman baru atau sesuatu yang nantinya menjadi alat penting
  • 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 foo memanggil fungsi bar yang belum didefinisikan, lalu terjadi error saat foo dijalankan
    • Di lingkungan Lisp lama atau Smalltalk, penghentian foo akan 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 bar yang belum ada di dalam REPL lalu melanjutkan foo untuk memperoleh hasil yang masuk akal
  • 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 debugging ke penggunaan debugger yang sesungguhnya
    • printf debugging adalah 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

 
GN⁺ 2023-07-28
Komentar Hacker News
  • Tidak menyangka akan melihat tulisan saya di sini. Suatu saat saya ingin menulis tulisan lanjutan, tapi untuk sekarang saya masih pemula
    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
    • Ada beberapa kekurangan juga. Terakhir kali saya cek, QuickLisp hanya mengambil paket lewat HTTP plaintext, tanpa enkripsi maupun mekanisme verifikasi untuk mendeteksi modifikasi saat transit
      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
    • Jika membutuhkan build statis dan tidak keberatan memakai SBCL yang agak lama, pekerjaan daewok yang membangun dan menautkan SBCL di lingkungan musl mungkin merupakan solusi yang Anda cari. Saya sempat mencoba memindahkan patch-nya ke versi terbaru, tetapi karena perubahan upstream masih terjadi segmentation fault
      https://www.timmons.dev/posts/static-executables-with-sbcl.h...
      https://www.timmons.dev/posts/static-executables-with-sbcl-v...
    • Saya akan memberi satu cons
      https://cons.io
      Gerbil/Gambit Scheme bisa menghasilkan binary statis penuh sebagai alternatif CL
    • Saya sedang membuat CIEL untuk mereka yang ingin mulai dengan kumpulan library yang bagus (json, csv, database, HTTP client, argumen command-line, ekstensi bahasa, dan sebagainya): https://github.com/ciel-lang/CIEL/
      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
  • Di tempat kerja saya memakai Clojure, tetapi setiap kali perlu debugging atau kode yang cepat, saya merindukan hampir semua hal dari Common Lisp. Bisa mengeklik bagian mana pun dari stack di dalam error bertingkat untuk melihat lexical binding sangat berguna, dan cara mengeklik objek lalu menyalinnya ke REPL dengan M- terasa jauh lebih baik daripada tap> yang disediakan Clojure. Bahkan dengan tool seperti Portal, menurut saya tap> lebih mirip pretty printing yang dipoles
    Dari sisi performa juga, Common Lisp bisa diberi penandaan tipe statis, dan SBCL dapat menghasilkan kode yang sangat efisien jika begitu. Menjalankan DISASSEMBLE pada 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
    • Ini merangkum dengan baik keluhan yang saya rasakan saat menulis tesis magister dengan Clojure. Bukan berarti saya menyesali pilihannya. Clojure, dengan nuansa “semuanya adalah map key-value” dan pendekatan “kalau key :quack bernilai true, perlakukan seperti bebek”, cocok untuk desain game bergaya entity-component-system
      Namun 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 Clojure, flowstorm layak dilihat. Anda bisa melakukan step execution maju-mundur di dalam fungsi, dan mengirim map ke REPL bersama fungsi-fungsi tersebut
    • Menurut saya, tidak satu pun dari kedua bahasa itu menyediakan fitur mengirim form ke REPL sebagai bagian dari bahasanya sendiri; itu adalah fitur tool
      Di Cider hal itu jelas mudah dilakukan, dan tool utama di editor lain tampaknya juga cukup mampu melakukan hal serupa
    • Di Clojure juga, sampai batas tertentu, Anda bisa melakukan hal yang mirip dengan DISASSEMBLE

Ada 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

  • Steel Bank Common Lisp adalah pekerja keras yang memungkinkan saya membangun perusahaan perangkat lunak yang menguntungkan. Tanpanya, rasanya saya tidak akan seproduktif ini. Alur kerja berbasis REPL luar biasa, image Lisp sangat tangguh, dan performanya juga tinggi
    • Untuk yang penasaran, bisakah berbagi perusahaan-perusahaan itu yang mana?
    • Kelihatannya keren, tapi sekarang saya terlalu malas untuk kembali ke Emacs. Untuk pengembangan Python dan JavaScript, saya terutama memakai VSCode dengan konfigurasi yang hampir default. Karena peran saya CTO, saya tidak coding penuh waktu
    • Menurut saya masa ketika stack teknologi punya arti penting, meski sedikit, sebagian besar sudah lewat
      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
    • Katanya alur kerja berbasis REPL itu menakjubkan, tapi saya jadi bertanya-tanya apakah mereka tidak tahu bahwa sekarang hampir semua bahasa berbasis mesin virtual/interpreter punya REPL
      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
  • Kalau tertarik pada grafik 3D, ada codebase Common Lisp yang layak dilihat. Saya berusaha menjaganya tetap sederhana dan mudah dipahami
    https://github.com/kaveh808/kons-9
    • Coba cari video tutorial CL dari Kaveh di YouTube. Benar-benar dibuat dengan baik
  • Sebagai developer Clojure, break loop dan alur kerja berbasis REPL terlihat keren, dan rasanya pasti akan membantu Clojure juga. Ini bisa membuatnya lebih mirip coding frontend JS/TypeScript yang memakai alat debugging browser yang hebat. Sayangnya, kondisi tooling dan dukungan komunitas di ekosistem Clojure saat ini terlihat cukup minim
    • Clojure juga bisa meniru alur REPL sungguhan sampai batas tertentu jika pemanggilan dari bagian dalam lapisan kode terjadi pada tiap request, seperti di web server. Selama server terus berjalan, kita bisa menulis ulang dan memuat ulang beberapa fungsi, lalu mengirim request lagi dari browser
      Namun redefinisi dan penimpaan seperti ini pada akhirnya mengotori namespace, dan pada titik tertentu ada sesuatu yang rusak sehingga server harus dimuat ulang
  • Saya suka CL dan benar-benar merindukannya saat mengerjakan hal lain. Dibandingkan pengalaman Lisp puluhan tahun lalu, banyak hal di bahasa-bahasa “modern” terasa begitu menyakitkan sampai tidak lucu lagi
    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
    • Tidak mungkin ada Lisp tanpa cons
      Saat bekerja dengan C#, apa yang kamu rindukan dari Lisp?
    • Mengapa TypeScript tidak cocok untuk produk itu?
  • Di blog dan komentar penulis di sini banyak dibicarakan soal “coding”, tetapi hampir tidak terlihat jenis perangkat lunak apa yang dibuat dan use case apa yang ditargetkan
    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
    • Poin yang bagus. Saat ini semuanya hanya hobi. Yang saya buat dengan Common Lisp hanyalah klien Battlesnake biasa dan game multiplayer susun kata yang ditutup per kemarin. Keduanya tidak memberi keuntungan besar karena dibuat dengan Lisp, tetapi saya belajar banyak dalam prosesnya, dan itu inti utamanya

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

  • Common Lisp masih memiliki CLOS, salah satu sistem berorientasi objek paling maju hingga sekarang. Kebanyakan Lisp bukan bahasa fungsional seperti Haskell
  • Elixir memakai pemrograman fungsional, dan sangat unggul saat diperlukan pengembangan web atau sistem yang toleran terhadap kegagalan
    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
  • Butuh waktu untuk memahami monad dan IO monad, dan lebih lama lagi untuk mempelajari cara mengomposisikannya dengan aman serta mengatur urutan eksekusinya. Namun sekarang saya bisa menulis aplikasi yang type-safe, dan bahkan saat bekerja dengan bahasa non-fungsional pun membuat lebih sedikit bug. Bahasa yang saya pakai untuk dibayar adalah Java
    Menurut saya Lisp adalah titik awal, dan Haskell-lah yang sebenarnya. Saya menyarankan mempelajari gaya itu meski tidak menulis kode produksi sungguhan
  • Common Lisp bukan bahasa “fungsional murni”, melainkan mendukung semua paradigma. Hal aneh seperti ini pun diperbolehkan
    (let ((pair (cons 1 nil)))
    (setf (cdr pair) pair)
    (list (first pair) (second pair) (third pair)))
    ;; => (1 1 1)
  • Jika ingin sesuatu yang “akan berjalan selamanya”, tulislah dengan Rust
    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
  • Seperti ungkapan “karena ini tahun 2023, tentu saja saya belajar Common Lisp”, saya juga sedang mempelajarinya, tetapi sejauh ini fitur-fitur yang disebutkan belum terasa berguna. Mungkin pendapat saya akan berubah setelah terbiasa
    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
  • Intinya, Scheme dan Janet juga hebat, tetapi penulis menginginkan bahasa yang lebih standalone. Yang membuat perbedaan adalah breakloop, yaitu REPL penuh yang terbuka saat program mengalami error. Bukan stack trace atau debugger, melainkan membangun langsung dari titik yang sedang rusak
    • Kedengarannya benar-benar luar biasa, jadi mengapa Common Lisp bukan bahasa paling populer? Saya bertanya sebagai orang yang hampir tidak menulis kode
    • Sebagai developer Clojure, ini terdengar menarik, tetapi saya penasaran apa yang terjadi dalam kasus berikut. Ini bukan kode yang Lisp-idiomatis, hanya contoh untuk menunjukkan bagian yang tidak saya pahami
      (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 berubah
      Maka 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
    • Mendengar bahwa REPL penuh terbuka di titik error, dan bahwa ini bukan stack trace atau debugger melainkan membangun langsung dari titik yang sedang rusak, membuat saya ingin membuka Smalltalk image kecil
    • Hal seperti ini juga standar di Gambit Scheme
    • Bukankah hal-hal semacam ini bisa menjadi jalur serangan bagi kode berbahaya atau celah keamanan di produksi?