Currying adalah teknik lanjutan dalam mengerjakan sebuah fungsi. Currying tidak hanya digunakan di JavaScript tetapi dalam bahasa lain juga.
Currying adalah transformasi fungsi yang mengubah fungsi yang dipanggil sebagai f(a, b, c)
menjadi f(a)(b)(c)
.
Currying tidak memanggil suatu fungsi melainkan hanya mengubahnya.
Mari kita lihat contoh terlebih dahulu untuk memahami apa yang akan kita bicarakan lalu kemudian mempraktikkannya.
Kita akan membuat fungsi pembantu curry(f)
yang melakukan currying untuk dua argumen f
. Dengan kata lain, curry(f)
untuk dua argumen f(a, b)
diubah menjadi fungsi yang dijalankan sebagai f(a)(b)
:
function curry(f) { // curry(f) melakukan currying
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// penggunan
function sum(a, b) {
return a + b;
}
let curriedSum = curry(sum);
alert( curriedSum(1)(2) ); // 3
Seperti yang Anda lihat, implementasinya cukup mudah: hanya membutuhkan dua pembungkus.
- Hasil dari
curry(func)
adalah pembungkusfunction(a)
. - Ketika dipanggil
curriedSum(1)
, argumen disimpan di lingkungan leksikal, dan pembungkus baru dikembalikanfunction(b)
. - Kemudian pembungkus ini dipanggil dengan
2
sebagai argumen, dan ini meneruskan panggilan ke fungsisum
yang asli.
Implementasi currying yang lebih lanjut, seperti _.curry dari library lodash, mengembalikan pembungkus yang memungkinkan fungsi dipanggil secara normal maupun parsial:
function sum(a, b) {
return a + b;
}
let curriedSum = _.curry(sum); // menggunakan _.curry dari library lodash
alert( curriedSum(1, 2) ); // 3, tetap bisa dijalankan secara normal
alert( curriedSum(1)(2) ); // 3, secara parsial
Currying? Untuk apa?
Untuk memahami manfaatnya kita membutuhkan contoh implementasi di dunia nyata.
Sebaga contoh, kita memiliki fungsi pencatatan log(date, importance, message)
yang memformat dan mengeluarkan informasi. Dalam proyek yang sebenarnya, fungsi seperti itu memiliki banyak fitur yang berguna seperti mengirim log melalui jaringan, disini kita akan menggunakan alert
:
function log(date, importance, message) {
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
Mari lakukan currying!
log = _.curry(log);
Setelah itu log
berjalan normal:
log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
…Tetapi juga bekerja dalam bentuk currying:
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
Sekarang kita dapat dengan mudah membuat fungsi untuk log saat ini:
// logNow akan menjadi bagian dari log dengan argumen pertama tetap
let logNow = log(new Date());
// gunakan
logNow("INFO", "pesan"); // [HH:mm] INFO pesan
Sekarang logNow
adalah log
dengan argumen pertama yang sudah ditentukan, dengan kata lain “fungsi yang diterapkan sebagian” atau singkatnya “parsial”.
Kita bisa melanjutkannya dan membuat fungsi untuk log debug saat ini:
let debugNow = logNow("DEBUG");
debugNow("pesan"); // [HH:mm] DEBUG pesan
Jadi:
- Kita tidak kehilangan apapun setelah melakukan currying:
log
tetap bisa dipanggil secara normal. - Kita dapat dengan mudah membuat fungsi parsial seperti “log untuk hari ini”.
Implementasi currying lanjutan
Jika Anda ingin mengetahui lebih detail, berikut implementasi currying “lanjutan” untuk fungsi multi-argumen yang dapat kita gunakan di atas.
Itu cukup pendek:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
Contoh penggunaan:
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6, tetap bisa dijalankan secara normal
alert( curriedSum(1)(2,3) ); // 6, currying argumen pertama
alert( curriedSum(1)(2)(3) ); // 6, currying secara penuh
Fungsi curry
yang baru mungkin terlihat rumit, tetapi sebenarnya cukup mudah dipahami.
Hasil dari pemanggilan curry(func)
adalah pembungkus curried
yang terlihat seperti ini:
// func adalah fungsi untuk di transformasi
function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
};
Ketika kita menjalankannya, ada dua cabang eksekusi if
:
- Jika lewat
args
count adalah sama atau lebih dari fungsi asli memiliki definisi (func.length
), maka cukup teruskan panggilan menggunakanfunc.apply
. - Jika tidak, dapatkan sebagian: kita belum memanggil
func
. Sebagai gantinya, pembungkus lain dikembalikan, yang akan menerapkan kembalicurried
yang memberikan argumen sebelumnya bersama dengan yang baru.
Kemudian, jika kita menyebutnya, sekali lagi, kita akan mendapatkan parsial baru (jika tidak cukup argumen) atau, akhirnya, hasilnya.
Currying membutuhkan fungsi untuk memiliki sejumlah argumen tetap.
Fungsi yang menggunakan sisa parameter, seperti f(...args)
, tidak bisa di currying dengan cara ini.
Menurut definisi, currying harus mengubah sum(a, b, c)
menjadi sum(a)(b)(c)
.
Namun sebagian besar implementasi currying di JavaScript bersifat lanjutan, seperti yang dijelaskan: implementasi tersebut juga membuat fungsi dapat dipanggil dalam bentuk multi-argumen.
Ringkasan
Currying adalah transformasi yang membuat f(a,b,c)
dapat dipanggil sebagai f(a)(b)(c)
. Implementasi JavaScript biasanya membuat fungsi dapat dipanggil secara normal dan mengembalikan dalam bentuk parsial jika jumlah argumen tidak cukup.
Currying memungkinkan kita untuk mendapatkan sebuah bagian. Seperti yang kita lihat di contoh logging, setelah currying tiga argumen dari fungsi universal log(date, importance, message)
akan memberikan kita fungsi parsial ketika dipanggil dengan satu argumen (seperti log(date)
) atau dua argumen (seperti log(date, importance)
).
komentar
<code>
, untuk beberapa baris – bungkus dengan tag<pre>
, untuk lebih dari 10 baris – gunakan sandbox (plnkr, jsbin, < a href='http://codepen.io'>codepen…)