Fungsi Variadik

Parameter ...T dan cara meneruskan argumen ke sana.

Daftar isi

Fungsi variadik (variadic function) menerima jumlah argumen yang tidak tetap pada salah satu parameternya. Topik ini membahas cara menuliskan parameter ...T, perilakunya di dalam badan fungsi, dan cara meneruskan argumen ke sana, baik satu per satu maupun lewat slice, yaitu urutan data bertipe sama yang panjangnya dapat berubah. Setelah membaca topik ini, mekanisme di balik fungsi seperti fmt.Println yang menerima argumen secara bebas dapat dipahami.

Parameter …T dan bentuk slice-nya#

Parameter variadik ditulis dengan tiga titik sebelum tipenya, misalnya pesan ...string. Di dalam badan fungsi, parameter tersebut adalah slice []string biasa, sehingga len, range, dan pengindeksan bekerja sebagaimana slice pada umumnya. Pemanggil boleh mengirim sejumlah argumen apa pun, termasuk nol argumen, dan pada kasus terakhir nilai parameternya adalah slice nil yang panjangnya nol.

go Aturan posisi parameter variadik
// tidak-dijalankan
func f(a int, b ...string)  // sah, parameter ...T selalu di posisi terakhir
func g(a ...int, b string)  // tidak sah dan tidak akan lolos kompilasi

Kompiler hanya mengizinkan satu parameter variadik per fungsi, dan parameter itu wajib berada pada posisi terakhir. Aturan ini membuat pemisahan argumen tetap tak ambigu, yaitu argumen sebelumnya mengisi parameter biasa dan sisanya mengumpul menjadi slice variadik. Seluruh fungsi cetak pada paket fmt, seperti Println dan Printf, dibangun di atas mekanisme yang sama. Perlu dicatat pula bahwa tiga titik pada definisi ...T dan tiga titik pada pemanggilan nilai... adalah dua hal yang berbeda, yaitu deklarasi parameter dan pembongkaran argumen.

Nilai awal parameter variadik bergantung pada bentuk pemanggilannya. Apabila pemanggil tidak mengirim argumen sama sekali, parameter variadik menerima slice nil yang panjangnya nol, sedangkan pemanggilan dengan argumen satu per satu membuat slice baru yang isinya argumen-argumen tersebut. Fungsi bawaan append yang menyambung elemen ke slice juga dibangun di atas parameter variadik.

Parameter variadik dan parameter slice#

Di dalam badan fungsi, parameter variadik dan parameter biasa bertipe slice tidak dapat dibedakan karena keduanya bertipe []T. Perbedaannya muncul pada tempat pemanggilan, yaitu pilihan bentuk argumen yang tersedia bagi pemanggil. Fungsi dengan parameter slice menuntut pemanggil menyiapkan slice terlebih dahulu, sedangkan fungsi variadik memberi kebebasan mengirim nilai langsung atau membongkar slice yang sudah ada.

go Perbedaan pemanggilan parameter slice dan variadik
// tidak-dijalankan
data := []float64{22.5, 23.0}

jumlah(data)         // parameter slice: slice dikirim apa adanya
jumlah(data...)      // parameter variadik: isi slice dibongkar menjadi argumen
jumlah(24.0, 25.5)   // parameter variadik: argumen ditulis langsung

Karena kedua bentuk sama-sama menerima slice, pilihan di antara keduanya adalah soal kejelasan kontrak. Parameter eksplisit bertipe slice menyatakan bahwa masukannya lazim sudah berbentuk kumpulan, sedangkan parameter variadik menyatakan bahwa fungsi lazim dipanggil dengan beberapa nilai lepas. Pustaka standar memakai keduanya sesuai kebutuhan masing-masing fungsinya.

Menghitung dari sejumlah argumen#

Fungsi variadik paling mudah dipahami pada perhitungan atas sejumlah nilai. Program berikut menghitung rata-rata pembacaan suhu dari argumen yang dikirim pemanggil, lengkap dengan jumlah pembacaannya. Panggilan tanpa argumen tetap sah dan menghasilkan rata-rata nol bersama jumlah pembacaan nol.

go rekap-suhu.go
package main

import "fmt"

// rataRata menghitung rata-rata dari sejumlah pembacaan suhu.
// Parameter nilai bertipe []float64, tetapi diisi langsung dari argumen.
func rataRata(nilai ...float64) (float64, int) {
	total := 0.0
	for _, n := range nilai {
		total += n
	}
	if len(nilai) == 0 {
		return 0, 0
	}
	return total / float64(len(nilai)), len(nilai)
}

