Ekspresi dan definisi

Model segala sesuatu adalah ekspresi, penulisan definisi, serta perbandingan where dan let.

Daftar isi

Program Haskell disusun dari bahan yang sama sejak awal hingga akhir, yaitu ekspresi (expression) yang menghasilkan nilai. Topik ini membahas model tersebut, aturan penulisan definisi (definition) tingkat atas dan definisi lokal, serta perbandingan dua bentuk pengikatan yang disediakan Haskell, yaitu where dan let. Setelah membacanya, program sederhana dapat disusun dengan definisi yang rapi, dan pilihan antara where dan let dapat ditentukan dengan alasan yang jelas.

Segala sesuatu adalah ekspresi#

Ekspresi adalah bagian program yang menghasilkan nilai. Angka 7, penjumlahan 3 + 4, pemanggilan fungsi, hingga percabangan if-then-else semuanya merupakan ekspresi karena menghasilkan nilai. Model ini berbeda dari bahasa imperatif yang memisahkan ekspresi dari pernyataan (statement), yaitu instruksi yang dijalankan tanpa menghasilkan nilai. Dalam Haskell tidak ada pernyataan semacam itu, sehingga sebuah program dapat dibaca sebagai rangkaian penghitungan nilai dari masukan hingga keluaran.

Karena setiap ekspresi menghasilkan nilai, percabangan pun menghasilkan nilai. Program berikut menyimpan hasil if-then-else ke dalam sebuah definisi, sesuatu yang tidak mungkin dilakukan pada pernyataan if bahasa imperatif. Kedua cabangnya juga wajib menghasilkan nilai bertipe sama karena hasil akhirnya adalah nilai dari salah satu cabang.

haskell ekspresi.hs
-- Setiap bagian program adalah ekspresi yang menghasilkan nilai
rataRata :: Double
rataRata = (7 + 8 + 9) / 3

hasil :: String
hasil = if rataRata >= 8 then "lulus" else "berlatih"

main :: IO ()
main = do
  print rataRata
  putStrLn hasil
text Keluaran
8.0
lulus

Kedua baris keluaran memperlihatkan dua nilai yang dihitung dari ekspresi. Sifat ekspresional ini dapat dicoba cepat melalui perintah ghc -e, yaitu opsi compiler untuk mengevaluasi ekspresi langsung tanpa membuat berkas program.

bash
# dua ekspresi dievaluasi langsung tanpa membuat berkas program
ghc -e '1 + 2 * 3'
ghc -e 'if 5 > 3 then "benar" else "salah"'
text Keluaran
7
"benar"

Definisi tingkat atas#

Definisi mengikat satu nama ke satu nilai. Pada tingkat atas, yaitu bagian berkas program yang tidak berada di dalam definisi mana pun, definisi ditulis sebagai nama = ekspresi tanpa kata kunci. Anotasi tipe di atasnya seperti jumlahHari :: Int bersifat pilihan, tetapi lazim ditulis karena berfungsi sebagai dokumentasi yang diperiksa compiler. Nilai yang terikat tidak pernah berubah, sehingga pembacaan nama yang sama di mana pun menghasilkan nilai yang sama.

Program berikut menggunakan beberapa definisi tingkat atas yang saling merujuk.

haskell definisi-tingkat-atas.hs
-- Definisi tingkat atas mengikat satu nama ke satu nilai
namaKegiatan :: String
namaKegiatan = "jadwal piket kelas"

jumlahHari :: Int
jumlahHari = 6

jamPerHari :: Double
jamPerHari = 2.5

totalJam :: Double
totalJam = fromIntegral jumlahHari * jamPerHari

main :: IO ()
main = do
  putStrLn namaKegiatan
  print jumlahHari
  print totalJam
text Keluaran
jadwal piket kelas
6
15.0

Definisi totalJam menunjukkan bahwa definisi boleh merujuk definisi lain. Fungsi fromIntegral mengubah nilai Int menjadi Double agar perkaliannya dengan jamPerHari sah, sebab konversi antar tipe tidak dilakukan secara diam-diam. Urutan penulisan definisi tidak menentukan hasil, sehingga totalJam boleh ditulis sebelum definisi yang dirujuknya.

Anotasi tipe juga berperan sebagai alat berpikir sebelum tubuh definisi ditulis, karena satu baris tersebut merangkum bentuk masukan dan keluaran. Apabila anotasi dan tubuh tidak cocok, compiler menunjuk ketidakcocokannya dan program tidak jadi dibangun. Anotasi pada konstanta seperti jumlahHari :: Int memang terlihat jelas tanpa bantuan, tetapi kebiasaan menulisnya membangun pembiasaan yang berguna untuk tipe yang lebih rumit.

Definisi lokal dengan where#

