Fungsi adalah komponen utama penyusunan program Haskell, dan cara mendefinisikannya menentukan keterbacaan seluruh kode. Topik ini membahas penulisan fungsi dengan persamaan berganda, mekanisme aplikasi fungsi, serta dua operator aplikasi tambahan, yaitu $ dan & yang kerap disebut operator pipe. Setelah membacanya, fungsi dengan banyak kasus dapat ditulis dengan rapi, dan rantai pemanggilan fungsi dapat disusun tanpa tanda kurung yang bertumpuk.
Persamaan berganda#
Setiap fungsi boleh ditulis sebagai beberapa persamaan (equation), yaitu satu baris definisi untuk setiap kasus masukan. Setiap persamaan menyandingkan nama fungsi dengan sebuah pola, lalu compiler memilih persamaan pertama yang polanya cocok dengan argumen yang diberikan. Persamaan terakhir biasanya berupa nama variabel biasa sebagai pola, sehingga menangkap seluruh kasus yang tidak cocok dengan persamaan sebelumnya.
Pola pada persamaan boleh berupa nilai literal, nama variabel, atau struktur yang lebih kompleks. Persamaan namaBulan 1 menyandingkan nilai literal 1, sehingga hanya argumen bernilai 1 yang cocok, sedangkan persamaan namaBulan n menerima nilai apa pun. Selama satu fungsi ditulis sebagai beberapa persamaan, compiler memperlakukan semuanya sebagai satu fungsi dengan satu tipe, sehingga anotasi tipe cukup ditulis satu kali.
-- Satu fungsi, beberapa persamaan: persamaan pertama yang cocok dipakai
namaBulan :: Int -> String
namaBulan 1 = "Januari"
namaBulan 2 = "Februari"
namaBulan 3 = "Maret"
namaBulan n = "Bulan " ++ show n
main :: IO ()
main = do
putStrLn (namaBulan 1)
putStrLn (namaBulan 3)
putStrLn (namaBulan 7)Januari
Maret
Bulan 7Pemilihan persamaan berlangsung dari atas ke bawah, sehingga urutan penulisannya berarti. Apabila persamaan dengan variabel n ditaruh paling atas, seluruh angka akan ditangkap persamaan itu dan ketiga kasus bernomor tidak pernah tercapai. Mekanisme di balik persamaan berganda adalah pencocokan pola (pattern matching) atas bentuk argumen, yang dibahas lebih rinci pada topik pattern matching, sedangkan bagian ini hanya memerlukan persamaan dibaca sebagai daftar kasus.
Persamaan berganda paling bermanfaat ketika setiap kasus berujung pada penghitungan yang berbeda. Apabila seluruh kasus menggunakan rumus yang sama, satu persamaan dengan variabel sudah cukup, dan penambahan persamaan justru menambah tempat yang harus diperiksa. Jumlah persamaan juga bebas, sehingga fungsi dengan sepuluh kasus tetap terbaca sebagai daftar baris tanpa percabangan bersarang.
Persamaan yang saling tumpang tindih tidak ditolak compiler, karena aturan pertama yang cocok langsung menang. Konsekuensinya, persamaan paling spesifik ditaruh paling atas dan persamaan penangkap paling umum ditaruh paling bawah. Kesalahan urutan membuat fungsi tetap terkompilasi tetapi memberi hasil yang keliru, dan kesalahan semacam ini jarang tertangkap compiler.
Aplikasi fungsi#
Aplikasi fungsi (function application) dalam Haskell ditulis tanpa tanda kurung, yaitu nama fungsi diikuti argumennya, seperti gandakan 3. Fungsi dengan beberapa argumen diterapkan satu per satu dari kiri ke kanan, sehingga penyandingan pada setiap langkah tetap mengikuti aturan yang sama. Karena merupakan operasi paling kencang, aplikasi mengikat lebih kuat daripada operator aritmetika, sehingga gandakan 3 + 4 dibaca (gandakan 3) + 4 dan bukan gandakan (3 + 4). Tanda kurung tetap diperlukan ketika argumen berupa ekspresi yang lebih rumit, misalnya gandakan (3 + 4).
-- Aplikasi fungsi tanpa kurung: nama fungsi lalu argumen
gandakan :: Int -> Int
gandakan n = n * 2
main :: IO ()
main = do
print (gandakan 3 + 4) -- dibaca (gandakan 3) + 4
print (gandakan (3 + 4)) -- argumen dihitung lebih dahulu
print (gandakan $ 3 + 4) -- $ menyerahkan sisa ekspresi sebagai argumen
print $ gandakan $ 3 + 4 -- beberapa $ sekaligus10
14
14
14Empat baris keluaran menegaskan perbedaan prioritas tersebut. Baris pertama memperlihatkan aplikasi yang mengikat lebih kuat daripada penjumlahan, sedangkan tiga baris berikutnya menghitung gandakan (3 + 4) dengan dua gaya penulisan yang berbeda, yaitu dengan tanda kurung dan dengan operator $. Hasil ketiganya sama karena $ hanyalah aplikasi dengan prioritas berbeda.
Operator $#
Tanda $ adalah aplikasi dengan prioritas paling rendah. Definisinya sederhana, yaitu f $ x sama dengan f x, tetapi $ menyerahkan seluruh ekspresi di sebelah kanannya sebagai argumen tanpa perlu tanda kurung. Operator ini bersifat asosiatif kanan (right-associative), sehingga beberapa $ dapat dirantai dan setiap ekspresi di kanannya tetap diserahkan utuh.
# ekspresi di kanan $ diserahkan utuh sebagai argumen
ghc -e 'take 3 $ [10, 20 ..]'
# tanpa $, maksud yang sama ditulis dengan tanda kurung
ghc -e 'take 3 [10, 20 ..]'[10,20,30]
[10,20,30]Kedua bentuk menghasilkan nilai sama, tetapi $ menghilangkan sepasang tanda kurung yang dalam rantai panjang akan bertumpuk dan menyulitkan pembacaan. Pembacaannya mengikuti arah penulisan, yaitu fungsi di kiri diterapkan pada seluruh ekspresi di kanannya, sehingga print $ gandakan $ 3 + 4 sama dengan print (gandakan (3 + 4)). Konvensi yang lazim dijumpai pada kode Haskell adalah menempatkan $ sebelum argumen terakhir yang berupa ekspresi panjang, sedangkan kurung tetap dipertahankan untuk argumen pendek.
Tanda $ bukan pengganti seluruh tanda kurung. Apabila bagian di dalam kurung perlu dihitung lebih dahulu sebelum masuk ke fungsi terluar, kurung tetap diperlukan, dan memaksakan $ justru membuat maksud baris tidak jelas. Pemilihan di antara keduanya didasarkan pada kejelasan baris itu sendiri, bukan banyaknya karakter yang dihemat.
Operator pipe &#
Operator & mengubah arah pembacaan aplikasi. Operator ini terdapat pada modul Data.Function dan menerapkan fungsi pada nilai yang berada di sebelah kirinya, sehingga alur data dapat dibaca dari kiri ke kanan sebagaimana aliran pipa (pipe). Impornya ditulis eksplisit karena & tidak termasuk bagian Prelude.
-- & meneruskan nilai ke fungsi sehingga alur data dibaca dari kiri ke kanan
import Data.Function ((&))
gandakan :: Int -> Int
gandakan n = n * 2
tambahLima :: Int -> Int
tambahLima n = n + 5
main :: IO ()
main = do
print (3 & gandakan & tambahLima) -- dikalikan dua, lalu ditambah lima
print (tambahLima (gandakan 3)) -- bentuk bersarang, dibaca dari dalam ke luar11
11Bentuk bersarang pada baris terakhir dibaca dari dalam ke luar, sedangkan rantai & dibaca berurutan dari kiri ke kanan. Untuk rangkaian transformasi yang panjang, bentuk pipa biasanya lebih mudah diikuti karena urutan pengerjaannya searah dengan urutan tulisannya. Keduanya sah dan menghasilkan nilai sama, sehingga pilihannya kembali ke keterbacaan.
Ringkasan#
- Satu fungsi dapat didefinisikan dengan beberapa persamaan, dan persamaan pertama yang polanya cocok dengan argumen itulah yang dipilih.
- Aplikasi fungsi ditulis tanpa kurung dan mengikat lebih kuat daripada semua operator, sehingga
gandakan 3 + 4berarti(gandakan 3) + 4. $adalah aplikasi berprioritas terendah yang menyerahkan seluruh ekspresi di kanannya sebagai argumen, sehingga mengurangi tanda kurung bertumpuk.&dariData.Functionmenerapkan fungsi ke nilai kirinya, sehingga rantai transformasi dibaca dari kiri ke kanan.
Referensi#
- Data.Function — Hackage
— Dokumentasi resmi modul yang mendefinisikan
$dan&beserta tipenya. - Haskell 2010 Language Report, Bab 3 — haskell.org — Spesifikasi aplikasi fungsi dan prioritas operator.
- base package — Hackage
— Library standar yang mengekspor
$melaluiPrelude.