Banyak fungsi generik hanya butuh satu kemampuan dari tipe penggantinya: membandingkan kesamaan nilai dengan ==. Mencari posisi elemen, menghapus duplikat, dan menghitung frekuensi kemunculan semuanya berdiri di atas operator ==. Go menyediakan constraint predeclared comparable untuk kebutuhan tepat ini, sekaligus menjelaskan mengapa kunci map wajib comparable. Chapter ini membedah aturan tipe mana saja yang comparable, memakainya sebagai type parameter, dan membuktikan batasnya: slice, map, dan function tidak pernah comparable.
comparable Adalah Constraint Predeclared #
comparable adalah nama bawaan (predeclared) yang dideklarasikan sebagai interface dengan himpunan tipe semua nilai yang bisa dibandingkan dengan == dan !=. Constraint ini dipakai langsung pada deklarasi type parameter tanpa perlu import apa pun:
func Index[T comparable](data []T, dicari T) int {
for i, v := range data {
if v == dicari {
return i
}
}
return -1
}Di dalam fungsi, operator == sah dipakai pada v dan dicari karena constraint menjamin setiap tipe pengganti T mendukung operator tersebut. Satu hal yang sering keliru dipahami: comparable hanya menjamin kesamaan (==, !=), bukan pengurutan. Tipe string bisa diurutkan dengan <, tetapi struct umumnya tidak; constraint untuk pengurutan adalah cmp.Ordered yang kita singgung pada chapter Type Constraint
.
Syarat Kunci Map #
Map di Go menyimpan pasangan kunci-nilai dalam struktur hash, dan struktur hash hanya bisa bekerja bila setiap kunci mendukung ==. Itulah sebabnya spesifikasi Go mensyaratkan kunci map bertipe comparable. Constraint comparable mengangkat aturan yang sama ke fungsi generik: type parameter yang dipakai sebagai kunci map harus dibatasi comparable, sebagaimana fungsi Frekuensi pada program demo kita nanti yang membangun map[T]int dari T.
Tipe Mana yang Comparable #
Aturan comparability mengikuti definisi spesifikasi Go, dan tabel berikut merangkumnya:
| Tipe | Comparable | Dasar perbandingan |
|---|---|---|
bool, numerik, string | Ya | Nilai isinya |
| Pointer, channel | Ya | Identitas (alamat/tujuan sama) |
| Array | Ya, jika elemen comparable | Elemen demi elemen |
| Struct | Ya, jika semua field comparable | Field demi field |
| Slice, map, function | Tidak | Hanya bisa dibandingkan dengan nil |
Dua baris terakhir tabel butuh penekanan. Slice, map, dan function berisi referensi ke struktur internal yang tidak punya definisi kesamaan nilai; perbandingan x == y pada ketiganya ditolak compiler, dan perbandingan x == nil tetap sah untuk menguji keadaan kosong. Struct menjadi comparable hanya bila seluruh fieldnya comparable — struct berisi satu field slice saja sudah kehilangan status comparable.
Program Demo #
Berkas comparable-demo.go menjalankan fungsi Index untuk int, string, dan struct, lalu memakai comparable sebagai kunci map:
package main
import "fmt"
type Peserta struct {
Nama string
Skor int
}
// Index mengembalikan posisi pertama nilai yang sama, atau -1.
func Index[T comparable](data []T, dicari T) int {
for i, v := range data {
if v == dicari {
return i
}
}
return -1
}
// Frekuensi menghitung kemunculan tiap nilai; T dipakai sebagai kunci map.
func Frekuensi[T comparable](data []T) map[T]int {
hasil := make(map[T]int)
for _, v := range data {
hasil[v]++
}
return hasil
}
func main() {
angka := []int{10, 20, 30, 40}
fmt.Println("Index angka 30 :", Index(angka, 30))
fmt.Println("Index angka 99 :", Index(angka, 99))
buah := []string{"apel", "jeruk", "mangga"}
fmt.Println("Index pisang :", Index(buah, "pisang"))
// Struct comparable karena seluruh fieldnya comparable.
daftar := []Peserta{
{Nama: "Budi", Skor: 88},
{Nama: "Siti", Skor: 92},
}
fmt.Println("Index Siti :", Index(daftar, Peserta{Nama: "Siti", Skor: 92}))
// T yang comparable sah dipakai sebagai kunci map.
suara := Frekuensi([]string{"ya", "tidak", "ya", "ya", "tidak"})
fmt.Println("Frekuensi suara:", suara)
}Output yang Dihasilkan:
Index angka 30 : 2
Index angka 99 : -1
Index pisang : -1
Index Siti : 1
Frekuensi suara: map[tidak:2 ya:3]Mari kita telaah baris demi baris program di atas:
- Baris 5-8 (
type Peserta): struct dengan dua field comparable (stringdanint) membuatPesertacomparable secara keseluruhan. - Baris 11 (
func Index[T comparable]): constraintcomparablemengizinkan operator==pada baris 13; fungsi ini kini bekerja untuk semua tipe comparable tanpa duplikasi. - Baris 21 (
func Frekuensi[T comparable]): type parameterTdipakai sebagai kuncimap[T]intpada baris 22 — pemakaian ini sah justru karena constraint menjamin syarat kunci map. - Baris 31-32: pemanggilan untuk
[]intmengembalikan posisi30yaitu indeks2; nilai99tidak ditemukan sehingga nilai baliknya-1. - Baris 42: pembandingan dua struct dilakukan field demi field; literal
Peserta{Nama: "Siti", Skor: 92}sama nilainya dengan elemen indeks1sehingga hasilnya1. - Baris 45-46 (
Frekuensi): string"ya"muncul tiga kali dan"tidak"dua kali;fmt.Printlnmengurutkan kunci map sejak Go 1.12 sehingga outputmap[tidak:2 ya:3]selalu terurut dan deterministik antar eksekusi.
Batas comparable: Slice Bukan Tipe Comparable #
Fungsi Index di atas menolak slice sebagai argumen bukan karena aturan generics, melainkan karena slice memang tidak comparable. Berkas error-slice.go membuktikan batas tersebut:
package main
import "fmt"
// Index mengembalikan posisi pertama nilai yang sama, atau -1.
func Index[T comparable](data []T, dicari T) int {
for i, v := range data {
if v == dicari {
return i
}
}
return -1
}
func main() {
// Slice tidak comparable sehingga []int tidak memenuhi comparable.
matriks := [][]int{{1, 2}, {3, 4}}
fmt.Println(Index(matriks, []int{3, 4}))
}Output yang Dihasilkan:
# command-line-arguments
./error-slice.go:18:19: []int does not satisfy comparableCompiler menolak [][]int pada saat kompilasi karena tipe elemen []int tidak termasuk himpunan comparable. Pesan yang sama muncul untuk map dan function. Ketika Anda membutuhkan kesamaan isi dua slice, gunakan slices.Equal dari package slices — fungsi generik resmi yang membandingkan elemen satu per satu.
Referensi #
- Spesifikasi Go: Predeclared identifiers
— daftar resmi nama bawaan termasuk
comparable. - Tutorial: Getting started with generics
— tutorial resmi yang juga memakai
comparable. - Dokumentasi package slices
—
slices.Equalsebagai pembanding isi slice.
comparable bekerja karena compiler tahu isi himpunan tipe di baliknya — mekanisme deklaratif yang bernama type sets. Chapter Type Sets
membuka kotak hitam tersebut: bagaimana union, tilde, dan irisan interface menyusun himpunan tipe yang menentukan operasi sah pada type parameter.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
comparable-demo.go
Unduh berkaspackage main
import "fmt"
type Peserta struct {
Nama string
Skor int
}
// Index mengembalikan posisi pertama nilai yang sama, atau -1.
func Index[T comparable](data []T, dicari T) int {
for i, v := range data {
if v == dicari {
return i
}
}
return -1
}
// Frekuensi menghitung kemunculan tiap nilai; T dipakai sebagai kunci map.
func Frekuensi[T comparable](data []T) map[T]int {
hasil := make(map[T]int)
for _, v := range data {
hasil[v]++
}
return hasil
}
func main() {
angka := []int{10, 20, 30, 40}
fmt.Println("Index angka 30 :", Index(angka, 30))
fmt.Println("Index angka 99 :", Index(angka, 99))
buah := []string{"apel", "jeruk", "mangga"}
fmt.Println("Index pisang :", Index(buah, "pisang"))
// Struct comparable karena seluruh fieldnya comparable.
daftar := []Peserta{
{Nama: "Budi", Skor: 88},
{Nama: "Siti", Skor: 92},
}
fmt.Println("Index Siti :", Index(daftar, Peserta{Nama: "Siti", Skor: 92}))
// T yang comparable sah dipakai sebagai kunci map.
suara := Frekuensi([]string{"ya", "tidak", "ya", "ya", "tidak"})
fmt.Println("Frekuensi suara:", suara)
}
error-slice.go
Unduh berkaspackage main
import "fmt"
// Index mengembalikan posisi pertama nilai yang sama, atau -1.
func Index[T comparable](data []T, dicari T) int {
for i, v := range data {
if v == dicari {
return i
}
}
return -1
}
func main() {
// Slice tidak comparable sehingga []int tidak memenuhi comparable.
matriks := [][]int{{1, 2}, {3, 4}}
fmt.Println(Index(matriks, []int{3, 4}))
}