Topik ini memperkenalkan fungsi orde tinggi (higher-order function), yaitu fungsi yang menerima fungsi lain sebagai argumen: nilai fun beserta dua bentuknya, trio lists:map, lists:filter, dan lists:foldl sebagai tulang punggung modul lists, serta closure yang menangkap variabel luar. Semua contoh dieksekusi nyata di shell OTP 29.
Nilai fun dan dua bentuknya#
Fungsi pada Erlang adalah nilai biasa yang dapat diikat ke variabel, dikirim sebagai argumen, dan dipanggil. Nilai itu dibuat dengan ekspresi fun:
Erlang/OTP 29 [erts-17.1.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit:ns]
Eshell V17.1.1 (press Ctrl+G to abort, type help(). for help)
1> Kuadrat = fun(X) -> X * X end.
#Fun<erl_eval.42.130099583>
2> Kuadrat(5).
25
3> F = fun lists:sum/1.
fun lists:sum/1
4> F([1, 2, 3]).
6Bentuk pertama adalah fungsi anonim (anonymous function) yang terikat ke Kuadrat, dan bentuk kedua adalah referensi fun Modul:Fungsi/Aritas yang merangkai fungsi yang sudah ada menjadi nilai. Perhatikan bahwa tubuh fun ditutup dengan end, bukan titik, dan variabel di dalamnya mengikuti aturan pengikatan sekali yang sama. Nilai fun inilah bahan baku semua fungsi orde tinggi.
map, filter, dan foldl#
Tiga fungsi modul lists mencakup sebagian besar kebutuhan pengolahan list. map/2 menerapkan satu fungsi pada setiap elemen, filter/2 menyisakan elemen yang lolos uji, dan foldl/3 meringkas seluruh list menjadi satu nilai dengan membawa akumulasi:
5> lists:map(fun(X) -> X * 2 end, [1, 2, 3]).
[2,4,6]
6> lists:filter(fun(N) -> N rem 2 =:= 0 end, lists:seq(1, 10)).
[2,4,6,8,10]
7> lists:foldl(fun(X, Acc) -> X + Acc end, 0, [1, 2, 3, 4]).
10
8> lists:foldl(fun(X, Acc) -> X * Acc end, 1, [1, 2, 3, 4]).
24Fungsi foldl/3 menerima fungsi dua argumen: elemen saat ini dan akumulasi. Ekspresi ke-7 di atas memulai dari 0 lalu menjumlahkan seluruh elemen, sedangkan ekspresi ke-8 memulai dari 1 untuk perkalian, dan inilah alasan nilai awalnya berbeda: 0 adalah unsur identitas penjumlahan, 1 adalah unsur identitas perkalian. Ketiganya membungkus pola rekursi dengan akumulator yang ditulis manual pada topik akumulator dan tail call, dan eksekusinya mengikuti pola yang sama pula.
Untuk pemrosesan dari belakang, foldr/3 tersedia dengan urutan yang berlawanan. Perbedaannya terlihat pada operasi yang tidak bersifat asosiatif. Pengurangan dengan foldl/3 menghitung dari kiri, sedangkan foldr/3 dari kanan, dan hasilnya berbeda pada data yang sama:
1> lists:foldl(fun(X, Acc) -> X - Acc end, 0, lists:seq(1, 4)).
2
2> lists:foldr(fun(X, Acc) -> X - Acc end, 0, lists:seq(1, 4)).
-2foldl/3 merangkai dari kiri sehingga bentuk bersarangnya adalah 4 - (3 - (2 - (1 - 0))), sedangkan foldr/3 merangkai dari kanan dengan bentuk 1 - (2 - (3 - (4 - 0))), dan kedua urutan itu menghasilkan 2 dan -2. Untuk penjumlahan dan perkalian hasilnya selalu sama karena kedua operasi itu asosiatif, sehingga pilihan arah biasanya ditentukan oleh bentuk pembangunan strukturnya, bukan oleh hasil angkanya.
Closure: fungsi yang membawa lingkungannya#
Fungsi anonim mengikat variabel yang terlihat pada saat ia dibuat, dan ikatan itu ikut bepergian bersama fungsinya. Sifat ini disebut closure:
9> Pengali = 3.
3
10> Kalikan = fun(X) -> X * Pengali end.
#Fun<erl_eval.42.130099583>
11> Kalikan(4).
12Fungsi Kalikan menangkap nilai Pengali pada saat didefinisikan, dan pemanggilannya kemudian tetap memakai nilai tangkapan itu. Satu aturan penting berlaku: variabel luar hanya bisa dibaca dari dalam fun, bukan diikat ulang, karena aturan pengikatan sekali berlaku sama di dalam maupun di luar. Closure membuat perilaku fungsi orde tinggi dapat disesuaikan tanpa argumen tambahan pada setiap pemanggilan.
Untuk efek samping: foreach#
Trio di atas semuanya mengembalikan list atau nilai ringkasan. Bila tujuan utamanya efek samping, misalnya mencetak, lists:foreach/2 menjalankan fungsi pada setiap elemen lalu mengembalikan atom ok. Pemaduan fungsi juga sah: hasil satu fungsi orde tinggi langsung menjadi masukan fungsi yang lain:
1> lists:foreach(fun(N) -> io:format("nilai ~p~n", [N]) end, [1, 2]).
nilai 1
nilai 2
ok
2> lists:filter(fun(N) -> N rem 2 =:= 0 end, lists:map(fun(X) -> X + 1 end, [1, 2, 3, 4])).
[2,4]Ekspresi kedua menambahkan satu pada setiap elemen lalu menyaring yang genap, dan hasilnya [2, 4] sesuai urutan evaluasinya dari dalam ke luar. Untuk alur yang panjang, pola penulisan berlapis semacam ini biasanya digantikan list comprehension agar urutan tahapannya terbaca alami.
Contoh menyeluruh: statistik nilai ujian#
Modul berikut menggabungkan pola-pola di atas untuk satu kebutuhan nyata, yaitu rata-rata nilai ujian:
-module(statistik).
-export([rerata/1]).
rerata([]) -> 0;
rerata(Nilai) -> lists:sum(Nilai) / length(Nilai).Modul lists sebenarnya sudah menyediakan lists:sum/1, sehingga fungsi ini cukup menggabungkannya dengan length/1. Hasilnya di shell:
1> c(statistik).
{ok,statistik}
2> Nilai = [70, 85, 90, 60, 75].
"FUZ<K"
3> statistik:rerata(Nilai).
76.0Cetakan "FUZ<K" pada baris kedua bukan keanehan data, melainkan kebiasaan shell: list yang seluruh elemennya adalah kode karakter yang dapat dicetak ditampilkan sebagai string, dan [70, 85, 90, 60, 75] kebetulan membaca F, U, Z, <, K. Nilainya tetap list bilangan, dan rerata/1 menghitungnya menjadi 76.0. Perilaku pencetakan ini dibahas ulang pada modul Record dan string.
Menulis fungsi orde tinggi sendiri#
Karena fungsi hanyalah nilai, fungsi orde tinggi dapat ditulis sendiri tanpa alat tambahan. Modul berikut mendefinisikan versi map sendiri dengan rekursi biasa:
-module(orde).
-export([mapku/2]).
mapku(_F, []) -> [];
mapku(F, [K | S]) -> [F(K) | mapku(F, S)].Satu-satunya perbedaan dari rekursi list pada topik rekursi sebagai iterasi adalah parameter F yang dipanggil seperti fungsi biasa. Klausa dasarnya mengembalikan list kosong tanpa memanggil F, sehingga list kosong aman:
6> c(orde).
{ok,orde}
7> orde:mapku(fun(X) -> X * 10 end, [1, 2]).
[10,20]Hasilnya identik dengan lists:map/2 untuk kasus ini, dan struktur penerapannya memperlihatkan bahwa map, filter, dan fold hanyalah rekursi list yang membawa fungsi sebagai argumen. Dalam proyek nyata, versi modul lists tetap pilihan utama karena sudah teruji dan punya optimasi tersendiri.
Ringkasan#
- Fungsi adalah nilai: dibuat dengan fun anonim atau dirujuk dengan fun Modul:Fungsi/Aritas, dan keduanya dipanggil seperti fungsi biasa.
- lists:map/2, lists:filter/2, dan lists:foldl/3 menutupi kebutuhan ubah, saring, dan ringkas, dengan foldl membawa akumulasi dan nilai awal.
- Nilai awal fold dipilih dari unsur identitas operasinya, misalnya 0 untuk penjumlahan dan 1 untuk perkalian.
- Fungsi anonim menangkap variabel luarnya sebagai closure, tetapi variabel tersebut hanya dapat dibaca, tidak diikat ulang.
- lists:foreach/2 menjalankan efek samping per elemen dan mengembalikan ok, sedangkan pemaduan fungsi orde tinggi membentuk alur dari dalam ke luar.
- Fungsi orde tinggi bisa ditulis sendiri dengan rekursi list biasa yang membawa fungsi sebagai argumen.
Referensi#
- lists — Ericsson — Dokumentasi resmi modul lists, termasuk map, filter, foldl, dan foldr.
- Erlang Reference Manual: Fun Expressions — Ericsson — Aturan ekspresi fun, closure, dan batasan pengikatan variabelnya.
- Learn You Some Erlang for Great Good! — Fred Hebert — Bab fungsi orde tinggi dengan contoh map, filter, dan fold.