Back

/ 8 min read

Membangun Blok Program dengan Fungsi di Go - Becoming Gopher

Selamat datang kembali di seri Becoming Gopher! Di episode sebelumnya, kita sudah menguasai cara mengelola data secara dinamis menggunakan slice dan map. Ransel petualangan kita sekarang penuh dengan ‘alat’ canggih untuk data.

Tapi, seiring program kita bertambah besar, kita akan sering menemukan diri kita menulis logika yang sama berulang-ulang di banyak tempat. Menyalin dan menempel kode (copy-paste) adalah resep jitu untuk bencana: tidak efisien, sulit dirawat, dan sangat rawan kesalahan.

Untuk mengatasi ini, kita perlu belajar cara membungkus logika kita ke dalam ‘kapsul’ atau ‘blok bangunan’ yang bisa digunakan kembali kapan pun kita butuhkan. Selamat datang di dunia function!

Postingan ini akan menjadi deep dive. Kita akan mulai dari dasar-dasar fungsi, lalu perlahan-lahan naik level ke konsep-konsep powerful seperti closure dan recursive. Anggap ini seperti belajar merakit berbagai macam mesin dari komponen-komponen dasar.

Siap untuk menjadi arsitek kodemu sendiri? Mari kita mulai membangun dengan function!

Dasar-Dasar Fungsi

Fungsi adalah blok kode bernama yang melakukan tugas tertentu. Keindahan fungsi adalah kita bisa menulisnya sekali dan memanggilnya berkali-kali.

Membuat dan Memanggil Fungsi

Bentuk paling sederhana dari sebuah fungsi adalah yang tidak menerima input dan tidak mengembalikan output. Kita sudah sering melihatnya: fungsi main.

// Mendefinisikan fungsi bernama sayHello
func sayHello() {
fmt.Println("Hello, Gopher!")
}
func main() {
// Memanggil fungsi sayHello tiga kali
sayHello()
sayHello()
sayHello()
}

Memberi ‘Input’ dengan Parameter

Agar lebih berguna, fungsi harus bisa menerima data untuk diolah. Data yang dikirim ke dalam fungsi ini disebut parameter.

// Fungsi ini menerima satu parameter bernama 'name' dengan tipe data string
func greet(name string) {
fmt.Println("Halo,", name)
}
func main() {
greet("Budi") // "Budi" adalah argumen yang dikirim ke parameter 'name'
greet("Siti")
}

Jika ada beberapa parameter dengan tipe data yang sama, kita bisa menyingkat penulisannya.

// Cara biasa
func sayHi(firstName string, lastName string) { /* ... */ }
// Cara singkat
func sayHi(firstName, lastName string) { /* ... */ }

Mendapat ‘Output’ dengan Return Value

Selain menerima input, fungsi juga bisa memberikan hasil kembali. Nilai yang dikembalikan ini disebut return value. Tipe data dari nilai yang akan dikembalikan harus didefinisikan.

func getGreeting(name string) string {
greeting := "Hello, " + name
return greeting
}
func main() {
pesanSelamat := getGreeting("Rudi")
fmt.Println(pesanSelamat) // Output: Hello, Rudi
}

Fungsi Tingkat Lanjut

Sekarang kita sudah paham dasarnya, mari kita lihat fitur-fitur canggih yang membuat fungsi di Go sangat fleksibel.

Mengembalikan Banyak Nilai

Ini adalah salah satu fitur khas Go. Sebuah fungsi bisa mengembalikan lebih dari satu nilai sekaligus. Ini sangat berguna, misalnya untuk mengembalikan hasil dan status error secara bersamaan.

func getFullName() (string, string) {
return "John", "Doe"
}
func main() {
firstName, lastName := getFullName()
fmt.Println(firstName, lastName) // John Doe
}
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("tidak bisa membagi dengan nol")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Hasil:", result) // Hasil: 5
}
}

Jika kita hanya butuh salah satu nilai, kita bisa menggunakan _ (underscore) untuk mengabaikan nilai yang tidak kita perlukan.

_, lastName := getFullName()
fmt.Println("Nama belakangnya adalah:", lastName)

Pintasan dengan Named Return Values

Go mengizinkan kita untuk memberi nama pada variabel return value langsung di deklarasi fungsi. Ini bisa membuat kode lebih mudah dibaca dan kita bisa menggunakan return kosong.

// 'name' dan 'age' adalah variabel yang sudah dideklarasikan
func getUserInfo() (name string, age int) {
name = "Budi"
age = 25
return // Otomatis mengembalikan nilai dari variabel 'name' dan 'age'
}

Parameter Tak Terbatas dengan Variadic Function

Bagaimana jika kita ingin membuat fungsi yang bisa menerima argumen sebanyak apa pun? Misalnya fungsi sum yang bisa menjumlahkan 2 angka, 5 angka, atau 100 angka. Inilah gunanya variadic parameter.

Gunakan ... sebelum tipe data. Parameter ini harus diletakkan di posisi paling akhir dan di dalam fungsi akan diperlakukan sebagai sebuah slice.

func sumAll(numbers ...int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}
func main() {
fmt.Println(sumAll(10, 20)) // 30
fmt.Println(sumAll(5, 10, 15, 20)) // 50
// Kita juga bisa mengirim slice, tapi harus dengan ...
mySlice := []int{1, 2, 3}
fmt.Println(sumAll(mySlice...)) // 6
}

