Apa itu Haskell?

Karakter inti Haskell: fungsi murni, data immutable, evaluasi lazy, dan tipe statis kuat, beserta ranah di mana bahasa ini layak digunakan.

Daftar isi

Haskell adalah bahasa pemrograman fungsional murni dengan sistem tipe statis yang kuat dan evaluasi lazy. Topik ini membahas empat karakter inti bahasa tersebut, yaitu fungsi murni, data immutable, evaluasi lazy, dan pemeriksaan tipe pada waktu kompilasi, beserta dua program kecil yang dapat dijalankan. Setelah membacanya, karakter Haskell dapat dibedakan dari bahasa imperatif pada umumnya dan ranah penggunaan yang sesuai dapat ditimbang dengan dasar yang jelas.

Fungsi murni tanpa efek samping#

Haskell memandang fungsi sebagaimana fungsi pada matematika, yaitu pemetaan dari nilai masukan ke nilai keluaran. Fungsi murni (pure function) tidak menimbulkan efek samping (side effect), yakni perubahan terhadap keadaan di luar perhitungannya, misalnya mengubah variabel global, menulis ke berkas, atau mencetak ke layar. Karena tidak ada keadaan yang ikut berubah, fungsi yang sama selalu menghasilkan keluaran yang sama untuk masukan yang sama. Sifat ini membuat hasil pemanggilan fungsi dapat digantikan dengan nilainya tanpa mengubah perilaku program, suatu sifat yang disebut transparansi referensial (referential transparency).

Program berikut memuat dua fungsi murni untuk konversi suhu. Fungsi celciusKeFahrenheit memetakan suhu Celsius ke Fahrenheit, sedangkan selisihSuhu menggunakan hasil fungsi pertama. Bagian main adalah satu-satunya tempat program menyentuh dunia luar, yaitu mencetak nilai ke layar, dan tipe IO pada bagian itu menandai adanya efek samping secara eksplisit.

haskell konversi-suhu.hs
-- Fungsi murni: keluaran hanya ditentukan oleh masukan
celciusKeFahrenheit :: Double -> Double
celciusKeFahrenheit c = c * 9 / 5 + 32

selisihSuhu :: Double -> Double -> Double
selisihSuhu a b = celciusKeFahrenheit a - celciusKeFahrenheit b

main :: IO ()
main = do
  print (celciusKeFahrenheit 100)
  print (celciusKeFahrenheit 100)
  print (selisihSuhu 100 0)
text Keluaran
212.0
212.0
180.0

Dua pemanggilan pertama menerima masukan yang sama dan menghasilkan keluaran yang sama, sesuai janji fungsi murni. Tidak ada variabel yang ditimpa dan tidak ada keadaan yang tersimpan di antara dua pemanggilan tersebut. Efek mencetak ke layar dipisahkan ke dalam main, sehingga kedua fungsi di atas tetap dapat diuji tanpa layar maupun berkas.

Data immutable#

Nilai dalam Haskell bersifat immutable, yaitu tidak dapat diubah setelah dibuat. Yang pada bahasa lain disebut mengubah data dinyatakan di Haskell sebagai fungsi yang menerima nilai lama dan menghasilkan nilai baru, sedangkan nilai lama tetap utuh. Program berikut menunjukkan perilaku tersebut pada list, yaitu struktur data urutan nilai yang bertipe sama.

haskell immutable-lazy.hs
-- Nilai tidak diubah setelah dibuat: fungsi menghasilkan nilai baru
-- Evaluasi lazy: hanya bagian yang diminta yang dihitung
angka :: [Int]
angka = [3, 1, 4, 1, 5]

terbalik :: [Int]
terbalik = reverse angka

main :: IO ()
main = do
  print angka
  print terbalik
  print (take 3 [1 ..])
text Keluaran
[3,1,4,1,5]
[5,1,4,1,3]
[1,2,3]

Fungsi reverse tidak membalik list angka di tempatnya, melainkan menghasilkan list baru bernama terbalik. Baris pertama dan kedua keluaran menunjukkan bahwa kedua nilai itu tetap tersedia tanpa saling menimpa. Apabila banyak bagian program memegang rujukan ke angka, tidak ada satu pun rujukan yang berubah tiba-tiba, sehingga penelusuran alur data lebih mudah diikuti.

Evaluasi lazy#

Haskell mengevaluasi ekspresi secara lazy (lazy evaluation), artinya ekspresi tidak dihitung saat didefinisikan, melainkan saat hasilnya benar-benar dibutuhkan. Baris print (take 3 [1 ..]) pada program sebelumnya memanfaatkan sifat ini. Notasi [1 ..] menyatakan list bilangan bulat yang naik tanpa batas, sesuatu yang tidak mungkin dihitung seluruhnya, tetapi take 3 hanya meminta tiga elemen pertama, sehingga hanya tiga perhitungan yang perlu dilakukan.

