Map, Filter, dan Reduce adalah tiga fungsi utilitas paling terkenal di dunia pemrograman fungsional, dan ketiganya adalah kasus penggunaan generics yang paling alami: logika identik untuk tipe elemen apa pun. Kombinasi type parameter ganda, fungsi sebagai argumen, dan inferensi tipe menjadikan penulisan fungsi generik sesederhana fungsi biasa. Chapter ini menyusun ketiga fungsi tersebut dari nol, merangkum aturan inferensi tipe, lalu menguji batas constraint dengan pesan compiler yang asli.
Tiga Fungsi Utilitas Klasik #
Map mengubah setiap elemen slice menjadi nilai baru; Filter menyimpan elemen yang memenuhi syarat; Reduce melipat seluruh elemen menjadi satu nilai. Ketiganya membutuhkan type parameter karena tipe elemen slice harus fleksibel, dan Map bahkan membutuhkan dua type parameter karena tipe keluaran boleh berbeda dari tipe masukan:
func Map[T, U any](data []T, ubah func(T) U) []U {
hasil := make([]U, 0, len(data))
for _, v := range data {
hasil = append(hasil, ubah(v))
}
return hasil
}Type parameter [T, U any] berarti keduanya menerima tipe apa pun — constraint any cukup karena seluruh pekerjaan logika didelegasikan ke fungsi ubah yang dikirim pemanggil. Pola “kirim fungsi” ini adalah idiom Go dari chapter First-Class Function
; generics memperluasnya agar fungsi utilitas tidak diduplikasi per tipe.
Inferensi Tipe pada Pemanggilan #
Pemanggilan Map(harga, func(h int) float64 { ... }) tidak menuliskan satu pun type parameter, tetapi compiler mengisi keduanya. Aturan inferensinya ringkas:
- Tipe argumen biasa dicocokkan langsung ke posisi type parameter: slice
hargabertipe[]intmengisiT = int. - Konstanta tak bertipe memakai default type-nya —
0menjadiint,2.5menjadifloat64. - Fungsi anonim dicocokkan dengan tipe parameter fungsi: argumen
func(h int) float64dipadukan denganfunc(T) UmenghasilkanU = float64. - Jika pencocokan gagal — misalnya type parameter tidak pernah muncul pada argumen — pemanggilan wajib menuliskan instansiasi eksplisit seperti
Map[string, string](nama, strings.ToUpper).
Instansiasi eksplisit tetap sah kapan pun, dan sering dipilih ketika tipe yang disimpulkan tidak terbaca jelas. Program demo kita memakainya bersama strings.ToUpper — fungsi standard library bertipe func(string) string.
Fungsi dengan Constraint comparable #
Tidak semua fungsi generik cocok dengan any. Fungsi pencari indeks membutuhkan operator ==, sehingga constraint-nya adalah comparable yang kita bahas pada chapter Constraint comparable
:
func Index[T comparable](data []T, dicari T) int {
for i, v := range data {
if v == dicari {
return i
}
}
return -1
}Fungsi serupa di standard library lebih kaya: slices.Index dan slices.Contains untuk semua tipe comparable, maps.Keys untuk mengiterasi kunci map generik. Ketika kebutuhan Anda persis sama dengan stdlib, memakai stdlib adalah pilihan pertama — bahasan chapter Kapan Menggunakan Generics
.
Batas Constraint yang Tidak Terpenuhi #
Nilai nyata constraint baru terasa ketika compiler menolak tipe yang tidak memenuhinya. Berkas error-func.go mencoba memakai Index untuk slice of function:
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() {
// Function tidak comparable sehingga func() tidak memenuhi comparable.
langkah := []func(){func() { fmt.Println("mulai") }}
fmt.Println(Index(langkah, langkah[0]))
}Output yang Dihasilkan:
# command-line-arguments
./error-func.go:18:19: func() does not satisfy comparablePesan does not satisfy menyebut tipe penyalah (func()) dan constraint yang dilanggar (comparable) — tipe fungsi tidak pernah masuk himpunan comparable karena function tidak bisa dibandingkan dengan ==. Versi any dari fungsi ini akan menerima pemanggilan tersebut tanpa keluhan lalu gagal saat runtime.
Program Demo #
Berkas generic-func-demo.go menjalankan Map, Filter, dan Reduce pada data belanja:
package main
import (
"fmt"
"strings"
)
// Map mengubah setiap elemen []T menjadi []U lewat fungsi ubah.
func Map[T, U any](data []T, ubah func(T) U) []U {
hasil := make([]U, 0, len(data))
for _, v := range data {
hasil = append(hasil, ubah(v))
}
return hasil
}
// Filter menyimpan elemen []T yang memenuhi fungsi syarat.
func Filter[T any](data []T, syarat func(T) bool) []T {
hasil := make([]T, 0, len(data))
for _, v := range data {
if syarat(v) {
hasil = append(hasil, v)
}
}
return hasil
}
// Reduce melipat seluruh elemen []T menjadi satu nilai bertipe sama.
func Reduce[T any](data []T, awal T, gabung func(T, T) T) T {
total := awal
for _, v := range data {
total = gabung(total, v)
}
return total
}
func main() {
harga := []int{25000, 15000, 48000, 12000}
// T=int dan U=float64 tersimpulkan dari argumen.
diskon := Map(harga, func(h int) float64 { return float64(h) / 1000 })
fmt.Println("Dalam ribuan :", diskon)
mahal := Filter(harga, func(h int) bool { return h >= 20000 })
fmt.Println(">= 20 ribu :", mahal)
total := Reduce(harga, 0, func(a, b int) int { return a + b })
fmt.Println("Total :", total)
// Instansiasi eksplisit: kedua type parameter ditulis manual.
nama := []string{"budi", "siti", "dewi"}
kapital := Map[string, string](nama, strings.ToUpper)
fmt.Println("Kapital :", kapital)
}Output yang Dihasilkan:
Dalam ribuan : [25 15 48 12]
>= 20 ribu : [25000 48000]
Total : 100000
Kapital : [BUDI SITI DEWI]Mari kita telaah baris demi baris program di atas:
- Baris 9 (
func Map[T, U any]): dua type parameter dipisah koma;Tuntuk tipe elemen masukan danUuntuk tipe elemen keluaran, keduanya bebas karena logika ada di tangan fungsiubah. - Baris 10 (
make([]U, 0, len(data))): alokasi slice hasil bertipeUdengan kapasitaslen(data)— alokasi sekali, tanpa realokasi saatappendkarena jumlah elemen sudah diketahui. - Baris 18-25 (
Filter) dan 29-35 (Reduce): satu type parameter karena tipe masukan dan keluaran sama;Reducemenerima nilai awalawalbertipeTagar penjumlahan tetap se-tipe. - Baris 41 (
Map(harga, ...)): inferensi mengisiT = intdarihargadanU = float64dari tipe fungsi anonim; hasilnya[]float64berisi harga dalam ribuan. - Baris 44 (
Filter(harga, ...)): syarath >= 20000menyaring dua dari empat harga — slice[25000 48000]yang tersisa tetap bertipe[]int. - Baris 47 (
Reduce(harga, 0, ...)): nilai awal0adalah konstanta tak bertipe dengan default typeint, cocok denganT = int; total keempat harga menghasilkan100000. - Baris 52 (
Map[string, string]): instansiasi eksplisit dipadukan denganstrings.ToUpper;strings.ToUpperbertipefunc(string) stringsehingga hasilnya[]stringberisi nama kapital.
Referensi #
- Tutorial: Getting started with generics — tutorial resmi penulisan generic function.
- Spesifikasi Go: Type parameter declarations — aturan sintaks type parameter ganda.
- Why Generics? — latar belakang desain fungsi generik.
Fungsi generik menghasilkan keluarga fungsi; tipe generik menghasilkan keluarga tipe data. Chapter Generic Type
menyusun container Stack dengan method generiknya sendiri, dari deklarasi hingga konstruktor.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
error-func.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() {
// Function tidak comparable sehingga func() tidak memenuhi comparable.
langkah := []func(){func() { fmt.Println("mulai") }}
fmt.Println(Index(langkah, langkah[0]))
}
generic-func-demo.go
Unduh berkaspackage main
import (
"fmt"
"strings"
)
// Map mengubah setiap elemen []T menjadi []U lewat fungsi ubah.
func Map[T, U any](data []T, ubah func(T) U) []U {
hasil := make([]U, 0, len(data))
for _, v := range data {
hasil = append(hasil, ubah(v))
}
return hasil
}
// Filter menyimpan elemen []T yang memenuhi fungsi syarat.
func Filter[T any](data []T, syarat func(T) bool) []T {
hasil := make([]T, 0, len(data))
for _, v := range data {
if syarat(v) {
hasil = append(hasil, v)
}
}
return hasil
}
// Reduce melipat seluruh elemen []T menjadi satu nilai bertipe sama.
func Reduce[T any](data []T, awal T, gabung func(T, T) T) T {
total := awal
for _, v := range data {
total = gabung(total, v)
}
return total
}
func main() {
harga := []int{25000, 15000, 48000, 12000}
// T=int dan U=float64 tersimpulkan dari argumen.
diskon := Map(harga, func(h int) float64 { return float64(h) / 1000 })
fmt.Println("Dalam ribuan :", diskon)
mahal := Filter(harga, func(h int) bool { return h >= 20000 })
fmt.Println(">= 20 ribu :", mahal)
total := Reduce(harga, 0, func(a, b int) int { return a + b })
fmt.Println("Total :", total)
// Instansiasi eksplisit: kedua type parameter ditulis manual.
nama := []string{"budi", "siti", "dewi"}
kapital := Map[string, string](nama, strings.ToUpper)
fmt.Println("Kapital :", kapital)
}