REPL

Menggunakan konsol Scala CLI untuk mengevaluasi ekspresi, memeriksa tipe, dan menjajal sintaks dengan cepat.

Daftar isi

REPL (Read-Eval-Print Loop) adalah konsol interaktif yang membaca satu ekspresi, mengevaluasinya, mencetak hasilnya, lalu mengulangi siklus itu sampai sesi ditutup. Topik ini membahas cara memulai REPL Scala, cara hasil dievaluasi dan diberi nama, pemeriksaan tipe dengan perintah :type, serta batas REPL dibandingkan dengan berkas program. Setelah membaca topik ini, ekspresi kecil seperti aritmetika, definisi nilai, dan definisi fungsi dapat diuji tanpa membuat satu berkas pun.

Cara kerja REPL#

REPL Scala dimulai dengan mengetik scala-cli repl pada terminal. Scala CLI menampilkan sapaan pembuka yang memuat versi Scala dan versi JVM yang digunakan, lalu tanda scala> menandai tempat ekspresi diketik. Satu ekspresi diketik, tombol Enter ditekan, dan hasilnya dicetak pada baris berikutnya. Siklus ini berulang sampai perintah :quit dituliskan.

Nama REPL merangkum siklusnya. Read membaca teks yang diketik, eval mengevaluasi teks itu sebagai ekspresi Scala, print mencetak hasil evaluasinya, dan loop mengulanginya dari awal. Hasil yang dicetak selalu menyertakan tipe nilainya, sehingga REPL sekaligus berperan sebagai pemeriksa tipe yang cepat.

Sifat REPL yang paling perlu dipahami sejak awal adalah kecenderungannya untuk tidak menyimpan apa pun. Apabila sesi ditutup, seluruh definisi yang pernah diketik di dalamnya hilang, sehingga REPL menjadi tempat percobaan, sedangkan program yang layak disimpan tetap ditulis di berkas.

Mengevaluasi ekspresi#

Sesi REPL paling sederhana adalah mengetik ekspresi dan membaca hasilnya. Transkrip berikut memuat empat ekspresi yang diketik berurutan dalam satu sesi.

scala Sesi REPL: mengevaluasi ekspresi
scala> 2 + 3
val res0: Int = 5

scala> val name = "Scala"
val name: String = "Scala"

scala> name.toUpperCase
val res1: String = "SCALA"

scala> List(1, 2, 3).map(_ * 10)
val res2: List[Int] = List(10, 20, 30)

Hasil yang tidak diberi nama otomatis diberi nomor res0, res1, dan seterusnya mengikuti urutan sesi, sedangkan val name menyimpan nilainya di bawah nama itu sendiri dan tidak menggunakan nomor. Definisi pada satu sesi tetap hidup sampai sesi ditutup, sebagaimana terlihat pada ekspresi ketiga yang menggunakan nilai name dari ekspresi kedua. Baris hasil juga selalu menampilkan tipe di samping nilai, sehingga bentuk List[Int] pada hasil terakhir memberi tahu bahwa operasi map menghasilkan daftar bertipe Int. Penomoran res hanya berlaku di dalam satu sesi, dan sesi baru selalu memulai dari res0.

Memeriksa tipe#

Kadang yang dibutuhkan bukan nilainya, melainkan tipenya. Perintah :type menampilkan tipe sebuah ekspresi tanpa menyimpan hasilnya, sehingga pemeriksaan ini tidak mengambil nomor res. Transkrip berikut memeriksa dua ekspresi, lalu menunjukkan satu perilaku pembagian yang sering mengejutkan pemula.

scala Sesi REPL: memeriksa tipe
scala> :type 42 + 8
Int

scala> :type "data".toUpperCase
String

scala> val ratio: Double = 3 / 4
val ratio: Double = 0.0

scala> 3 / 4.toDouble
val res0: Double = 0.75

Ekspresi 3 / 4 adalah pembagian antara dua Int, sehingga hasilnya Int bernilai 0, dan nilai itu baru dilebarkan menjadi Double 0.0 ketika dimasukkan ke val ratio: Double. Hasil pecahan baru muncul apabila salah satu sisinya bertipe Double lebih dahulu, seperti pada 4.toDouble, sehingga pembagiannya menghasilkan 0.75. Dua panggilan :type di awal tidak mengambil nomor res, sehingga pembagian terakhir tetap bernama res0. Perilaku semacam ini paling cepat ditemukan di REPL, jauh sebelum kode yang sama ditulis ke dalam program.