Perintah ghc -e mengevaluasi satu ekspresi melalui compiler tanpa perlu membuat berkas program. Ekspresi berikut menyaring bilangan genap dari list tak hingga [1 ..] dan mengambil lima elemen pertamanya.

bash
# ambil lima bilangan genap pertama dari list tak hingga
ghc -e 'take 5 (filter even [1 ..])'
text Keluaran
[2,4,6,8,10]

Evaluasi lazy membuat pola seperti ini sah. Fungsi filter tidak mengolah seluruh list, melainkan berhenti segera setelah take mendapatkan jumlah elemen yang diminta. Perhitungan yang tidak pernah diminta hasilnya tidak pernah dilakukan.

Tipe statis yang kuat#

Setiap ekspresi dalam Haskell memiliki tipe yang diketahui pada waktu kompilasi (compile time), sebelum program dijalankan. Haskell juga kuat (strongly typed) dalam arti konversi antar tipe tidak dilakukan secara diam-diam, sehingga penjumlahan antara bilangan dan teks ditolak compiler. Banyak bug yang berasal dari kekeliruan tipe tertangkap sebelum program sempat berjalan.

Baris celciusKeFahrenheit :: Double -> Double pada program pertama adalah anotasi tipe, yaitu pernyataan bahwa fungsi menerima satu Double dan menghasilkan satu Double. Apabila tubuh fungsi tidak sesuai janjinya, compiler menampilkan pesan error dan program tidak jadi dibangun. Apabila anotasi tidak ditulis, compiler tetap menentukan tipe melalui mekanisme inferensi tipe (type inference), sehingga anotasi berperan sebagai dokumentasi yang diperiksa, bukan sekadar formalitas.

Perbandingan dengan bahasa imperatif#

Bahasa imperatif pada umumnya mengevaluasi ekspresi secara eager, yaitu segera saat ekspresi ditemui, dan membiarkan data diubah dari mana saja. Tabel berikut merangkum perbedaan karakter Haskell dengan ciri paling khas bahasa imperatif. Perbandingan ini menyederhanakan spektrum yang sebenarnya, sebab banyak bahasa modern menyediakan fitur dari kedua paradigma.

AspekHaskellBahasa imperatif umum
Model penulisankomposisi ekspresi dan fungsiurutan pernyataan yang mengubah keadaan
Dataimmutable, perubahan berarti nilai barumutable, variabel dapat ditimpa
Efek sampingdipisahkan ke tipe IOterjadi di mana saja dalam kode
Waktu evaluasilazy, dihitung saat dibutuhkaneager, dihitung saat ekspresi ditemui
Pemeriksaan tipestatis dan kuat saat kompilasibergantung bahasa, banyak yang dinamis

Perbedaan-perbedaan di atas saling menguatkan. Kemurnian fungsi hanya masuk akal apabila data immutable, dan list tak hingga hanya mungkin apabila evaluasi lazy. Karena itu, perpindahan dari bahasa imperatif ke Haskell bukan sekadar pergantian sintaks, melainkan pergantian cara menyusun program.

Ranah penggunaan#

Kombinasi fungsi murni, data immutable, dan tipe statis menjadikan Haskell layak dipertimbangkan pada perangkat lunak yang menuntut kebenaran tinggi. Compiler dan interpreter bahasa adalah contoh yang lazim, dan GHC sendiri ditulis dalam Haskell. Alat bantu pengembang seperti pengformat kode dan penganalisis statis juga sering dibangun dengan bahasa ini, sebab logika transformasi teks dan struktur program cocok dengan gaya komposisi fungsi. Pemfaktoran ulang berskala besar menjadi lebih terkendali karena compiler memeriksa ulang tipe seluruh kode yang terpengaruh.

Di sisi lain, Haskell bukan jawaban tunggal untuk semua masalah. Program yang interaksi eksternalnya dominan tetap dapat ditulis melalui tipe IO, tetapi disiplin pemisahan efek menuntut pembiasaan tersendiri. Pemilihan bahasa tetap bergantung pada kebutuhan proyek, tim, dan ekosistem yang tersedia, sedangkan pemahaman karakter Haskell memberi kerangka berpikir fungsional yang dapat dibawa ke bahasa mana pun.

Ringkasan#

  • Haskell adalah bahasa fungsional murni yang fungsi-fungsinya bebas efek samping dan selalu memberi keluaran sama untuk masukan yang sama.
  • Data bersifat immutable, sehingga perubahan dinyatakan sebagai fungsi yang menghasilkan nilai baru dan nilai lama tetap utuh.
  • Evaluasi lazy menunda perhitungan hingga hasilnya dibutuhkan, sehingga struktur seperti list tak hingga dapat ditulis dengan aman.
  • Tipe diperiksa saat kompilasi tanpa konversi diam-diam antar tipe, sehingga banyak error tipe tertangkap sebelum program berjalan.

Referensi#