1 poin oleh GN⁺ 1 hari lalu | 1 komentar | Bagikan ke WhatsApp
  • Lone Lisp adalah Lisp yang berjalan langsung di atas system call Linux tanpa libc, dan dimulai oleh Matheus Moreira untuk melihat apakah bahasa lengkap dan program ruang pengguna bisa dibuat hanya dengan freestanding C
  • Dengan memanfaatkan langsung ABI ruang pengguna Linux yang stabil, ia menyingkirkan state global dan API warisan dari library C seperti errno, locale, serta buffering file implisit, lalu mengimplementasikan sendiri runtime, pengalokasi memori, dan alat pengujian
  • Mendukung generator, delimited continuation, penanganan error yang dapat dilanjutkan, termasuk FEXPR dan proper tail call optimization, serta memperlakukan vektor, tabel, dan continuation dengan cara pemanggilan fungsi yang sama
  • Startup-nya cepat, tetapi dalam benchmark sementara merupakan interpreter tingkat-list yang 10–100 kali lebih lambat daripada Python; interpreter C akan dipertahankan sebagai acuan bootstrap, dan dalam jangka panjang ia berencana mengimplementasikan compiler JIT di Lone sendiri
  • Moreira memanfaatkan Claude untuk peninjauan kode dan manajemen proyek, tetapi menulis dan meninjau sendiri kode Lone, sambil berupaya membangun ulang ruang pengguna Linux tradisional dengan membuat static site generator, shell, dan utilitas

Dari game ke C++, Ruby, dan Lisp

  • Pemicu ketertarikannya pada pemrograman adalah Mega Man Battle Network; ia belajar bahasa Inggris untuk berinteraksi di forum terkait, sekaligus mempelajari bahasa pertamanya, C++
  • Di sekolah menengah teknik di Brasil, ia mulai mengambil jurusan informatika sekitar usia 13–14 tahun
    • Menggunakan Dev-C++ dan tutorial cplusplus.com
    • Dalam 6 bulan ia menguasai seluruh kurikulum sekolah, lalu dipekerjakan sebagai asisten pengajar untuk membantu siswa lain
    • Ia juga mencoba membuat game, tetapi tidak berhasil karena kurangnya pengetahuan fisika yang diperlukan
  • Setelah C++, ia beralih ke Java, Ruby, Python, lalu mengenal Lisp dan Scheme; ia tertarik pada keanggunan bahasa Scheme
    • Ia mempelajari pemrograman berorientasi objek dengan membaca hampir seluruh tutorial Java dari Sun
    • Karena menyukai Java Swing dan desain Nimbus, ia juga membuat aplikasi menggambar lingkaran dan busur untuk dosen matematika
    • Di Ruby, ia menulis beberapa gem, termasuk parser argumen berbasis perintah bergaya Git, Acclaim
  • Dorongan untuk mengeksplorasi berbagai bahasa bermula dari proyek sekolah, tetapi kemudian berpusat pada rasa ingin tahu dan keinginan menemukan bahasa yang tepat untuk dirinya

Ekspresivitas Ruby dan transparansi C

  • Ruby sering kali tetap benar meski nama method hanya ditebak, serta mendukung bentuk tunggal/jamak dan sinonim, sehingga cocok dengan cara berpikirnya
    • Kombinasi seperti items.each.with_index.map memungkinkan banyak pekerjaan dilakukan dengan sedikit kode, mirip pipeline shell
    • Hingga kini ia selalu memasang Ruby, dan baru-baru ini juga memulai proyek Rails
  • Di C, fungsi berkorespondensi langsung dengan simbol ELF dan kode assembly, sehingga hasil kompilasi mudah dipahami
    • Ia menganggap C++ modern pada dasarnya sudah menjadi bahasa yang berbeda dari masa lalu, sehingga tidak lagi mengatakan bahwa ia tahu C++
    • Ia sempat mencoba pindah ke Rust tetapi tidak menetap; ia juga tertarik pada Zig dan penciptanya, yang melihat alasan menulis ulang dalam C pada kecepatan dan ABI
  • Saat remaja, ia membaca source VM CRuby dan mencari jawaban lewat implementasi untuk pertanyaan Stack Overflow “mengapa Ruby berperilaku seperti ini”
  • Setelah itu ia menjelajahi kode CRuby, CPython, JVM, Guile, dan V8, serta secara khusus menilai source kernel Linux sebagai tolok ukur pemrograman C profesional

