Filosofi REPL-first

Memahami REPL sebagai tempat utama menumbuhkan kode Clojure secara interaktif sejak hari pertama.

Daftar isi

REPL (read-eval-print loop) adalah mesin utama pengembangan pada Clojure: proses yang berjalan itu membaca satu form, mengevaluasinya, mencetak nilainya, lalu menunggu form berikutnya, dan putaran itu berulang sepanjang sesi. Topik ini menjelaskan cara REPL bekerja, menumbuhkan satu fungsi rata-rata dari eksperimen kecil melalui tiga langkah yang semuanya dijalankan sungguhan, lalu membandingkan ritme kerja ini dengan siklus kompilasi yang panjang. Siklus yang sama digunakan untuk setiap eksperimen pada jalur ini.

Apa yang dilakukan REPL#

REPL membaca satu form, mengevaluasinya di dalam proses yang sedang berjalan, mencetak nilai hasilnya, dan menunggu form berikutnya. Keempat tahap itulah yang dirangkum namanya, yaitu read, eval, print, dan loop. Prompt user=> menandakan form dievaluasi pada namespace user, namespace bawaan sesi yang baru dimulai. Sesi REPL dimulai dengan memanggil clojure tanpa argumen pada terminal, dan sesi diakhiri dengan Ctrl+D.

bash
# tidak-dijalankan
# mulai REPL interaktif, sesi berakhir dengan Ctrl+D
clojure

Blok di atas tidak dijalankan pada pembahasan ini karena REPL interaktif menunggu masukan dan tidak berakhir dengan sendirinya. Transkrip berikut memperlihatkan dua evaluasi pertama pada sesi yang baru dimulai, lengkap dengan prompt-nya.

text Transkrip REPL: dua evaluasi pertama
user=> (+ 40 2)
42
user=> (str "halo, " "Clojure")
"halo, Clojure"
user=>

Baris yang berawalan prompt adalah form yang diketik, sedangkan baris tanpa prompt adalah nilai yang dicetak REPL. String dicetak lengkap dengan tanda kutipnya karena nilai kembalian direpresentasikan sebagaimana tertulis di kode.

Siklus tulis, evaluasi, amati, perluas#

Bekerja dengan REPL berarti mengulang siklus empat langkah: menulis form, mengevaluasinya, mengamati nilai yang dicetak, lalu memperluas form yang sudah terbukti berjalan. Satuan perubahannya kecil, satu form saja, sehingga umpan balik datang seketika dan kesalahan ditemukan pada form yang tepat, bukan beberapa langkah kemudian. Tidak ada langkah build dan tidak ada proses baru yang harus dimulai di antara dua evaluasi, karena keadaan proses dipertahankan, termasuk dependensi yang sudah dimuat. Siklus ini juga mengubah cara pertanyaan kecil dijawab. Bentuk keluaran sebuah fungsi pustaka atau perilaku pembagian pecahan tidak perlu dicari lebih dahulu di dokumentasi, karena form kecil dapat diketik dan nilainya diamati langsung. Dokumentasi tetap digunakan, tetapi REPL menyelesaikan keraguan kecil dalam hitungan detik. Form yang sudah terbukti dipindahkan ke berkas sumber secara berkala, sedangkan definisi yang hanya hidup di REPL bersifat sementara dan hilang ketika sesinya berakhir. Contoh pada bagian berikut mengikuti siklus itu tiga kali: dimulai dari data literal dan perhitungan langsung, dilanjutkan dengan penamaan fungsi, dan diakhiri laporan yang menampilkan data per siswa.

Masalahnya sederhana: menghitung rata-rata nilai ujian lima siswa dengan skor [78 91 85 66 74]. Langkah pertama tidak mendefinisikan fungsi apa pun. Data literal langsung diolah dengan reduce dan count, lalu hasilnya diamati.

clojure
(def nilai-ujian [78 91 85 66 74])

(println "jumlah skor :" (reduce + nilai-ujian))
(println "banyak nilai:" (count nilai-ujian))
(println "rata-rata   :" (/ (reduce + nilai-ujian) (count nilai-ujian)))
text Keluaran
jumlah skor : 394
banyak nilai: 5
rata-rata   : 394/5

Hasil 394/5 adalah temuan yang layak diamati: pembagian bilangan bulat pada Clojure menghasilkan Ratio, pecahan eksak, bukan pembulatan desimal. Pada REPL, temuan semacam ini terlihat pada evaluasi pertama, jauh sebelum kode ditata. Hasil amatan itu dibawa ke langkah kedua, yaitu perhitungan yang terbukti diberi nama sebagai fungsi rata-rata, dan rasionya dikonversi dengan double agar laporan menampilkan desimal.

clojure
(def nilai-ujian [78 91 85 66 74])

(defn rata-rata [xs]
  (double (/ (reduce + xs) (count xs))))

(println "matematika:" (rata-rata nilai-ujian))
(println "bahasa    :" (rata-rata [82 79 88 71]))
text Keluaran
matematika: 78.8
bahasa    : 80.0

