Lambda dan section

Fungsi anonim dan section operator untuk fungsi pendek satu ekspresi.

Daftar isi

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.

haskell lambda-dasar.hs
-- 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])
text Keluaran
[2,5,10]
[3,4,5]
[3,7]
24

Baris 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.

haskell section-dasar.hs
-- 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])
text Keluaran
[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.

haskell tiga-gaya.hs
-- 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])
text Keluaran
[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.

KebutuhanBentuk yang lazim
Digunakan di beberapa tempat atau perlu anotasi tipefungsi bernama
Satu pemanggilan dengan dua parameter atau pola tuplelambda
Satu operator dengan satu nilai tetapsection
Logika yang tidak muat dalam satu ekspresifungsi 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.

bash
# 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]'
text Keluaran
[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 dengan subtract atau 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#