Cara membaca kode orang lain

  • Ia tidak berhenti pada menggunakan library, tetapi terus membaca source untuk memahami implementasi internal, komunikasi antara driver dan hardware, serta lapisan-lapisan tersembunyi
  • Bahkan saat membuat aplikasi nyata, ia sering terseret ke eksplorasi teknologi dasarnya, dan muncul pula kecenderungan untuk membuat ulang masalah yang sudah terselesaikan dengan cara yang lebih baik
  • Karena menilai kecenderungan ini tidak cocok dengan karier pengembangan perangkat lunak pada umumnya, ia tidak memilih pemrograman sebagai profesi dan mempertahankannya sebagai hobi
  • Ia tidak langsung memahami kode driver sejak awal; meski frustrasi, ia terus membaca hingga mampu memahami sebagian perilakunya

Reverse engineering keyboard RGB

  • Aplikasi Windows untuk mengontrol keyboard RGB pada laptop barebone Clevo sangat lambat, sampai butuh lebih dari 1 menit untuk membuka jendela, sehingga ia membuat penggantinya sendiri
  • Dengan Wireshark, ia menangkap paket USB yang dikirim aplikasi ke keyboard, menganalisis strukturnya, lalu menulis driver C ruang pengguna Linux
  • Setelah mengunggahnya ke GitHub, ia sempat melupakannya, tetapi ternyata ada pengguna nyata; salah satu pengguna bahkan membuat GUI
  • Ia kemudian juga melakukan reverse engineering atas fitur laptop lain seperti manajemen daya ACPI, meski tidak berhasil mengungkap semua fitur

liblinux yang melewati libc

  • System call Linux memiliki kode yang cukup sederhana hingga mirip pemanggilan fungsi biasa, dan Linux bisa dijadikan target langsung tanpa melalui libc
  • Linux dikenal memiliki ABI ruang pengguna yang stabil, tetapi menurutnya komponen ruang pengguna tidak demikian
    • Ia menemukan catatan di manual bahwa beberapa system call tidak didukung glibc
    • Kasus LWN bahwa getrandom membutuhkan sekitar 2 tahun sebelum masuk ke glibc juga berpengaruh
  • Dari gagasan membuat liblinux.a yang hanya berisi system call dan menautkannya dengan -llinux, ia mengembangkan liblinux
    • Itu adalah kumpulan wrapper system call Linux untuk freestanding C
    • C runtime (CRT) berarti library C standar seperti glibc, musl, dan uclibc

Masalah yang hilang di freestanding C

  • Linux mengembalikan error sebagai nilai return biasa, tetapi library C menyimpannya ke variabel global errno
  • Dengan menghapus libc, berbagai state global dan API lama juga bisa dihindari
    • errno, rand/srand, state locale
    • strtok yang tidak reentrant
    • Buffering file implisit yang bisa menimbulkan masalah tak terduga
    • gets, atexit, signal, malloc
    • math.h yang menjadi rumit ketika digabung dengan NaN dan errno
  • Sebaliknya, aturan integer promotion dan tidak adanya pemeriksaan overflow adalah masalah C itu sendiri, sehingga tetap ada
    • Untuk overflow ia menggunakan fitur bawaan compiler
    • Ia juga mewaspadai kemungkinan dirinya membuat cacat baru
  • Tanpa standard library, ia harus menulis sendiri semuanya dari penyalinan byte hingga konversi integer→string, tetapi ternyata tidak semustahil yang dibayangkan
    • Repositori glibc sulit ditafsirkan, tetapi source musl libc bersih dan mudah dijelajahi, sehingga sangat membantu
    • Di Lone ia juga mengimplementasikan suite pengujian otomatis yang hanya memakai memori statis, tanpa library C dan tanpa alokasi memori dinamis

