Fungsi (3)

.

Fungsi nirnama

Sebelumnya kita telah pelajari bahwa fungsi adalah sekumpulan perintah yang diberi nama dan mempunyai lingkupnya sendiri. Di bahasa-bahasa pemrograman modern, fungsi tak lagi harus punya nama serta bisa ditempelkan di bagian-bagian kode program secara langsung. Fungsi yang demikian itu disebut dengan fungsi tanpa nama (anonymous function) atau fungsi tempelan (closure). Beberapa bahasa pemrograman lain menyebutnya lambda. Dalam bahasa Indonesia, saya lebih suka menyebutnya dengan istilah fungsi nirnama (tanpa nama).

Walaupun demikian, fungsi nirnama berbeda dengan kelompok perintah (statement block) biasa. Sebagaimana sebuah fungsi, fungsi nirnama juga memiliki lingkup (dan juga konteks) tersendiri. Pada contoh sebelumnya, kita harus membuat fungsi terlebih dahulu secara terpisah untuk bisa menggunakannya sebagai tipe data atau nilai. Sekarang kita akan mencoba dengan memanfaatkan fungsi nirnama.

Dari contoh sebelumnya:

// buat sebuah fungsi
func
funcA() {
print("fungsi A")
}
let a = funcA  // buat konstanta yang bernilai fungsi funcA
a() // jalankan konstanta fungsi

Dengan memanfaatkan fungsi nirnama, contoh di atas bisa ditulis menjadi:

// buat konstanta yang bernilai fungsi nirnama
let
a = {
print("nirnama 1")
}
a()  // jalankan konstanta fungsi

Mudah bukan? Penulisan fungsi nirnama sama dengan fungsi biasa, namun tanpa ada nama. Bagian badan fungsi langsung ditempelkan ke penyimpan sebagai nilai. Bagaimana jika fungsi nirnama membutuhkan parameter masukan atau juga data keluaran?

Dari contoh sebelumnya:

// buat fungsi tanpa parameter dengan keluaran test
func funcA() -> String {
return "fungsi A"
}
var s = funcA()  // penyimpan s bertipe teks berisi keluaran funcA
print(s) // akan mencetak teks "fungsi A" keluaran funcA

Dengan fungsi nirnama, bisa ditulis menjadi:

// buat variabel dengan nilai fungsi nirnama
var s = { () -> String in
return "nirnama 1"
}()
print(s)  // akan mencetak "nirnama 1"

Di sini mulai nampak sedikit perbedaan. Jika pada fungsi biasa parameter fungsi dan tipe data keluaran dituliskan setelah nama fungsi dan sebelum tanda {} (penanda kelompok perintah), namun karena fungsi nirnama tak punya nama, maka parameter dan keluaran digeser ke dalam tanda {} lalu kata kunci in menjadi penanda awalan isi kelompok perintah dalam fungsi nirnama.

Perhatikan adanya tanda () (pasangan kurung) di akhir penulisan fungsi nirnama pada contoh di atas. Sebagaimana pada fungsi biasa, tanda () menunjukkan pemanggilan fungsi. Demikian pula pada fungsi nirnama. Karena ada perintah pemanggilan tersebut maka variabel s secara otomatis bertipe data teks yang diperoleh dari keluaran fungsi nirnama, bukan bertipe data fungsi nirnama. Hal ini persis sama dengan contoh sebelumnya, yaitu baris perintah var s = funcA() namun bagian funcA digantikan langsung oleh sebuah fungsi nirnama.

Sebaliknya, jika tanda () pada fungsi nirnama tersebut dihapus maka variabel s akan bertipe fungsi, bukan lagi teks. Dan perintah print(s) akan mencetak teks “(Function)” yang berarti variabel s berisi fungsi. Seperti pada contoh berikut ini.

Dari contoh sebelumnya:

// fungsi menjumlahkan dua bilangan
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a+b
}
// buat variabel bertipe fungsi dengan struktur:
// 2 parameter bertipe Int dan keluaran bertipe Int
var mathFunction: (Int, Int) -> Int
mathFunction = addTwoInts       // isi var dengan fungsi penjumlahan
print(mathFunction(3,7)) // mencetak 10 dari 3+7

Dengan fungsi nirnama, bisa ditulis menjadi:

// buat variabel dengan nilai fungsi nirnama
var mathFunction = { (_ a: Int, _ b: Int) -> Int in
return a+b
}
print(mathFunction(3,7))  // mencetak 10 dari parameter 3 dan 7
print(mathFunction) // mencetak "(Function)" karena nirnama