Tidak semua nilai pendukung perlu menjadi definisi tingkat atas. Kata kunci where menyediakan definisi lokal yang hanya terlihat oleh definisi yang dilayaninya, sehingga nama pembantu tidak bocor ke luar. Program berikut menggunakan where untuk dua keperluan, yaitu nilai-nilai persamaan kuadrat dan konversi tinggi badan ke meter.

haskell definisi-where.hs
-- Definisi lokal where menaruh nilai pendukung di bawah tubuh definisi
akarPositif :: Double
akarPositif = (-b + sqrt diskriminan) / (2 * a)
  where
    a = 1.0
    b = -3.0
    c = 2.0
    diskriminan = b * b - 4 * a * c

-- Definisi lokal where bebas menggunakan parameter fungsi
indeksMassa :: Double -> Double -> Double
indeksMassa beratKg tinggiCm = beratKg / (tinggiMeter * tinggiMeter)
  where
    tinggiMeter = tinggiCm / 100

main :: IO ()
main = do
  print akarPositif
  print (indeksMassa 80 200)
text Keluaran
2.0
20.0

Definisi lokal tinggiMeter menggunakan parameter tinggiCm dengan bebas karena berada dalam lingkup fungsi indeksMassa. Satu hal yang wajib dijaga adalah indentasi, sebab Haskell menentukan batas blok dari kolom, bukan dari tanda kurung. Klausa where harus ditulis lebih menjorok daripada awal persamaan yang dilayaninya, dan seluruh definisi di dalamnya harus sejajar pada satu kolom. Contoh berikut ditolak compiler karena where berada di kolom yang sama dengan awal persamaannya.

haskell Contoh: where yang kurang menjorok
-- tidak-dijalankan
akarPositif :: Double
akarPositif = (-b + sqrt diskriminan) / (2 * a)
where
  a = 1.0
  b = -3.0
  c = 2.0
  diskriminan = b * b - 4 * a * c

Compiler menolak contoh tersebut dengan pesan parse error. Perbaikannya sederhana, yaitu menjorokkan where satu tingkat dari persamaannya sebagaimana program yang benar di atas, lalu menyelaraskan seluruh definisi lokalnya.

Ekspresi let in#

Bentuk pengikatan kedua adalah let ... in, yang mengikat nama di dalam satu ekspresi. Strukturnya let pengikatan in ekspresi, dan nama yang diikat hanya terlihat pada ekspresi setelah in. Karena berupa ekspresi penuh, bentuk ini dapat ditempatkan di mana pun ekspresi diperbolehkan, termasuk di dalam penjumlahan maupun argumen fungsi.

haskell definisi-let.hs
-- let mengikat nama lokal di dalam satu ekspresi
volumKubus :: Int
volumKubus =
  let sisi = 4
  in sisi * sisi * sisi

main :: IO ()
main = do
  let pengali = 10
  print volumKubus
  print (volumKubus * pengali)
text Keluaran
64
640

Program di atas memperlihatkan dua bentuk penggunaan. Blok let ... in mengikat sisi untuk tubuh volumKubus, sedangkan baris let pengali = 10 di dalam main adalah varian blok do yang mengikat nama untuk sisa blok tersebut tanpa kata kunci in. Keduanya menyelesaikan kebutuhan yang sama dengan where, yaitu memberi nama pada nilai antara, dengan lingkup yang lebih ketat.

Memilih where atau let#

Kedua bentuk sering dapat dipertukarkan, sehingga pilihannya lebih ditentukan oleh kejelasan daripada keharusan. Tabel berikut merangkum perbedaan yang menentukan.

Aspekwherelet ... in
Bentukklausa milik satu definisiekspresi penuh
Letakdi bawah tubuh definisidi mana pun ekspresi sah
Lingkup namaseluruh tubuh definisimulai in hingga akhir ekspresi
Penggunaan lazimnilai pembantu fungsi, termasuk bagi guardpengikatan sesaat dan pengikatan di blok do

Aturan praktisnya, gunakan where apabila definisi lokal melayani satu fungsi dan perlu terlihat oleh seluruh tubuhnya, terutama ketika fungsi tersebut memiliki banyak cabang. Gunakan let apabila pengikatan hanya berlaku pada satu ekspresi atau apabila kode berada di dalam blok do. Konsistensi pilihan di dalam satu proyek lebih penting daripada preferensi pribadi, karena pembaca mengandalkan pola yang tetap.

Ringkasan#

  • Hampir semua bagian program Haskell adalah ekspresi yang menghasilkan nilai, termasuk percabangan if-then-else.
  • Definisi mengikat satu nama ke satu nilai yang tidak berubah, dengan anotasi tipe pilihan yang berperan sebagai dokumentasi yang diperiksa compiler.
  • where menyediakan definisi lokal untuk satu definisi dan menuntut indentasi yang benar karena batas blok ditentukan dari kolom.
  • let ... in adalah ekspresi pengikatan yang lingkupnya sebatas ekspresi setelah in, sedangkan bentuk let di blok do mengikat nama untuk sisa blok.

Referensi#