Menemukan nolibc dan awal Lone Lisp

  • Ia bertanya kepada Greg Kroah-Hartman mengapa Linux tidak memiliki library C resmi, dan mendapat jawaban bahwa dulu ada proyek klibc
  • Setelah itu ia menemukan bahwa para pengembang kernel Linux membuat nolibc.h untuk alat internal
    • Ia menilai proyek itu lebih lengkap dan lebih mudah digunakan daripada liblinux miliknya, lalu mengarsipkan proyeknya
    • Saat ini nolibc.h telah berkembang di dalam tree kernel menjadi direktori yang mirip libc sementara, tetapi jaminan stabilitas eksternal belum pasti
  • Sebagai proyek berikutnya, ia memulai Lone Lisp
    • Jika freestanding Lisp yang berjalan langsung di Linux bisa dibuat, ia menganggap program apa pun dapat ditulis dengan cara yang sama
    • Ini adalah eksperimen untuk menyingkirkan ruang pengguna Linux modern yang kompleks dan memulai lagi di atas antarmuka biner system call yang stabil
  • Ia juga mengirim patch ke Cosmopolitan, antara lain untuk implementasi parsing floating-point
    • Ia sangat menghargai format APE yang dapat ditafsirkan sekaligus sebagai format executable di berbagai platform, serta fitur arsip tertanam di dalam file executable

Penanganan error dan delimited continuation

  • Pada Lone awal, semua jalur error di interpreter berakhir dengan exit(255), tetapi kemudian ia mengimplementasikan sistem kondisi yang dapat dilanjutkan ala Common Lisp
  • Dasar penanganan error-nya adalah delimited continuation
    • Ia memahami continuation sebagai penyalinan stack frame dan pemutarannya ulang di lokasi lain
    • Exception juga dipandang sebagai salah satu bentuk continuation yang tidak menangkap nilai
  • Jenis error dipetakan dari simbol seperti 'not-found ke lambda
    • (lambda (error) ...) dengan satu argumen bertindak seperti handler exception biasa
    • (lambda (error continuation) ...) dengan dua argumen dapat memanggil continuation untuk melanjutkan kode yang error bersama sebuah nilai
  • Ia sengaja tidak mengadopsi restart API seperti use-value di Common Lisp, tempat kode yang mengirim sinyal mencantumkan pilihan restart
    • Mekanisme dasarnya disusun di dalam evaluator agar signal, throw, dan transfer berperilaku seolah-olah sekadar mengembalikan nilai

FEXPR dan elemen bahasa yang dapat dipanggil

  • Ia juga menghindari cara evaluator Lisp tradisional menangani special form seperti if secara langsung
  • Lone mendukung FEXPR, yang setara plugin evaluator, untuk memisahkan special form dari evaluator
    • Dengan desain ini, proper tail call optimization untuk fungsi Lisp murni bisa dilakukan lebih dulu, tetapi primitive sempat memiliki bug selama beberapa waktu
    • Masalah itu diselesaikan dengan menambahkan mekanisme terpisah agar primitive dapat return dari posisi tail
  • Vektor dan tabel juga dapat dipanggil
    • ([1 2 3] 0) mengembalikan 1
    • ([1 2 3] 0 4) mengubah vektor menjadi [4 2 3]
  • Continuation dan generator juga mengikuti konvensi pemanggilan yang sama
    • control mirip begin yang mendukung continuation
    • transfer keluar dari blok control terdekat dan mengembalikan continuation yang dapat dipanggil yang menunjukkan titik penghentian
    • Jika continuation tersebut dipanggil bersama nilai, komputasi yang terhenti dapat dilanjutkan dengan nilai return lain

System call dan struktur eksekusi

  • Sebelum Lone dimulai, proses induk memanggil execve, lalu Lone melakukan sekitar 12 kali mmap untuk manajemen memori internal
  • Ia membaca dan menjalankan skrip dari standard input lalu keluar; selama eksekusi, mmap, munmap, dan mremap tambahan dapat terjadi
  • Sistem modul menggunakan openat dan close
  • Dari lebih dari 300 system call Linux, hanya sebagian yang digunakannya sendiri, tetapi ia menyediakan system-call primitive di level Lisp agar program dapat memanggil yang lain juga
  • Untuk panggilan seperti ioctl yang membutuhkan struct C kernel, perlu ditambahkan kemampuan membuat dan memanipulasi struct
    • Mengambil ukuran terminal juga merupakan contoh yang membutuhkan dukungan seperti ini

