Sebagian besar fungsi yang diteruskan kepada map, filter, dan foldr hanya digunakan satu kali dan tubuhnya berupa satu ekspresi. Haskell menyediakan dua bentuk ringkas untuk kebutuhan tersebut, yaitu lambda sebagai fungsi anonim yang ditulis langsung pada tempat pemanggilannya dan section sebagai aplikasi parsial operator. Topik ini membahas sintaks keduanya, aturan pembacaannya, serta pertimbangan memilih di antara fungsi bernama, lambda, dan section.
Fungsi anonim dengan lambda#
Lambda adalah fungsi tanpa nama yang ditulis dengan garis miring terbalik, diikuti daftar parameter, panah, lalu satu ekspresi sebagai tubuhnya. Bentuk \n -> n * n + 1 adalah fungsi yang menerima satu bilangan dan menghasilkan kuadratnya ditambah satu. Karena fungsi berstatus nilai kelas satu, lambda dapat diteruskan kepada fungsi orde tinggi tanpa definisi terpisah, sebagaimana program berikut.
-- Lambda: fungsi anonim yang ditulis langsung pada tempatnya
main :: IO ()
main = do
print (map (\n -> n * n + 1) [1, 2, 3])
print (filter (\n -> n > 2 && n < 9) [1, 2, 3, 4, 5])
print (map (\(a, b) -> a + b) [(1, 2), (3, 4)])
print (foldr (\x acc -> x * acc) 1 [1, 2, 3, 4])[2,5,10]
[3,4,5]
[3,7]
24Baris ketiga memperlihatkan pola tuple pada parameter lambda, yaitu \(a, b) -> a + b yang menerima satu pasangan dan menjumlahkan komponennya. Pola seperti itu sah sebagaimana pola pada persamaan fungsi biasa. Baris keempat menggunakan lambda dua parameter untuk foldr, dengan x menerima elemen dan acc menerima akumulator. Bentuk \x y -> ... merupakan singkatan dari \x -> \y -> ..., sehingga lambda beberapa parameter hanyalah pelapisan lambda satu parameter.
Nama parameter pada lambda bebas dipilih, tetapi nama yang bermakna seperti acc pada contoh di atas membantu pembaca memahami peran setiap parameter. Tubuh lambda hanya berupa satu ekspresi, sehingga logika yang membutuhkan persamaan berganda tetap sebaiknya ditulis sebagai fungsi bernama. Batasan ini menjaga lambda tetap pendek dan memaksa logika yang besar keluar dari baris pemanggilannya.
Section operator#
Section adalah aplikasi parsial operator dengan salah satu sisinya dibiarkan kosong dan ditulis di dalam tanda kurung. Bentuk (* 3) berarti kalikan dengan tiga, sedangkan (2 ^) berarti dua dipangkatkan dengan elemen. Keduanya menghasilkan fungsi satu argumen yang siap diteruskan ke fungsi orde tinggi. Posisi sisi yang disisakan menentukan artinya, terutama pada operator yang tidak komutatif.
-- Section: aplikasi parsial operator dengan satu sisi yang disisakan
main :: IO ()
main = do
print (map (* 3) [1, 2, 3])
print (map (10 -) [1, 2, 3])
print (map (^ 2) [1, 2, 3])
print (map (2 ^) [3, 4])[3,6,9]
[9,8,7]
[1,4,9]
[8,16]Baris kedua dan pasangan baris ketiga serta keempat keluaran menunjukkan perbedaan arah pada operator yang tidak komutatif. Section (10 -) berarti 10 dikurangi elemen, sehingga 10 - 1 menghasilkan 9, sedangkan (^ 2) dan (2 ^) memberikan hasil berbeda karena pangkat tidak komutatif. Section juga merupakan ekspresi biasa yang dapat diberi nama, misalnya tambahSepuluh = (+ 10), karena hasilnya bertipe fungsi dan tidak berbeda dengan fungsi yang didefinisikan lewat persamaan.
Section juga menyatu rapi dengan operator komposisi (.), karena hasilnya bertipe fungsi sehingga dapat dirantai tanpa tanda kurung tambahan. Bentuk sum . map (* 2) menyusun dua tahap, yaitu penggandaan setiap elemen lalu penjumlahannya, dan tanda kurung yang membungkus (* 2) berpindah dari posisi argumen map menjadi bagian dari rantainya. Keterbacaan tetap terjaga selama setiap tahapnya pendek dan dapat disebut dengan nama yang jelas.
Memilih bentuk yang tepat#
Tiga gaya berikut menghasilkan nilai yang sama, yaitu menggandakan setiap elemen list. Fungsi bernama unggul apabila logikanya digunakan di beberapa tempat, lambda unggul apabila logikanya khusus untuk satu pemanggilan dan parameternya perlu diberi nama, sedangkan section unggul apabila logikanya cukup dinyatakan dengan satu operator dan satu nilai tetap.
-- Tiga gaya menyatakan fungsi pendek: bernama, lambda, dan section
duaKali :: Int -> Int
duaKali n = n * 2
main :: IO ()
main = do
print (map duaKali [1, 2, 3])
print (map (\n -> n * 2) [1, 2, 3])
print (map (* 2) [1, 2, 3])[2,4,6]
[2,4,6]
[2,4,6]Keluaran ketiga baris sama persis, sehingga pilihannya murni persoalan keterbacaan dan penggunaan ulang. Tabel berikut merangkum panduannya.
| Kebutuhan | Bentuk yang lazim |
|---|---|
| Digunakan di beberapa tempat atau perlu anotasi tipe | fungsi bernama |
| Satu pemanggilan dengan dua parameter atau pola tuple | lambda |
| Satu operator dengan satu nilai tetap | section |
| Logika yang tidak muat dalam satu ekspresi | fungsi bernama dengan where |
Lambda yang tubuhnya melebihi satu langkah biasanya lebih baik dipindahkan menjadi fungsi bernama, karena nama memberi tempat bagi anotasi tipe dan penjelasan. Sebaliknya, mendefinisikan fungsi bernama untuk logika satu ekspresi yang hanya digunakan sekali menambah jeda pembacaan tanpa manfaat. Keseimbangan keduanya menjaga berkas tetap pendek tanpa mengorbankan kejelasan.
Kesalahan umum#
Kesalahan yang sering terjadi adalah menulis (- 3) dengan maksud mengurangi setiap elemen dengan tiga. Bentuk tersebut dibaca sebagai bilangan negatif tiga, bukan section, sehingga compiler menolaknya dengan pesan tipe yang sulit dihubungkan dengan maksud awalnya. Cara yang benar menyatakan pengurangan dengan fungsi subtract atau dengan section kiri.
# setiap elemen dikurangi tiga dengan subtract
ghc -e 'map (subtract 3) [10, 20]'
# section kiri: tiga dikurangi setiap elemen
ghc -e 'map (3 -) [10, 20]'[7,17]
[-7,-17]Fungsi subtract 3 menerapkan pengurangan dengan argumen terbalik, yaitu elemen dikurangi tiga, sedangkan section kiri (3 -) berarti tiga dikurangi elemen. Keduanya perlu dibedakan karena hasilnya berbeda tanda. Kesalahan kedua adalah melupakan tanda kurung ketika lambda diteruskan sebagai argumen, misalnya map \n -> n * 2 [1, 2, 3]. Tubuh lambda menjangkau sejauh mungkin ke kanan, sehingga seluruh n * 2 [1, 2, 3] menjadi tubuh lambda dan map hanya menerima satu argumen. Penulisan yang benar adalah map (\n -> n * 2) [1, 2, 3] dengan lambda di dalam kurung.
Ringkasan#
- Lambda adalah fungsi anonim yang terdiri atas parameter dan satu ekspresi tubuh, ditulis
\x -> ..., dan dapat memuat pola seperti tuple. - Section adalah aplikasi parsial operator dengan satu sisi kosong, dan posisi sisi kosongnya menentukan arah operasinya.
- Bentuk
(- 3)dibaca sebagai bilangan negatif, sehingga pengurangan dinyatakan dengansubtractatau section kiri. - Fungsi bernama dipilih apabila logika digunakan ulang atau membutuhkan anotasi tipe, sedangkan lambda dan section dipilih apabila logikanya pendek dan hanya digunakan sekali.
Referensi#
- Haskell 2010 Language Report, Bab 3 — haskell.org — Spesifikasi lambda abstraction dan section pada gramatika resmi bahasa.
- Lambda abstraction — Haskell Wiki — Penjelasan bentuk lambda dan penggunaannya sebagai argumen.
- Prelude — Hackage
— Dokumentasi
subtract,map, danfilteryang menjadi contoh penerima lambda.