Function composition adalah teknik menggabungkan beberapa function menjadi satu function baru yang menjalankan semuanya secara berurutan. Output function pertama menjadi input function berikutnya, dan seterusnya. Pada chapter ini kita akan memahami dua operator komposisi umum — compose (kanan-ke-kiri) dan andThen (kiri-ke-kanan) — serta melihat bagaimana pipeline transformasi dapat dibangun tanpa menulis nested call berlapis. Kita juga akan membahas dasar currying — teknik mengubah function banyak parameter menjadi rangkaian function satu parameter.

Apa Itu Function Composition #

Komposisi function f dan g menghasilkan function baru h sehingga h(x) = f(g(x)). Output g menjadi input f. Kotlin tidak memiliki operator bawaan untuk komposisi, tetapi kita dapat menulis sendiri dengan higher-order function.

kotlin Definisi compose dan andThen
// compose: f o g = (f compose g)(x) = f(g(x))
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C =
    { x -> f(g(x)) }

// andThen: g andThen f = (g andThen f)(x) = f(g(x))
infix fun <A, B, C> ((A) -> B).andThen(next: (B) -> C): (A) -> C =
    { x -> next(this(x)) }

compose membaca dari kanan ke kiri (fungsional matematis), sedangkan andThen membaca dari kiri ke kanan (lebih natural untuk urutan eksekusi).

Contoh Function Composition #

Mari kita lihat komposisi function dalam beberapa skenario — numerik dan string processing.

kotlin
// function_composition.kt
// Function composition: menggabungkan beberapa function menjadi pipeline.

// compose: (f o g)(x) = f(g(x))
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
    return { x -> f(g(x)) }
}

// infix andThen: g andThen f = compose(f, g)
infix fun <A, B, C> ((A) -> B).andThen(next: (B) -> C): (A) -> C {
    return { x -> next(this(x)) }
}

// Currying dasar: function dua parameter -> function bertingkat.
fun tambah(a: Int): (Int) -> Int = { b -> a + b }

fun main() {
    val kalikan2: (Int) -> Int = { it * 2 }
    val tambah3: (Int) -> Int = { it + 3 }

    // compose: kalikan2 setelah tambah3 → (x + 3) * 2
    val proses1 = compose(kalikan2, tambah3)
    println("compose (x+3)*2 untuk 5: ${proses1(5)}")

    // andThen: tambah3 andThen kalikan2 → (x + 3) * 2
    val proses2 = tambah3 andThen kalikan2
    println("andThen (x+3)*2 untuk 5: ${proses2(5)}")

    // Pipeline panjang: tambah3 andThen kalikan2 andThen tambah3
    val pipeline = tambah3 andThen kalikan2 andThen tambah3
    // (5 + 3) * 2 + 3 = 19
    println("Pipeline (x+3)*2+3 untuk 5: ${pipeline(5)}")

    // Currying.
    val tambah10 = tambah(10)
    println("tambah10(7) = ${tambah10(7)}")
    println("tambah(10)(7) = ${tambah(10)(7)}")

    // Komposisi string processing.
    val trim: (String) -> String = String::trim
    val upper: (String) -> String = String::uppercase
    val eksclam: (String) -> String = { it + "!" }

    val prosesString = trim andThen upper andThen eksclam
    val teksAsli = "  halo kotlin  "
    println("Hasil pipeline: ${prosesString(teksAsli)}")
}

Output yang Dihasilkan:

text
compose (x+3)*2 untuk 5: 16
andThen (x+3)*2 untuk 5: 16
Pipeline (x+3)*2+3 untuk 5: 19
tambah10(7) = 17
tambah(10)(7) = 17
Hasil pipeline: HALO KOTLIN!

Mari kita telaah baris-baris kode di atas:

  1. Baris 6-8 (fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C): function generic compose. Menerima dua function f dan g, mengembalikan function baru (A) -> C. Hasil pemanggilan compose(f, g)(x) sama dengan f(g(x)).
  2. Baris 11-13 (infix fun <A, B, C> ((A) -> B).andThen(...)): extension function infix pada function type. Operator andThen membaca dari kiri ke kanan: (g andThen f)(x) = f(g(x)). Sama semantik dengan compose(f, g), hanya urutan argumen yang berbeda.
  3. Baris 16 (fun tambah(a: Int): (Int) -> Int = { b -> a + b }): dasar currying. Function dua parameter tambah(a, b) dipecah menjadi rangkaian: pertama terima a dan kembalikan function, lalu function tersebut menerima b.
  4. Baris 23 (val proses1 = compose(kalikan2, tambah3)): komposisi (kalikan2 o tambah3). Untuk input 5: tambah3(5) = 8, lalu kalikan2(8) = 16.
  5. Baris 27 (val proses2 = tambah3 andThen kalikan2): komposisi yang sama, ditulis dari kiri ke kanan. tambah3 dijalankan dulu, lalu kalikan2. Hasil sama: 16.
  6. Baris 31 (val pipeline = tambah3 andThen kalikan2 andThen tambah3): pipeline tiga tahap. Untuk input 5: (5+3) * 2 + 3 = 19. Infix andThen membuat pipeline terbaca seperti kalimat.
  7. Baris 36-38 (val tambah10 = tambah(10); tambah10(7)): pemanggilan currying dua tahap. tambah(10) mengembalikan function (Int) -> Int. Function tersebut lalu dipanggil dengan 7, menghasilkan 17.
  8. Baris 39 (tambah(10)(7)): currying dipanggil langsung dalam satu ekspresi. tambah(10) mengembalikan function, lalu (7) langsung memanggil function tersebut.
  9. Baris 42-50 (val trim = String::trim; ...; val prosesString = trim andThen upper andThen eksclam): pipeline string processing. Menggabungkan callable reference (String::trim, String::uppercase) dengan lambda ({ it + "!" }). Pipeline membersihkan spasi, mengkapitalisasi, lalu menambah tanda seru.

compose vs andThen #

Aspekcompose(f, g)g andThen f
Urutan eksekusig dulu, lalu fg dulu, lalu f
Arah bacaKanan ke kiri (matematis)Kiri ke kanan (natural)
Hasil akhirSamaSama
Idiomatic KotlinJarangSering (infix lebih natural)

Currying — Pengantar #

Currying adalah teknik mengubah function banyak parameter menjadi rangkaian function satu parameter. Function f(a, b, c) menjadi f(a)(b)(c). Manfaat utama currying adalah partial application — kita dapat menyediakan sebagian argumen dan mendapatkan function yang menunggu sisa argumen.

kotlin Currying dasar
fun tambah(a: Int): (Int) -> Int = { b -> a + b }

val tambah10 = tambah(10)   // function yang menunggu argumen kedua
val hasil = tambah10(7)     // 17

Currying tidak idiomatik di Kotlin seperti di Haskell, tetapi berguna untuk membangun function family yang dapat dikonfigurasi sebagian.

Referensi Lanjutan #

Penutup #

Function composition memungkinkan kita membangun pipeline transformasi yang ringkas dan dapat diuji secara modular. Setiap function kecil dapat diuji terpisah, lalu digabung menjadi pipeline kompleks via andThen. Currying menambah dimensi lain — partial application yang membuat function family dapat dikonfigurasi. Selanjutnya, kita akan masuk ke topik praktis yang menggabungkan banyak konsep lambdas: scope functions let, run, with, apply, dan also.