Ruang pengguna yang ingin dibuat dengan Lone

  • Di level bahasa, ia telah mengimplementasikan penanganan error, proper tail call optimization, generator, dan delimited continuation; dalam jangka panjang ia juga ingin mengadopsi ide dari Erlang/BEAM
  • Struktur telah disiapkan agar beberapa interpreter dapat menjalankan kode Lisp secara paralel
  • Program pertama yang akan dibuat dengan Lone adalah pengganti static site generator berbasis fork PugJS yang saat ini ia gunakan
  • Setelah itu ia berencana menulis shell dan utilitas sendiri untuk membangun ulang ruang pengguna Linux tradisional dengan caranya
  • Ia juga pernah membuat implementasi yang mengekspor env sebagai tabel Lone Lisp; saat itu belum ada fitur iterasi, tetapi kini sudah mendukung generator
  • Interpreter juga memiliki fitur untuk membaca dan menjalankan kode Lisp dari segmen ELF khusus
    • Dengan menyalin binary interpreter dan menyisipkan kode Lisp, program standalone bisa dibuat

Peninjauan dan manajemen proyek lewat Claude

  • Meski total pekerjaannya tidak terbatas, ia menilai kumpulan fitur bahasa itu sendiri semakin mendekati selesai
  • Ia menggunakan Claude seperti semacam manajer, dan paling terbantu dalam peninjauan kode dan manajemen proyek
    • Ia memintanya mencari masalah, area yang belum dikembangkan, dan arah masa depan secara ketat di seluruh codebase
    • Pendekatan yang ditolak dan fitur yang akan ditangani nanti dikelola dalam dokumen dan memori proyek
    • Garbage collector dan pengalokasi memori sangat membaik karena Claude menunjukkan algoritme yang lebih baik
  • Ia meninjau sendiri setiap baris yang masuk ke Lone, dan karena penulisan kode sendiri juga dianggap edukatif, ia tidak memercayakan AI untuk menulis kode Lone menggantikannya
  • Sebaliknya, pada static site generator JavaScript, ia pernah membiarkan Claude menulis seluruh fitur
  • Fitur yang direncanakan mencakup API compiler GCC/Clang dan dukungan fungsi perlindungan stack

Sintaks yang diambil dari Ruby dan Scheme

  • Ia mencoba menggabungkan ekspresivitas Ruby dan keringkasan Scheme; karena parser Lisp sederhana, ia bisa menulisnya sendiri lalu langsung menangani nested list
  • Momen ketika ia benar-benar memahami Lisp adalah saat mengimplementasikan FEXPR, yaitu ketika fungsi menerima list itu sendiri, bukan nilai yang sudah dievaluasi
  • Array memakai sintaks [1 2 3], dictionary memakai sintaks {k v}
    • Selain Clojure dan newLisp, ini juga akrab bagi pengguna Ruby, Python, dan JavaScript, sehingga mengurangi hambatan masuk yang tidak perlu
  • Gaya coding C-nya menggunakan gaya kernel Linux yang dimodifikasi, tetapi ia lebih menyukai perataan dan ornamen
  • Di Ruby, ia lebih suka mengurangi tanda baca agar lebih mirip kalimat bahasa Inggris, seperti method 'arg' daripada method('arg')
  • Di Lisp juga ia mengurangi nesting sintaks
    • let di Lone berbentuk (let (a b c d ...) body...)
    • Ia menilai bentuk yang menempatkan nama dan nilai secara berurutan lebih alami untuk cara penerapan fungsi dibanding (let ((a b) (c d) ...) body...) tradisional

Performa dan rencana bootstrap

  • Benchmark yang dimenangkan Lone adalah kecepatan startup; ia dapat memulai dan mengakhiri program lebih cepat daripada waktu Ruby membaca Rubygems
  • Dalam pengukuran sementara, Lone sekitar 10–100 kali lebih lambat daripada Python
    • Lone adalah interpreter tingkat-list yang mirip bentuk pada SICP bab 5.4
    • Python dan Ruby adalah interpreter bytecode, sehingga berada di kategori performa berbeda
  • Ia menghindari kompilasi bytecode karena merasa mengubah list menjadi bytecode akan menghilangkan esensi Lisp
  • Dalam jangka panjang, interpreter C akan dipertahankan sebagai acuan bootstrap final, dan ia berencana menulis compiler JIT di dalam Lone
    • Caranya adalah memperoleh page yang dapat dieksekusi dengan mmap, lalu menghasilkan instruksi
  • Dalam perbandingan sementara Fibonacci rekursif dengan Bash, Lone jauh lebih cepat, tetapi ia mengakui distorsi pengukuran seperti kemungkinan Bash membuat proses pada setiap iterasi
  • Untuk skrip, waktu startup penting, sehingga bootstrap cepat Lone dianggap berguna
    • Jika hash table dihitung sebelumnya pada tahap build dan dijadikan data inisialisasi C, biaya inisialisasi saat runtime bisa semakin dikurangi