Perhatikan bahwa tidak ada tanda () di akhir fungsi nirnama pada variabel mathFunction yang artinya fungsi nirnama tidak dijalankan sehingga variabel bertipe fungsi, bukan Int (dari keluaran fungsi nirnama). Sebaliknya, jika di akhir fungsi nirnama pada variabel mathFunction ditambahi (3,7) maka fungsi nirnama tersebut langsung dijalankan dengan parameter 3 dan 7 dan menghasilkan tipe data Int bernilai 10. Sehingga variabel mathFunction otomatis menjadi bertipe data Int. Contoh:

// buat variabel dengan nilai fungsi nirnama
var mathFunction = { (_ a: Int, _ b: Int) -> Int in
return a+b
}(3,7) // ⬅︎ perhatikan perintah pemanggilan fungsi ini
print(mathFunction)      // mencetak 10 dari 3 dan 7      ⬅︎ sah!
print(mathFunction(3,7)) // var mathFunction bukan fungsi ⬅︎ salah!

Jadi, ingat bahwa fungsi nirnama yang menyertakan tanda () — beserta parameternya, jika ada — akan langsung menjalankan fungsi tersebut.

Walaupun secara umum fungsi nirnama berperilaku seperti fungsi biasa kecuali tanpa nama, ada sedikit perbedaan antara fungsi nirnama dan fungsi biasa, yaitu fungsi nirnama tak bisa menerima parameter dengan nilai bawaan (default). Dengan demikian, contoh program berikut ini akan memunculkan kesalahan.

// buat variabel dengan nilai fungsi nirnama
// dan parameter berisi nilai bawaan (
default)
var mathFunction = { (_ a: Int = 1, _ b: Int) -> Int in // ⬅︎ salah!
return a+b
}

Fungsi nirnama sebagai parameter fungsi

Manfaat paling besar dari fungsi nirnama adalah penggunaannya sebagai parameter suatu fungsi. Dari manfaat inilah fungsi nirnama disebut juga dengan fungsi tempelan atau fungsi pelengkap alias closure.

Fungsi sebagai parameter telah dijelaskan pada bahasan sebelumnya. Fungsi nirnama sebagai parameter fungsi secara prinsip sama saja dengan yang telah dijelaskan tersebut. Bedanya, jika fungsi biasa sebagai parameter maka fungsi parameter harus dibuat terpisah dari fungsi pemakai. Dengan fungsi nirnama, parameter fungsi bisa langsung ditempelkan saat fungsi utama dipanggil. Contoh:

// fungsi perkalian tiga
func times3(_ v: Int) -> Int {
return v*3
}
// fungsi perkalian dengan parameter bertipe fungsi
func multiply(_ value: Int, with factor: (Int) -> Int) -> Int {
return factor(value)
}
// pemanggilan fungsi multiply dengan parameter fungsi biasa
let a = multiply(2, with: times3)
print(a) // cetak 6 dari 2*3
// pemanggilan fungsi multiply dengan fungsi nirnama secara lengkap
let b = multiply(3, with: { (v: Int) -> Int in
return v*3
})
print(b) // cetak 9 dari 3*3
// pemanggilan fungsi multiply dengan fungsi nirnama secara ringkas
let c = multiply(3, with: { v in v*2 })
print(c) // cetak 6 dari 3*2
// pemanggilan fungsi multiply dengan fungsi nirnama secara tempel
let d = multiply(2) { $0 * 4 }
print(d) // cetak 8 dari 2*4

Pada contoh program di atas, ada 2 (dua) fungsi yaitu fungsi times3(_:Int) dengan keluaran bertipe Int dan fungsi multiply(_:Int, with:(Int)->Int) dengan keluaran bertipe Int juga. Perhatikan bahwa fungsi multiply memiliki parameter with dengan ciri (Int)->Int di mana ciri tersebut sesuai dengan ciri fungsi times3 yaitu satu parameter bertipe Int dan keluaran bertipe Int.

// pemanggilan fungsi multiply dengan parameter fungsi biasa
let a = multiply(2, with: times3)
print(a) // cetak 6 dari 2*3

Perhatikan pemanggilan fungsi multiply untuk mengisi penyimpan a. Fungsi multiply dipanggil dengan parameter with diisi dengan fungsi times3. Hal ini dimungkinkan karena fungsi times3 memiliki ciri yang sama dengan parameter with. Dengan demikian, penyimpan a berisi nilai 6 (enam) yang diperoleh dari parameter value yang berisi 2 (dua) yang dimasukkan sebagai nilai untuk parameter v pada fungsi times3 sehingga diperoleh nilai 2×3 sama dengan 6. Cara pemanggilan ini sama dengan yang telah dilakukan pada bahasan sebelumnya, yaitu dengan menggunakan fungsi yang telah ada.