func main() {
	rata, n := rataRata(22.5, 23.0, 24.5)
	fmt.Printf("pagi: %.1f dari %d pembacaan\n", rata, n)

	rata, n = rataRata(29.0)
	fmt.Printf("siang: %.1f dari %d pembacaan\n", rata, n)

	rata, n = rataRata()
	fmt.Printf("malam: %.1f dari %d pembacaan\n", rata, n)
}
text Keluaran
pagi: 23.3 dari 3 pembacaan
siang: 29.0 dari 1 pembacaan
malam: 0.0 dari 0 pembacaan

Perulangan for range menjumlahkan seluruh elemen nilai tanpa perlu mengetahui banyaknya argumen. Konversi float64(len(nilai)) diperlukan agar pembagiannya dihitung dalam pecahan, bukan bilangan bulat. Kasus len(nilai) == 0 ditangani lebih dahulu supaya pembagian terhadap nol tidak pernah terjadi.

Meneruskan slice dengan …#

Sebuah slice dapat diteruskan ke parameter variadik dengan menambahkan tiga titik setelah namanya. Bentuk ini disebut unpacking, yaitu membongkar isi slice menjadi deretan argumen. Program berikut mengirim argumen satu per satu, lalu meneruskan slice, lalu memanggil tanpa argumen sama sekali.

go catatan-perangkat.go
package main

import "fmt"

// catat mencetak setiap pesan dengan nomor urut dan penanda sumbernya.
func catat(sumber string, pesan ...string) {
	for i, p := range pesan {
		fmt.Printf("%s-%d: %s\n", sumber, i+1, p)
	}
}

func main() {
	catat("sensor", "suhu naik", "kelembapan stabil")

	antrean := []string{"kipas hidup", "kipas mati"}
	catat("perangkat", antrean...)

	catat("cadangan")
}
text Keluaran
sensor-1: suhu naik
sensor-2: kelembapan stabil
perangkat-1: kipas hidup
perangkat-2: kipas mati

Pemanggilan catat("perangkat", antrean...) menghasilkan slice variadik yang berbagi data dengan antrean, sehingga fungsi dan pemanggil melihat data yang sama. Pemanggilan catat("cadangan") menunjukkan bahwa nol argumen variadik adalah keadaan yang sah. Badan fungsi tetap berjalan, tetapi perulangan tidak berputar sekali pun, sehingga tidak ada baris yang dicetak.

Meneruskan slice kosong dengan tiga titik juga sah. Pemanggilan catat("perangkat", []string{}...) menjalankan badan fungsi dengan slice berpanjang nol, sebagaimana pemanggilan tanpa argumen. Perbedaannya hanya pada isi parameternya, yaitu slice kosong yang bukan nil, dan perbedaan itu jarang memengaruhi jalannya program.

Perbedaan sumber slice ini berdampak pada perubahan data. Pemanggilan dengan argumen satu per satu membuat slice baru, sehingga penyuntingan elemen di dalam fungsi tidak terlihat oleh pemanggil. Sebaliknya, pemanggilan dengan antrean... meneruskan array penyangga yang sama, sehingga penyuntingan elemen di dalam fungsi terbaca oleh pemanggil lewat antrean miliknya. Karena itu, fungsi variadik sebaiknya memperlakukan parameternya sebagai nilai yang hanya dibaca kecuali perilaku penyuntingannya memang didokumentasikan.

Kesalahan umum#

  • Melupakan tiga titik saat meneruskan slice. Pernyataan catat("perangkat", antrean) gagal dikompilasi karena antrean bertipe []string, sedangkan slot argumennya menuntut string. Perbaikannya, tuliskan antrean... agar isinya dibongkar menjadi argumen.
  • Menempatkan parameter variadik bukan di posisi terakhir. Deklarasi func g(a ...int, b string) ditolak kompiler dengan pesan can only use … with final parameter. Perbaikannya, pindahkan parameter variadik ke akhir daftar parameter.
  • Memakai parameter variadik untuk masukan yang sebenarnya wajib ada. Fungsi luas persegi panjang selalu membutuhkan panjang dan lebar, sehingga menjadikannya variadik menyembunyikan jumlah minimum masukan. Perbaikannya, tulis parameter eksplisit dan simpan variadik untuk deretan yang memang tidak tetap.

Ringkasan#

  • Parameter ...T membuat fungsi menerima sejumlah argumen bertipe T apa pun, termasuk nol argumen yang menghasilkan slice nil.
  • Di dalam badan fungsi, parameter variadik adalah slice []T biasa yang dapat dihitung, diindeks, dan diulang.
  • Slice diteruskan ke parameter variadik dengan akhiran tiga titik pada nama variabelnya, dan isinya berbagi array penyangga dengan pemanggil.
  • Parameter variadik hanya boleh berada di posisi terakhir dan paling banyak satu per fungsi.

Referensi#