Constraint menetapkan himpunan tipe yang diizinkan untuk sebuah parameter tipe, sehingga badan fungsi mengetahui operasi yang boleh dipakai. Topik ini membahas constraint any dan comparable yang tersedia di pustaka standar, elemen union beserta tanda tilde untuk tipe dasar, serta penulisan interface constraint kustom yang menuntut metode tertentu. Setelah membacanya, pembatasan tipe pada fungsi generik dapat dipilih dan ditulis dengan alasan yang jelas.
Setiap constraint adalah interface#
Segala bentuk constraint pada Go berupa interface, yaitu daftar perilaku atau tipe yang dituntut dari parameter tipe. Constraint any adalah interface kosong yang tidak menuntut apa pun, sehingga semua tipe memenuhinya dan badan fungsi tidak boleh berasumsi tentang operasi yang tersedia. Bentuk constraint yang lebih berguna adalah daftar tipe yang dipisahkan tanda |, yang disebut elemen union (union). Union int | float64 menuntut T berupa int atau float64, dan operasi yang sah di dalam fungsi adalah operasi yang dimiliki kedua tipe itu sekaligus.
Fungsi berikut menghitung rerata untuk kumpulan int dan kumpulan float64 dari satu definisi. Konversi float64(n) dan penjumlahan total += sah karena kedua anggota union menyediakannya, sedangkan argumen bertipe lain ditolak saat kompilasi sebelum program sempat dijalankan. Pemeriksaan di titik ini jauh lebih murah daripada menemukan kesalahan tipe pada saat program berjalan.
package main
import "fmt"
// rataRata menghitung rerata sekumpulan nilai bertipe int atau float64.
func rataRata[T int | float64](nilai []T) float64 {
var total float64
for _, n := range nilai {
total += float64(n)
}
return total / float64(len(nilai))
}
func main() {
poin := []int{70, 80, 90}
berat := []float64{62.5, 71.0, 66.5}
fmt.Println(rataRata(poin))
fmt.Println(rataRata(berat))
}80
66.66666666666667Pemanggilan rataRata(poin) membentuk instansiasi dengan T berupa int, sedangkan rataRata(berat) membentuk instansiasi dengan T berupa float64. Kedua pemanggilan berbagi definisi yang sama, tetapi pemeriksaan tipe masing-masing tetap dilakukan penuh saat kompilasi.
comparable untuk perbandingan kesamaan#
Constraint comparable menuntut tipe yang mendukung operator == dan !=. Tipe bilangan, string, bool, pointer, channel, serta array dan struct yang seluruh isinya dapat dibandingkan memenuhi syarat ini, sedangkan slice, map, dan tipe fungsi tidak. Tipe interface juga memenuhi comparable sejak Go 1.20, dengan catatan perbandingannya dapat memicu panic saat berjalan apabila nilai di dalamnya tidak dapat dibandingkan. Constraint ini juga menjadi syarat bagi tipe kunci sebuah map, sehingga fungsi generik yang menyentuh kunci map umumnya menuntut comparable.
Constraint any dan comparable tersedia tanpa pernyataan impor karena menjadi bagian dari pustaka bawaan Go dan didokumentasikan pada paket builtin. Pemilihan comparable juga membuat maksud fungsi terbaca, karena pembaca langsung tahu bahwa fungsi itu hanya membutuhkan kesamaan dan bukan perbandingan urutan maupun operasi lain. Constraint yang menuntut lebih banyak operasi mempersempit himpunan tipe yang memenuhinya, sehingga pilihan constraint selalu menyeimbangkan kemampuan yang dibutuhkan badan fungsi dan luas tipe yang ingin dilayani.
Pencarian kesamaan memang membutuhkan constraint comparable. Penulisan dengan any ditolak kompilator sebagaimana berikut.
// tidak-dijalankan
func memuat[T any](daftar []T, target T) bool {
for _, v := range daftar {
if v == target {
return true
}
}
return false
}Kompilator menolak baris v == target dengan pesan invalid operation: v == target (incomparable types in type set), karena any memuat tipe yang tidak mendukung perbandingan kesamaan. Dengan comparable, fungsi yang sama sah dan dapat dipakai untuk daftar string maupun daftar bilangan.
package main
import "fmt"
// memuat melaporkan apakah target ada dalam daftar.
func memuat[T comparable](daftar []T, target T) bool {
for _, v := range daftar {
if v == target {
return true
}
}
return false
}
// hitung menghitung banyaknya kemunculan target dalam daftar.
func hitung[T comparable](daftar []T, target T) int {
n := 0
for _, v := range daftar {
if v == target {
n++
}
}
return n
}
func main() {
nilai := []string{"A", "B", "A", "C", "A"}
fmt.Println(memuat(nilai, "C"))
fmt.Println(memuat(nilai, "D"))
fmt.Println(hitung(nilai, "A"))
fmt.Println(hitung([]int{7, 7, 3}, 7))
}true
false
3
2Elemen aproksimasi untuk tipe dasar#
Union mencocokkan tipe secara persis. Tipe yang didefinisikan sendiri (defined type) seperti type Suhu float64 tidak termasuk ke dalam int | float64, sekalipun tipe dasarnya (underlying type) adalah float64. Pemanggilan rataRata([]Suhu{26.5, 27.0, 28.5}) pada fungsi bagian sebelumnya ditolak dengan pesan Suhu does not satisfy int | float64 (possibly missing ~ for float64 in int | float64).
Pesan tersebut sekaligus mengarahkan pada solusinya. Tanda tilde (~) pada elemen union menyatakan semua tipe yang tipe dasarnya cocok, dan bentuk ini disebut elemen aproksimasi (approximation element). Constraint ~int | ~float64 mencakup int, float64, serta tipe sendiri yang bertumpu pada keduanya, sehingga fungsi rataRata yang sama dapat dipakai untuk data sensor. Perluasan ini tidak mengubah badan fungsi sedikit pun, karena konversi float64(n) tetap sah untuk tipe apa pun yang tipe dasarnya float64. Elemen aproksimasi juga berguna untuk bentuk kumpulan lain, misalnya ~[]byte yang mencakup setiap tipe yang bertumpu pada []byte.
package main
import "fmt"
// Suhu adalah tipe sendiri dengan tipe dasar float64.
type Suhu float64
// rataRata menerima tipe apa pun yang tipe dasarnya int atau float64.
func rataRata[T ~int | ~float64](nilai []T) float64 {
var total float64
for _, n := range nilai {
total += float64(n)
}
return total / float64(len(nilai))
}
func main() {
sensor := []Suhu{26.5, 27.0, 28.5}
poin := []int{70, 80, 90}
fmt.Println(rataRata(sensor))
fmt.Println(rataRata(poin))
}27.333333333333332
80Interface constraint kustom#
Constraint juga dapat menuntut metode. Bentuk ini cocok untuk fungsi generik yang memanggil perilaku tertentu dari setiap item tanpa memedulikan tipe konkretnya, dan penulisannya sama dengan deklarasi interface biasa. Constraint Penilai berikut menuntut metode Nilai yang mengembalikan float64. Dua struct yang saling tidak berkaitan tetap dapat diproses oleh fungsi rerata yang sama karena keduanya menyediakan metode tersebut. Bentuk ini memadukan penuntutan perilaku dengan pemeriksaan tipe saat kompilasi. Setiap item tetap bertipe konkretnya di dalam badan fungsi, sehingga pemanggilan x.Nilai() tidak memerlukan asersi.
package main
import "fmt"
// Penilai adalah constraint untuk tipe yang menyediakan nilai numerik.
type Penilai interface {
Nilai() float64
}
// MataKuliah menyimpan satu mata kuliah beserta skornya.
type MataKuliah struct {
Nama string
Skor float64
}
// Nilai mengembalikan skor mata kuliah.
func (m MataKuliah) Nilai() float64 {
return m.Skor
}
// Latihan menyimpan satu latihan mandiri beserta skornya.
type Latihan struct {
Topik string
Skor float64
}
// Nilai mengembalikan skor latihan.
func (l Latihan) Nilai() float64 {
return l.Skor
}
// rerata menghitung rerata nilai dari sekumpulan item bertipe sama.
func rerata[T Penilai](item []T) float64 {
var total float64
for _, x := range item {
total += x.Nilai()
}
return total / float64(len(item))
}
func main() {
kuliah := []MataKuliah{
{Nama: "aljabar", Skor: 82},
{Nama: "statistika", Skor: 90},
}
fmt.Println(rerata(kuliah))
latihan := []Latihan{
{Topik: "perulangan", Skor: 75},
{Topik: "fungsi", Skor: 88},
{Topik: "struct", Skor: 91},
}
fmt.Println(rerata(latihan))
}86
84.66666666666667Pemanggilan dengan tipe yang tidak menyediakan metode yang dituntut ditolak saat kompilasi. Struct SkorMentah tanpa metode Nilai menghasilkan pesan in call to rerata, T (type SkorMentah) does not satisfy Penilai (missing method Nilai), sehingga ketiadaan metode terdeteksi sebelum program dijalankan. Seluruh program pada topik ini dapat dijalankan lewat terminal sebagaimana berikut.
# siapkan folder kerja untuk eksperimen topik ini
mkdir -p /tmp/generics/constraint
cp rerata-nilai.go rerata-suhu.go cari-elemen.go nilai-rerata.go /tmp/generics/constraint/
cd /tmp/generics/constraint
# pastikan tidak ada berkas yang keluar dari format gofmt
gofmt -l .
# jalankan keempat programnya
go run rerata-nilai.go
go run rerata-suhu.go
go run cari-elemen.go
go run nilai-rerata.go80
66.66666666666667
27.333333333333332
80
true
false
3
2
86
84.66666666666667Kesalahan umum#
Tiga kesalahan berikut paling sering muncul saat constraint baru ditulis sendiri.
- Menuliskan union tanpa tanda tilde untuk tipe yang didefinisikan sendiri. Constraint
int | float64menolak[]Suhudengan pesanSuhu does not satisfy int | float64 (possibly missing ~ for float64 in int | float64). Perbaikannya, tulis~int | ~float64agar seluruh tipe dengan tipe dasar yang sama ikut tercakup. - Membandingkan nilai pada constraint
any. Operator==ditolak dengan pesaninvalid operation: v == target (incomparable types in type set). Perbaikannya, gunakancomparablebila fungsi memang hanya membutuhkan perbandingan kesamaan. - Memanggil fungsi dengan tipe yang tidak menyediakan metode yang dituntut constraint. Pesan
T (type SkorMentah) does not satisfy Penilai (missing method Nilai)muncul saat kompilasi. Perbaikannya, lengkapi metodenya pada tipe tersebut atau longgarkan constraintnya.
Ringkasan#
- Setiap constraint berupa interface, dan constraint
anyadalah interface kosong yang memuat semua tipe. comparablemenuntut dukungan operator == dan != serta menjadi syarat bagi tipe kunci peta.- Elemen union mencocokkan tipe secara persis, sedangkan tanda ~ memperluasnya ke semua tipe dengan tipe dasar yang sama.
- Interface constraint kustom menuntut metode tertentu sehingga fungsi generik dapat memanggil perilaku tersebut dari setiap item.
Referensi#
- All your comparable types — The Go Blog — Penjelasan resmi constraint comparable dan tipe yang memenuhinya.
- Package builtin — The Go Authors
— Definisi
comparablepada pustaka standar. - An Introduction To Generics — The Go Blog — Bagian constraint pada pengantar resmi generics.
- The Go Programming Language Specification — The Go Project — Aturan resmi constraint, elemen union, dan elemen aproksimasi.