Kode program dengan cara pemanggilan demikian cenderung lebih mudah dibaca dan dipahami karena masing-masing bagian telah terdefinisikan secara jelas. Namun, cara ini menjadi merepotkan ketika kita ingin melakukan perkalian dengan banyak faktor, tak cuma perkalian 3 (tiga) seperti yang disediakan oleh fungsi times3. Dengan cara ini, kita harus membuat fungsi untuk setiap faktor yang kita butuhkan. Nah, di sinilah manfaat fungsi nirnama sangat terasa.

// pemanggilan fungsi multiply dengan fungsi nirnama secara lengkap
let b = multiply(3, with: { (v: Int) -> Int in
return v*3
})
print(b) // cetak 9 dari 3*3

Dengan fungsi nirnama, fungsi bisa kita tempelkan langsung ke parameter with pada fungsi multiply. Perhatikan pemanggilan fungsi multiply untuk mengisi penyimpan b pada contoh program di atas. Alih-alih memasukkan fungsi times3, kita bisa langsung tuliskan fungsi nirnama pada parameter with. Dengan penempelan fungsi nirnama ini kita bisa melakukan operasi perkalian dengan faktor berapa pun tanpa harus membuat fungsi terpisah.

Penulisan fungsi nirnama di atas adalah penulisan secara lengkap dan formal, di mana seluruh bagiannya dinyatakan dengan jelas. Ada parameter dengan tipe datanya, ada keluaran dengan tipe datanya, ada kata kunci in sebagai pembuka, ada kata kunci return, dan seterusnya. Fungsi nirnama sebagai parameter fungsi bisa ditulis secara lebih ringkas agar penulisan kode program lebih efisien.

// pemanggilan fungsi multiply dengan fungsi nirnama secara ringkas
let c = multiply(3, with: { v in v*2 })
print(c) // cetak 6 dari 3*2

Peringkasan dimungkinkan dengan adanya kemampuan pengenalan otomatis (type inference) dalam bahasa Swift. Perhatikan pemanggilan fungsi multiply untuk mengisi penyimpan c pada contoh program di atas. Karena parameter with berciri (Int)->Int maka Swift bisa mengenali penulisan v in v*2 sebagai parameter v bertipe Int tanpa perlu penjelasan eksplisit dan v*2 sebagai keluaran tanpa perlu kata kunci return sebab fungsi hanya berisi satu perintah saja. Dengan demikian penulisan fungsi nirnama pun jadi lebih singkat.

// pemanggilan fungsi multiply dengan fungsi nirnama secara tempel
let d = multiply(2) { $0 * 4 }
print(d) // cetak 8 dari 2*4

Jika dirasa masih kurang ringkas, Swift menyediakan cara penulisan yang lebih singkat lagi yang disebut dengan cara ‘penempelan akhir’ atau trailing closure. Perhatikan pemanggilan fungsi multiply untuk mengisi penyimpan d pada contoh program di atas. Syarat agar bisa menggunakan penulisan penempelan akhir ini adalah parameter yang bertipe fungsi harus diletakkan sebagai parameter terakhir.

Dengan penempelan akhir, fungsi nirnama langsung dituliskan di akhir fungsi dan nama parameter untuk fungsi nirnama tak perlu dituliskan. Keistimewaan lain dari cara penempelan akhir adalah parameter fungsi nirnama bisa diakses dengan $ (tanda dolar) diikuti nomor urut parameter. Jadi, simbol $0 pada contoh program di atas menunjuk pada paremeter pertama yang bernilai 2 (dua).

Menjalankan contoh program di atas dengan Xcode Playground. Sumber: sendiri.

Berbagai cara penulisan parameter fungsi nirnama di atas tidak bersifat ekslusif satu sama lain. Artinya penulisan lengkap bisa saja tanpa kata kunci return, atau penulisan ringkas ada kata kunci return, atau menggunakan simbol $ dalam penulisan ringkas, dan sebagainya karena Swift akan secara otomatis mengenali apa di mana sebagai apa. Dan jika ada bagian yang tak bisa dikenali maka Swift akan menampilkan pesan kesalahan sebagai petunjuk untuk kita memperbaiki kode program.

Catatan: Walaupun penulisan ringkas cukup membantu namun gunakanlah seperlunya saja. Jika penulisan ringkas dirasa membingungkan maka tulislah kode program secara lengkap. Kode program yang panjang tapi mudah dipahami adalah lebih baik daripada kode program yang ringkas tapi sulit dipahami.

Fungsi-fungsi yang disediakan oleh Swift dalam pustaka bakunya (Swift Standard Library dan Foundation) banyak yang menggunakan fungsi nirnama sebagai parameter. Pemahaman terhadap cara membuat dan menggunakan fungsi nirnama dalam bahasa Swift cukup penting.

bersambung ke bagian 4

Show your support

Clapping shows how much you appreciated Mʀ Bee’s story.