Mendefinisikan fungsi#

Fungsi kecil dapat didefinisikan langsung di REPL untuk diuji sebelum dipindahkan ke berkas. Baris definisi dicetak ulang oleh REPL dalam bentuk tanda tangannya, tanpa nomor res, karena definisi tidak menghasilkan nilai.

scala Sesi REPL: mendefinisikan fungsi
scala> def square(n: Int): Int = n * n
def square(n: Int): Int

scala> square(7)
val res0: Int = 49

scala> square(2 + 3)
val res1: Int = 25

Definisi square menerima satu parameter Int dan mengembalikan Int. Pemanggilannya membuktikan bahwa argumen boleh berupa ekspresi lain, seperti 2 + 3, yang dievaluasi lebih dahulu sebelum diteruskan ke fungsi. Apabila definisi perlu diperbaiki, definisi yang sama cukup diketik ulang dengan isi yang baru, dan nama lama tertimpa oleh definisi yang baru. Pola kerja mendefinisikan lalu segera memanggil adalah cara yang cepat untuk menguji logika kecil, dan penomoran res0 lalu res1 mengikuti urutan pemanggilan yang menghasilkan nilai.

Dari REPL ke berkas#

REPL dan berkas program memenuhi kebutuhan yang berbeda. REPL digunakan untuk percobaan yang cepat, sedangkan berkas digunakan untuk program yang dijalankan berulang. Apabila sebuah eksperimen di REPL sudah stabil dan layak disimpan, isi sesinya dipindahkan ke berkas, dilengkapi penanda @main, lalu dijalankan seperti program biasa.

scala average.scala
// Eksperimen REPL yang sudah stabil disimpan sebagai program kecil
@main def average(): Unit =
  val readings = List(24, 26, 25, 27)
  val mean = readings.sum / readings.size.toDouble
  println(f"Rata-rata suhu: $mean%.1f derajat")
bash
scala-cli run average.scala
text Keluaran
Rata-rata suhu: 25.5 derajat

Program di atas adalah versi tersimpan dari percobaan rata-rata. Pembagiannya menggunakan pola yang sama dengan sesi pemeriksaan tipe, yaitu penyebut yang dikonversi lebih dahulu dengan toDouble agar hasilnya pecahan. Hasilnya dicetak melalui interpolator f, yaitu pembentuk string yang mendukung penanda format seperti %.1f, agar tampil dengan satu angka di belakang koma. Setelah berada di berkas, program tersebut dapat dijalankan berulang, disunting, dan diperluas, hal-hal yang tidak disediakan oleh sesi REPL.

Kesalahan umum#

Mengira isi sesi tersimpan di suatu tempat. REPL tidak menulis definisi apa pun ke berkas. Menutup terminal berarti kehilangan seluruh definisi yang belum dipindahkan. Perbaikannya adalah membiasakan pemindahan, yaitu eksperimen yang sudah benar segera disalin ke berkas dengan penanda @main.

Mengandalkan nomor res di luar sesinya. Nomor res0 dan seterusnya hanya berlaku pada sesi tempat nomor itu dibuat, dan sesi baru memulai penomoran dari awal. Rujukan pada res3 dari sesi kemarin tidak berarti apa-apa pada sesi hari ini. Perbaikannya adalah memberi nama pada nilai yang penting melalui val, sehingga rujukannya stabil dan bermakna.

Menempelkan program panjang ke REPL. Program yang lebih dari beberapa baris sulit diperbaiki di REPL karena satu kesalahan ketik memaksa seluruh barisnya diketik ulang. Tempat kerja untuk program sebesar itu adalah berkas sumber, sedangkan REPL digunakan untuk menguji potongan-potongan kecilnya.

Ringkasan#

  • REPL membaca satu ekspresi, mengevaluasinya, dan mencetak hasilnya beserta tipenya, sehingga sekaligus berfungsi sebagai pemeriksa tipe.
  • Hasil tanpa nama otomatis bernama res0, res1, dan seterusnya dalam satu sesi, sedangkan val menyimpan nilai di bawah namanya sendiri.
  • Perintah :type menampilkan tipe ekspresi tanpa mengambil nomor res.
  • REPL tidak menyimpan apa pun antarsesi, sehingga eksperimen yang stabil dipindahkan ke berkas dengan penanda @main.

Referensi#