Memori dan lingkungan minimal

  • mmap meminta page memori kepada kernel, dan ukuran page umum adalah 4.096 byte
  • Hak baca, tulis, dan eksekusi dapat ditentukan, dan JIT menggunakan page yang dapat dieksekusi
  • Meski hanya membutuhkan 16 byte, meminta 4KiB menghasilkan pemborosan besar; karena itu pengalokasi umum mengambil area besar seperti 128MB lalu membaginya menjadi potongan kecil
  • Lingkungan top-level Lone hanya memiliki dua simbol, import dan export
    • Selain list, literal, dan mekanisme pemanggilan fungsi, semuanya sampai if dan quote harus diimpor
    • Ini adalah desain agar pengguna mengontrol sendiri semua nama di lingkungannya
  • Saat ini ada sekitar 50 fungsi, dengan pusat pada if, let, begin, control/transfer, intercept/signal, flatten, dan lainnya

Standard library dan dokumentasi

  • Dokumentasi bahasa masih belum lengkap, tetapi pekerjaan standard library semakin dekat dibanding fitur bahasa
  • Ia berencana merujuk standard library Ruby dan gaya penamaan Scheme
  • Pekerjaan berikutnya adalah protokol iterasi
    • Ia mempertimbangkan bentuk (for (each collection) (lambda (item) ...))
    • Semua fungsi each yang ada akan didesain ulang agar mengembalikan generator
  • Setelah itu ia ingin mengimplementasikan manipulasi string dan string builder untuk penggabungan yang efisien
  • Jika iterasi dan fitur string selesai, ia menilai framework web berbentuk Hiccup juga dapat ditulis
  • Ia khawatir fokusnya akan menurun ketika fitur low-level selesai dan pekerjaan beralih ke standard library yang repetitif, sehingga berencana memanfaatkan bantuan manajemen dari Claude

Tujuan memiliki seluruh stack sendiri

  • Fitur yang tampak misterius seperti continuation pun menjadi masalah implementasi biasa jika dipahami sebagai penyalinan stack frame, dan Lone bertujuan mendorong orang lain membuat serta menjelajahi sistem mereka sendiri
  • Ia tidak ingin hanya menjadi konsumen library, tetapi menjadi pembuat library, serta ingin memakai langsung system call yang bagi pengembang umum seolah diperlakukan sebagai sesuatu yang terlarang
  • Untuk memiliki sistem sendiri, ketika rusak seseorang harus memperbaikinya sendiri, tetapi ia berharap lebih banyak orang memilih tanggung jawab sekaligus ganjaran itu
  • Ia menilai orang tidak perlu takut pada kode lama yang menumpuk dalam komputasi, dan lapisan yang tidak perlu bisa dihapus
    • Pihak yang membangun ulang ruang pengguna Linux mungkin bukan dirinya, melainkan pengembang Rust atau Zig
    • Meski Lone tidak berhasil, ia akan puas jika upaya seperti ini dapat menginspirasi

Free software dan Android

  • Ia menekankan penguatan hak pengguna yang menjadi tujuan free software, dan khawatir komputer serbaguna menyusut menjadi seperti peralatan rumah tangga yang dikelola perusahaan atau pemerintah
  • systemd dinilai sebagai contoh sukses dalam membayangkan ulang dan membangun kembali ruang pengguna Linux tradisional, meski pendekatannya cenderung berat
  • Ia menganggap seseorang harus memilih pertarungannya, alih-alih membuat semua lapisan sendiri
    • Jika Lone dibuat sebagai bare-metal Lisp, mungkin ia bahkan tidak akan mencapai tahap booting
    • Ia menyebut Sector Lisp dari jart, Lisp boot sector berukuran di bawah 512 byte, sebagai contoh
  • Lone dimulai di dalam Termux dan sebagian besar ditulis dari ponsel
  • Android memblokir beberapa system call termasuk statx, tetapi Lone berjalan di Android dan test suite-nya lulus 100%