Langkah ketiga memperluas fungsi menjadi laporan per siswa. Setiap siswa direpresentasikan sebagai map, dan parameter fungsi menggunakan pola {:keys [nama nilai]} untuk mengikat nilai keyword :nama dan :nilai ke nama yang lebih pendek. Teknik pengikatan bentuk data pada parameter ini disebut destructuring. Fungsi format menyusun teks dengan pola: %s menyalin string, %.1f menampilkan bilangan pecahan dengan satu digit desimal, dan %d menampilkan bilangan bulat.

clojure
(def nilai-ujian [78 91 85 66 74])

(defn rata-rata [xs]
  (double (/ (reduce + xs) (count xs))))

(defn laporan-siswa [{:keys [nama nilai]}]
  (format "%s: rata-rata %.1f dari %d nilai" nama (rata-rata nilai) (count nilai)))

(println (laporan-siswa {:nama "Ali" :nilai nilai-ujian}))
(println (laporan-siswa {:nama "Citra" :nilai [82 79 88 71]}))
text Keluaran
Ali: rata-rata 78.8 dari 5 nilai
Citra: rata-rata 80.0 dari 4 nilai

Ketiga blok memuat ulang definisi yang diperlukannya agar masing-masing dapat dieksekusi sendiri-sendiri. Pada sesi REPL sungguhan, definisi tetap tersimpan di memori, sehingga langkah kedua dan ketiga tidak mengetik ulang baris yang sudah ada. Mengetik ulang itu murni kebutuhan penyajian di sini. Alur tiga langkah itulah inti REPL-first: form diuji, hasilnya diamati, lalu form yang terbukti diperluas menjadi bentuk berikutnya.

REPL dibandingkan siklus kompilasi panjang#

Perbedaan utama REPL dengan alur bahasa yang dikompilasi lebih dahulu terletak pada satuan perubahan dan jeda umpan baliknya. Tabel berikut merangkum perbedaan tersebut.

AspekREPLSiklus kompilasi panjang
Satuan perubahansatu formsatu berkas atau lebih
Jeda umpan balikseketikamenit atau lebih
Pengamatan hasilnilai langsung tercetakmelalui log keluaran program
Keadaan prosesterpelihara antar-evaluasidibangun ulang pada tiap perubahan

Keuntungan utamanya terletak pada eksplorasi. Data nyata dapat dimuat, diambil sebagian, dan diubah bentuknya sambil mengamati setiap langkah, termasuk mencoba pustaka yang baru ditambahkan tanpa menuliskan program pembungkus. Ritme ini juga mengubah cara kesalahan ditemukan, karena evaluasi yang gagal menghentikan satu form saja, sedangkan proses dan dependensi yang sudah dimuat tetap berjalan.

Risikonya juga perlu dikenali. Keadaan REPL adalah akumulasi seluruh definisi yang pernah dievaluasi, sehingga REPL dapat menyimpang dari berkas sumber bila definisi di berkas disunting tanpa dimuat ulang, atau bila definisi eksperimen tetap tertinggal di memori. Kebiasaan yang menjaga kesesuaian adalah menjadikan berkas sumber sebagai tempat penyimpanan tunggal dan memuat ulang berkas itu setelah setiap suntingan.

Kesalahan umum#

  • Menuliskan definisi penting hanya di REPL. Definisi yang belum dipindahkan ke berkas sumber hilang ketika sesi berakhir, dan REPL yang baru dimulai tidak memuatnya lagi. Jadikan berkas sumber tempat penyimpanan tunggal, lalu pindahkan form yang sudah terbukti sebelum eksperimen berlanjut.
  • Mengharapkan mode skrip mencetak nilai form. Skrip yang berisi (* (+ 1 2) 4) tanpa println tidak mencetak apa pun, karena mode skrip tidak mencetak nilai kembalian. Bungkus hasil yang ingin dilihat dengan println atau prn.
  • Membaca dua baris keluaran REPL sebagai satu hasil. Mengevaluasi (println "halo") di REPL memunculkan halo lalu nil: baris pertama adalah keluaran dari println, sedangkan nil adalah nilai kembalian form yang dicetak REPL. Keduanya berasal dari mekanisme yang berbeda.
  • Mengira REPL macet karena prompt tidak muncul kembali. Penyebab yang lazim adalah kurung yang belum tertutup, sehingga REPL masih menunggu sisa form dan menampilkan baris lanjutan alih-alih prompt. Tutup kurung yang kurang, dan bila strukturnya sudah kacau, mulai ulang sesi lalu muat ulang berkas sumber.

Ringkasan#

  • REPL mengevaluasi satu form pada proses yang sedang berjalan dan mencetak nilainya, sehingga satuan perubahan terkecil adalah form.
  • Fungsi tumbuh dari eksperimen: data literal dengan reduce, lalu fungsi bernama dengan double, lalu laporan berformat dengan destructuring.
  • Pembagian bilangan bulat menghasilkan Ratio eksak seperti 394/5, dan double mengubahnya menjadi desimal.
  • REPL dapat menyimpang dari berkas sumber, sehingga berkas dimuat ulang setelah setiap suntingan.

Referensi#