Fungsi Sebagai Warga Kelas Satu

Di Go, fungsi bukanlah ‘warga kelas dua’. Ia diperlakukan setara dengan int, string, atau bool. Artinya, fungsi bisa disimpan di dalam variabel, dikirim sebagai parameter ke fungsi lain, bahkan dikembalikan sebagai hasil dari fungsi lain. Konsep ini disebut first-class citizen.

Function as Value & Anonymous Function

Karena fungsi adalah nilai, kita bisa menyimpannya di dalam variabel. Seringkali, fungsi yang disimpan ini adalah anonymous function (fungsi tanpa nama).

func main() {
// Membuat fungsi tanpa nama dan menyimpannya di variabel 'kurang'
kurang := func(a, b int) int {
return a - b
}
hasil := kurang(10, 3)
fmt.Println("Hasil:", hasil) // Hasil: 7
}

Function as Parameter (Callback)

Ini adalah pola yang sangat kuat. Kita bisa mengirim sebuah fungsi sebagai argumen ke fungsi lain. Fungsi yang dikirim ini sering disebut callback.

// 'filter' adalah fungsi yang akan dikirim sebagai parameter
func filterNama(names []string, filter func(string) bool) []string {
var result []string
for _, name := range names {
if filter(name) {
result = append(result, name)
}
}
return result
}
func main() {
dataNama := []string{"Budi", "Siti", "Joko", "Susan"}
// filter untuk nama yang mengandung huruf 'u'
namaDenganU := filterNama(dataNama, func(name string) bool {
return strings.Contains(name, "u")
})
fmt.Println(namaDenganU) // [Budi Susan]
}

Untuk membuat kode lebih rapi, kita bisa membuat alias untuk tipe fungsi menggunakan type.

type FilterFunc func(string) bool
func filterNama(names []string, filter FilterFunc) []string {
// ... implementasi sama ...
}

Pola Fungsi Lanjutan

Mari kita tutup dengan dua konsep yang mungkin sedikit membengkokkan pikiran, tetapi sangat powerful.

Recursive Function - Fungsi yang Memanggil Dirinya Sendiri

Fungsi rekursif adalah fungsi yang memanggil dirinya sendiri di dalam badannya. Ini sangat berguna untuk memecahkan masalah yang bisa dipecah menjadi sub-masalah yang lebih kecil dan identik, seperti menghitung faktorial.

5=5
times4
times3
times2
times1
// Versi rekursif untuk menghitung faktorial
func factorial(value int) int {
// Base Case: Kondisi berhenti agar tidak terjadi perulangan tak terbatas
if value == 1 {
return 1
} else {
// Recursive Step: Memanggil diri sendiri dengan masalah yang lebih kecil
return value * factorial(value-1)
}
}

Closure - Fungsi yang Punya Ingatan

Closure adalah sebuah fungsi yang “mengingat” variabel-variabel dari lingkungan tempat ia diciptakan, bahkan setelah lingkungan tersebut sudah tidak ada.

Lihat contoh ini. Fungsi increment adalah sebuah closure karena ia mengakses dan memodifikasi variabel counter yang ada di luar lingkupnya.

func main() {
counter := 0
increment := func() {
counter++
fmt.Println("Nilai counter sekarang:", counter)
}
increment() // Nilai counter sekarang: 1
increment() // Nilai counter sekarang: 2
// Meskipun 'increment' dipanggil berulang kali, ia tetap "ingat"
// nilai terakhir dari 'counter'.
}

Konsep ini menjadi lebih kuat ketika sebuah fungsi mengembalikan fungsi lain.

// newCounter adalah fungsi yang mengembalikan sebuah fungsi (closure)
func newCounter() func() int {
counter := 0
return func() int {
counter++
return counter
}
}
func main() {
// 'counterA' adalah sebuah closure dengan 'counter'-nya sendiri
counterA := newCounter()
fmt.Println("A:", counterA()) // A: 1
fmt.Println("A:", counterA()) // A: 2
// 'counterB' adalah closure lain dengan 'counter' yang terpisah
counterB := newCounter()
fmt.Println("B:", counterB()) // B: 1
}

Setiap kali newCounter() dipanggil, ia menciptakan sebuah lingkungan baru dengan variabel counter-nya sendiri. Fungsi closure yang dikembalikan akan selalu terikat pada lingkungan tersebut.

Petualangan Hari Ini Selesai!

Phew, itu tadi perjalanan yang panjang dan mendalam! Jika kamu berhasil sampai sini, selamat! Kamu baru saja menguasai salah satu pilar terpenting dalam pemrograman Go: fungsi.

Kita sudah menjelajahi semuanya, mulai dari:

  • Dasar-dasar membuat fungsi dengan parameter dan return value.
  • Fitur-fitur keren seperti multiple return dan variadic function.
  • Konsep canggih di mana fungsi diperlakukan sebagai nilai.
  • Pola-pola kuat seperti recursive dan closure.

Setelah kita bisa mengorganisir logika kita dengan rapi, langkah selanjutnya adalah mengorganisir data dan perilakunya dengan cara yang lebih canggih. Di petualangan berikutnya, kita akan belajar membuat tipe data kita sendiri menggunakan struct dan mendefinisikan perilakunya dengan method.