Dokter dan programmer nonprofesional

  • Moreira adalah dokter yang lulus fakultas kedokteran pada 2019
    • Saat SMA, ia membaca tentang pemrograman neural network lalu tertarik pada biologi
    • Jadwalnya kadang 10–14 jam praktik pada hari tertentu dan libur pada hari lain, sehingga ia bisa menyediakan waktu untuk pemrograman
  • Di rumah sakit yang memakai rekam medis elektronik, ia sangat mempersingkat pekerjaan dengan template dan kemampuan mengetik
    • Ia dapat mencatat sambil tetap menatap pasien
    • Proyek Rails yang baru dimulainya adalah sistem rekam medis pasien elektronik untuk memenuhi kebutuhannya sendiri
  • Salah satu alasan pengembang profesional tidak menyelesaikan masalah pribadi secara langsung, menurutnya, adalah kurangnya minat pada komputer itu sendiri
  • Untuk menggali bagian dalam implementasi, dibutuhkan rasa ingin tahu, bukan sekadar sikap ingin mendapatkan hasil; ia belum tahu bagaimana membangkitkan rasa ingin tahu itu pada orang lain

1 komentar

 
GN⁺ 1 hari lalu
Komentar di Lobste.rs
  • Saya sangat menyukai seri wawancara ini sampai selalu membacanya tanpa terlewat, dan sangat menarik melihat hubungan pribadi masing-masing orang dengan komputasi

    • Yang terutama menarik adalah mengumpulkan dan membandingkan awal mula yang membuat orang menyayangi komputer lebih dari sekadar alat
      Shonumi, penulis emulator GBE+ GBA, juga dipengaruhi oleh game Mega Man Battle Network seperti saya, dan suatu hari saya ingin membaca wawancaranya juga
  • Exception yang dapat dimulai ulang tidak selalu membutuhkan first-class continuation
    Common Lisp menyediakan exception yang dapat dimulai ulang meski tanpa continuation, dan intinya adalah tidak melakukan rewind stack sebelum memanggil handler
    Karena itu, stack dan state tempat exception terjadi tetap dipertahankan sehingga handler bisa return secara normal, atau seperti di Common Lisp, bisa melompat ke restart handler di suatu tempat dalam stack

    • Jika saya memberi kesan bahwa itu wajib, itu karena saya terlalu bersemangat dengan pencerahan saya
      Namun di Lone Lisp, continuation yang dikembalikan bisa dipanggil berkali-kali dan bisa keluar dari signal handler, jadi memang diperlukan dalam praktiknya
      (import (lone print set quote lambda intercept signal))
      
      (set continuation  
        (intercept  
          (('return (lambda (value continuation) continuation)))  
          (signal 'return 'value)))
      
      (print (continuation 1)); 1  
      (print (continuation 2)); 2  
      (print (continuation 3)); 3  
      
      Karena generator menjadi dasar pemrosesan iteratif, performanya lebih penting; jadi alih-alih continuation terbatas, saya memakai stack terpisah untuk menghilangkan penyalinan
      Penanganan error adalah jalur yang relatif jarang dilalui, jadi saya memutuskan untuk menyediakan fiturnya secara utuh
  • Bagi banyak orang, pekerjaan atau proyek terpenting mungkin adalah sesuatu yang dilakukan untuk bersenang-senang di waktu luang tanpa bayaran
    Saya jadi membayangkan bahwa dokter saya pun bisa menjalani kehidupan ganda secara diam-diam, membuat bahasa Lisp miliknya sendiri

    • Ternyata bukan hanya saya yang seperti itu
      Dr. Cameron Kaiser juga mengerjakan proyek-proyek besar, bahkan berbagi filosofi tentang kepemilikan mesin, dan sehari-hari menggunakan workstation POWER9 yang sepenuhnya terbuka
  • Saya baru mengetahui Lone Lisp kali ini, dan proyeknya tampak keren
    Saya juga mulai belajar C++ dengan Dev-C++ lama di kelas informatika sekitar usia 13–14 tahun, dan mengingatnya lagi setelah sekian lama benar-benar membangkitkan nostalgia

    • Mengejutkan bahwa Dev-C++ masih terus bertahan dan bahkan merilis versi pada 2021
  • Saya sama sekali tidak menyangka dia seorang dokter, dan ini mengingatkan saya pada Con Kolivas
    Rupanya cukup banyak developer dokter seperti ini
    https://en.wikipedia.org/wiki/Con_Kolivas