summaryrefslogtreecommitdiff
path: root/_content/blog
diff options
context:
space:
mode:
Diffstat (limited to '_content/blog')
-rw-r--r--_content/blog/index.adoc5
-rw-r--r--_content/blog/intro-generics/index.adoc496
2 files changed, 501 insertions, 0 deletions
diff --git a/_content/blog/index.adoc b/_content/blog/index.adoc
index e7140a2..0fe8293 100644
--- a/_content/blog/index.adoc
+++ b/_content/blog/index.adoc
@@ -5,6 +5,11 @@
== Indeks
+=== 2022
+
+* link:/blog/intro-generics/[Pengenalan terhadap generik^],
+ 22 Maret 2022. Robert Griesemer dan Ian Lance Taylor.
+
=== 2021
* link:/blog/tls-cipher-suites/[Pemilihan pasangan _cipher_ otomatis pada crypto/tls^], 15 September 2021.
diff --git a/_content/blog/intro-generics/index.adoc b/_content/blog/intro-generics/index.adoc
new file mode 100644
index 0000000..4f0c0d7
--- /dev/null
+++ b/_content/blog/intro-generics/index.adoc
@@ -0,0 +1,496 @@
+= Pengenalan terhadap generik
+Robert Griesemer; Ian Lance Taylor
+22 Maret 2022
+:toc:
+:sectlinks:
+
+== Pendahuluan
+
+Blog ini berdasarkan wicara pada GopherCon 2021.
+
+video::Pa_e9EeCdy8[youtube,width=640]
+
+Rilis Go 1.18 menambah dukungan terhadap generik.
+Generik adalah perubahan terbesar yang kami lakukan terhadap Go sejak
+rilis pertama.
+Dalam artikel ini kami akan memperkenalkan fitur bahasa yang baru.
+Kami tidak akan membahas semuanya secara rinci, namun kami akan
+jelaskan beberapa poin penting.
+Untuk deskripsi yang lebih detil dan lengkap, termasuk
+contoh-contohnya, lihatlah
+https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md[dokumen proposal^].
+Untuk deskripsi tentang perubahan bahasa, lihat
+https://go.home.local/go.dev/ref/spec[pembaruan dari spesifikasi bahasa^].
+(Ingatlah bahwa implementasi generik pada versi 1.18 belum
+menerapkan semua gagasan yang ada dalam proposal; tapi spesifikasi
+bahasa seharusnya sudah akurat.
+Rilis selanjutnya bisa melengkapi implementasi tersebut.)
+
+Generik adalah cara menulis kode yang tidak bergantung pada tipe
+tertentu.
+Tipe dan fungsi dapat ditulis menggunakan sekumpulan tipe.
+
+Generik menambahkan tiga hal besar pada bahasa Go:
+
+. Parameter tipe untuk fungsi dan tipe.
+. Mendefinisikan tipe interface sebagai sekumpulan tipe, termasuk
+ tipe-tipe yang tidak memiliki _method_.
+. Inferensi tipe, yang membolehkan mengabaikan argumen tipe saat
+ memanggil sebuah fungsi.
+
+
+== Parameter tipe
+
+Sekarang, fungsi dan tipe bisa memiliki parameter tipe.
+Sebuah daftar parameter tipe bentuknya seperti daftar parameter
+seperti biasa, kecuali mereka dibungkus dengan kurung siku "[]" bukan
+dengan tanda kurung lengkung "()".
+
+Untuk memperlihatkan cara kerjanya, mari kita mulai dengan fungsi
+`Min` tanpa generik untuk nilai desimal:
+----
+func Min(x, y float64) float64 {
+ if x < y {
+ return x
+ }
+ return y
+}
+----
+
+Kita dapat membuat fungsi `Min` tersebut menerima tipe-tipe yang
+berbeda dengan menambahkan sebuah parameter tipe.
+Pada contoh ini kita tambahkan parameter tipe `T`, dan mengganti
+penggunaan `float64` dengan `T`.
+----
+import "golang.org/x/exp/constraints"
+
+func GMin[T constraints.Ordered](x, y T) T {
+ if x < y {
+ return x
+ }
+ return y
+}
+----
+
+Sekarang kita bisa memanggil fungsi tersebut dengan argumen tipe
+dengan menulis pemanggilan seperti berikut
+----
+x := GMin[int](2, 3)
+----
+
+Dengan menyediakan argumen tipe ke fungsi `GMin`, pada contoh ini
+yaitu `int`, disebut juga dengan _instansiasi_.
+Instansiasi terjadi dalam dua tahap.
+Pertama, _compiler_ mengganti semua argumen tipe untuk semua parameter
+tipe yang diberikan lewat fungsi atau tipe generik.
+Kedua, _compiler_ memverifikasi setiap argumen tipe memenuhi batasan
+yang diberikan.
+Kita akan jelaskan maksud dari tahap kedua nanti, namun bila tahap
+tersebut gagal, maka instansiasi akan gagal dan program menjadi
+invalid.
+
+Setelah instansiasi sukses, kita memiliki sebuah fungsi non-generik
+yang dapat dipanggil seperti fungsi lainnya.
+Misalnya, pada kode berikut
+----
+fmin := GMin[float64]
+m := fmin(2.71, 3.14)
+----
+instansiasi `GMin[float64]` menghasilkan sebuah fungsi yang secara
+efektif seperti fungsi `Min` sebelumnya, yang dapat kita gunakan
+sebagai pemanggilan fungsi.
+
+Parameter tipe dapat digunakan juga pada tipe bentukan.
+----
+type Tree[T interface{}] struct {
+ left, right *Tree[T]
+ value T
+}
+
+func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }
+
+var stringTree Tree[string]
+----
+
+Pada contoh di atas, tipe generik `Tree` menyimpan nilai parameter
+bertipe `T`.
+Tipe-tipe generik dapat memiliki method, seperti `Lookup` pada contoh
+tersebut.
+Untuk dapat menggunakan tipe generik, ia harus di-instansiasi;
+`Tree[string]` adalah contoh cara meng-instansiasi tipe `Tree` dengan
+argumen tipe `string`.
+
+
+== Kumpulan tipe
+
+Mari kita lihat lebih dalam tentang argumen tipe yang dapat digunakan
+untuk meng-instansiasi sebuah parameter tipe.
+
+Sebuah fungsi biasa memiliki sebuah tipe untuk setiap nilai pada
+parameter;
+tipe tersebut mendefinisikan sekumpulan nilai.
+Misalnya, bila kita memiliki tipe `float64` seperti pada fungsi
+non-generik `Min` di atas, maka kumpulan nilai argumen yang dibolehkan
+yaitu kumpulan dari nilai _float_ yang dapat direpresentasikan oleh
+tipe `float64`.
+
+Hal yang sama, daftar parameter tipe memiliki sebuah tipe untuk setiap
+parameter tipe.
+Secara sebuah parameter tipe itu sendiri adalah sebuah tipe, maka
+tipe-tipe dari parameter tipe berisi kumpulan tipe.
+Meta-tipe ini disebut juga dengan _batasan tipe_ atau _type
+constraint_.
+
+Pada fungsi generik `GMin`, batasan tipe diimpor dari
+https://go.home.local/golang.org/x/exp/constraints[paket `constraints`^].
+Batasan `Ordered` berisi kumpulan dari semua tipe dengan nilai yang
+dapat diurut, atau, dengan kata lain, dapat dibandingkan dengan
+operator pembanding `<`, `\<=`, `>`, atau `\>=`.
+Batasan ini memastikan bahwa hanya tipe-tipe dengan nilai yang dapat
+diurut saja yang dapat dikirim ke `GMin`.
+Ia juga berarti bahwa di dalam badan fungsi `GMin` nilai dari
+parameter tipe dapat digunakan dalam pembandingan dengan operator
+`<`.
+
+Dalam Go, batasan tipe haruslah berupa interface.
+Sebuah tipe interface dapat digunakan sebagai sebuah tipe pada nilai,
+dan juga dapat digunakan sebagai meta-tipe.
+Interface mendefinisikan method-method, sehingga kita dapat
+mengekspresikan batasan tipe yang membutuhkan beberapa method
+tertentu.
+Tapi `constrains.Ordered` adalah tipe interface juga, dan operator
+`<` bukanlah sebuah method.
+
+Supaya dapat bekerja, kita harus melihat interface dengan cara baru.
+
+Spesifikasi Go menyatakan bahwa sebuah interface mendefinisikan
+kumpulan dari method.
+Tipe apa pun yang mengimplementasikan semua method tersebut berarti
+mengimplementasikan interface tersebut.
+
+image::https://go.dev/blog/intro-generics/method-sets.png["Kumpulan method",width=540]
+
+(Catatan penulis: dari gambar di atas, cara pandang umum dari
+interface yaitu tipe P, Q, dan R mengimplementasikan interface).
+
+Namun cara lain memandang hal ini yaitu menyatakan bahwa interface
+mendefinisikan kumpulan tipe, yaitu tipe-tipe yang
+mengimplementasikan method-method tersebut.
+Dari perspektif ini, tipe apa pun yang merupakan elemen dari kumpulan
+tipe interface mengimplementasikan interface tersebut.
+
+image::https://go.dev/blog/intro-generics/type-sets.png["Kumpulan tipe", width=540]
+
+(Catatan penulis: dari gambar di atas, cara pandang lain dari
+interface yaitu tipe P, Q dan R adalah kumpulan tipe dari interface).
+
+Dua cara pandang ini mengarah ke hasil yang sama: Untuk setiap
+kumpulan method kita dapat bayangkan korespondensi kumpulan tipe yang
+mengimplementasikan kumpulan method tersebut, yaitu kumpulan dari
+tipe yang didefinisikan oleh interface.
+
+Untuk tujuan ini, cara pandang terhadap kumpulan tipe memiliki
+kelebihan dibandingkan cara pandang terhadap kumpulan method: kita
+dapat secara eksplisit menambah tipe ke dalam sebuah kumpulan, dan hal
+ini mengontrol kumpulan tipe dengan cara yang baru.
+
+Kami telah mengembangkan sintaksis untuk tipe interface supaya hal ini
+bekerja.
+Misalnya, `interface{ int|string|bool }` mendefinisikan kumpulan tipe
+yang berisi tipe `int`, `string`, dan `bool`.
+
+image::https://go.dev/blog/intro-generics/type-sets-2.png["Type sets 2",width=540]
+
+Cara lain dari menyebut hal di atas yaitu interface tersebut dipenuhi
+hanya oleh `int`, `string`, atau `bool`.
+
+Sekarang mari kita lihat definisi dari `constrains.Ordered`:
+----
+type Ordered interface {
+ Integer|Float|~string
+}
+----
+
+Deklarasi tersebut menyatakan bahwa interface `Ordered` adalah
+kumpulan dari semua tipe integer, float, dan string.
+Simbol baris vertikal (pipa) mengekspresikan union dari tipe (atau
+sekumpulan dari tipe pada kasus ini).
+`Integer` dan `Float` adalah tipe interface yang juga didefinisikan di
+dalam paket `constrains`.
+Ingatlah bahwa tidak ada method yang didefinisikan oleh interface
+`Ordered`.
+
+Untuk batasan tipe kita tidak memperdulikan tipe tertentu, seperti
+`string`; kita lebih tertarik dengan semua tipe string.
+Itulah guna dari token `~`.
+Ekspresi dari `~string` artinya kumpulan dari semua tipe yang tipe
+dasarnya adalah `string`.
+Termasuk tipe `string` itu sendiri sebagaimana juga semua tipe yang
+dideklarasikan dengan definisi seperti `type MyString string`.
+
+Tentu saja kita masih ingin menspesifikasikan method di dalam
+interface, dan kita masih ingin tetap menjaga kompatibilitas
+terbelakang.
+Dalam Go 1.18 sebuah interface bisa berisi sekumpulan method dan
+menanam interface seperti sebelumnya, namun ia juga bisa menanam
+tipe-tipe non-interface, union, dan sekumpulan tipe-tipe dasar.
+
+Saat digunakan sebagai batasan tipe, kumpulan tipe yang didefinisikan
+oleh sebuah interface menspesifikasikan tipe-tipe apa saja yang
+dibolehkan sebagai argumen tipe terhadap parameter tipe.
+Di dalam badan fungsi generik, jika tipe dari sebuah operan adalah
+parameter tipe `P` dengan batasan `C`, operasi akan dibolehkan jika
+semua tipe dalam kumpulan tipe dari `C` membolehkan operasi tersebut
+(saat ini ada beberapa batasan implementasi, namun kode pada umumnya
+akan jarang menemukan batasan tersebut).
+
+Interface yang digunakan sebagai batasan bisa diberi nama (seperti
+`Ordered`), atau bisa berupa interface literal sebaris di dalam daftar
+parameter tipe.
+Misalnya:
+----
+[S interface{~[]E}, E interface{}]
+----
+
+Di sini, `S` haruslah tipe slice yang elemen-nya bisa tipe apa saja
+(`interface{}`).
+
+Karena kasus ini umum, maka `interface{}` dapat diabaikan pada saat
+penulisan batasan, sehingga menjadi lebih sederhana seperti:
+----
+[S ~[]E, E interface{}]
+----
+
+Karena interface kosong sangat umum dalam daftar parameter tipe, dan
+juga di dalam kode Go, Go 1.18 memperkenalkan identifikasi baru `any`
+sebagai alias dari tipe interface kosong.
+Dengan ini, kita dapat menulis kode lebih sederhana dan idiomatis:
+----
+[S ~[]E, E any]
+----
+
+Interface sebagai kumpulan tipe adalah sebuah mekanisme baru yang
+sangat berguna dan merupakan kunci untuk membuat batasan tipe bekerja
+dalam Go.
+Untuk saat sekarang, interface yang menggunakan bentuk sintaksis yang
+baru hanya bisa digunakan sebagai batasan saja.
+
+
+== Inferensi tipe
+
+Fitur baru dari bahasa Go yang terakhir yaitu inferensi tipe.
+Ini adalah perubahan paling kompleks pada bahasa Go, namun sangat
+penting karena ia memudahkan pengguna saat menulis kode menggunakan
+fungsi generik.
+
+=== Inferensi tipe pada argumen fungsi
+
+Dengan adanya parameter tipe maka dibutuhkan pengiriman argumen
+tipe, yang membuat kode lebih panjang.
+Kembali ke fungsi generik sebelumnya `GMin`:
+----
+func GMin[T constraints.Ordered](x, y T) T { ... }
+----
+parameter tipe `T` digunakan untuk menentukan tipe dari argumen `x`
+dan `y`.
+Seperti yang kita lihat sebelumnya, fungsi ini bisa dipanggil dengan
+secara eksplisit menulis argumen tipe:
+----
+var a, b, m float64
+
+m = GMin[float64](a, b) // argumen tipe eksplisit: [float64].
+----
+
+Pada banyak kasus, _compiler_ dapat menurunkan argumen tipe untuk `T`
+dari argumen-argumen fungsi.
+Hal ini membuat kode lebih singkat dan jelas.
+----
+var a, b, m float64
+
+m = GMin(a, b) // tidak ada argumen tipe.
+----
+
+Hal ini bekerja dengan menyamakan tipe-tipe dari argumen `a` dan `b`
+dengan tipe-tipe dari parameter `x` dan `y`.
+
+Jenis inferensi ini, yang menurunkan argumen tipe dari tipe-tipe
+argumen pada fungsi, disebut dengan _inferensi tipe pada argumen
+fungsi_.
+
+Inferensi tipe pada argumen fungsi hanya bekerja untuk parameter tipe
+yang digunakan dalam parameter fungsi, tidak untuk parameter tipe yang
+digunakan pada kembalian fungsi atau hanya di dalam badan fungsi.
+Contohnya, ia tidak berlaku untuk fungsi seperti `MakeT[T any]() T`,
+yang hanya menggunakan `T` sebagai tipe kembalian.
+
+
+=== Inferensi tipe batasan
+
+Bahasa Go mendukung jenis inferensi lain, _inferensi tipe batasan_.
+Untuk menjelaskan hal ini, mari kita mulai dengan contoh berikut yang
+mengembangkan sebuah slice dari integer:
+
+----
+// Scale mengembalikan salinan dari s dengan setiap elemen dikalikan
+// dengan c.
+// Implementasi ini memiliki masalah, yang akan kita lihat nanti.
+func Scale[E constraints.Integer](s []E, c E) []E {
+ r := make([]E, len(s))
+ for i, v := range s {
+ r[i] = v * c
+ }
+ return r
+}
+----
+Fungsi generik di atas bekerja untuk sebuah slice dari tipe integer
+apa pun.
+
+Anggap kita memiliki tipe `Point`, yang setiap `Point` adalah daftar
+nilai integer yang merupakan koordinat dari suatu titik.
+Biasanya tipe seperti ini akan memiliki beberapa _method_.
+----
+type Point []int32
+
+func (p Point) String() string {
+ // Badan fungsi ...
+}
+----
+
+Anggaplah kita ingin men-`Scale` sebuah `Point`.
+Secara `Point` adalah slice dari integer, kita dapat menggunakan
+fungsi `Scale` yang kita punya sebelumnya:
+----
+// ScaleAndPrint kali dua setiap nilai pada Point dan cetak.
+func ScaleAndPrint(p Point) {
+ r := Scale(p, 2)
+ fmt.Println(r.String()) // GAGAL KOMPILASI!
+}
+----
+
+Sayangnya kode tersebut gagal kompilasi, dengan galat seperti
+"`r.String undefined (type []int32 has no field or method String)`".
+
+Masalahnya adalah fungsi `Scale` mengembalikan sebuah nilai bertipe
+`[]E` yang mana `E` adalah tipe elemen dari argumen slice.
+Saat kita memanggil `Scale` dengan nilai dari tipe `Point`, yang tipe
+dasarnya adalah `[]int32`, kita mendapatkan nilai kembalian bertipe
+`[]int32` bukan `Point`.
+Hal ini memang sesuai dengan cara menulis kode generik, namun bukan
+yang kita inginkan.
+
+Untuk memperbaiki masalah ini, kita harus mengubah fungsi `Scale`
+menggunakan sebuah parameter tipe untuk tipe slice.
+
+----
+// Scale mengembalikan salinan s dengan setiap elemen dikalikan dengan
+// c.
+func Scale[S ~[]E, E constraints.Integer](s S, c E) S {
+ r := make(S, len(s))
+ for i, v := range s {
+ r[i] = v * c
+ }
+ return r
+}
+----
+
+Kita menambahkan sebuah parameter tipe baru `S` yaitu tipe dengan
+argumen slice.
+Kita telah membatasi parameter tipe tersebut sehingga tipe dasar
+adalah `S` bukan lagi `[]E`, dan tipe kembalian sekarang menjadi `S`.
+Secara `E` dibatasi sebagai integer, efeknya sama dengan sebelumnya:
+argumen pertama haruslah slice dengan tipe integer.
+Perubahan pada badan fungsi hanya pada saat pemanggilan `make`, yang
+sebelumnya `[]E` menjadi `S`.
+
+Fungsi `Scale` yang baru bekerja seperti sebelumnya bila kita
+memanggilnya dengan slice biasa, namun bila kita panggil dengan tipe
+`Point` kita mendapatkan kembalian bertipe `Point` juga.
+Inilah yang kita inginkan.
+Dengan versi `Scale` yang baru, fungsi `ScaleAndPrint` sebelumnya
+dapat dikompilasi dan berjalan seperti yang kita inginkan.
+
+Anda mungkin bertanya: kenapa boleh menulis pemanggilan `Scale` tanpa
+mengirim argumen tipe secara eksplisit?
+Dengan kata lain, kenapa kita dapat menulis `Scale(p, 2)`, tanpa
+argumen tipe, bukan dengan menulis `Scale[Point, int32](p, 2)`?
+Fungsi `Scale` kita yang baru memiliki dua parameter tipe, `S` dan
+`E`.
+Saat memanggil `Scale` tanpa mengirim argumen tipe, inferensi tipe
+pada argumen fungsi terjadi, seperti yang telah dijelaskan sebelumnya,
+_compiler_ menurunkan bahwa argumen tipe untuk `S` adalah `Point`.
+Namun fungsi tersebut juga memiliki parameter tipe `E` yang mana
+merupakan tipe dari parameter kedua `c`.
+Nilai parameter dari `c` adalah 2, dan karena 2 adalah konstanta tak
+bertipe, inferensi tipe pada argumen fungsi tidak dapat menurunkan
+tipe yang tepat untuk `E` (paling tidak _compiler_ bisa menurunkan
+tipe baku dari 2 yang mana `int` dan pada hal ini tidak benar).
+Proses di mana _compiler_ menurunkan argumen tipe untuk `E` adalah
+tipe elemen dari slice disebut dengan _inferensi tipe batasan_.
+
+_Inferensi tipe batasan_ men-deduksi argumen tipe dari batasan
+parameter tipe.
+_Inferensi tipe batasan_ digunakan saat parameter tipe memiliki sebuah
+batasan yang didefinisikan oleh parameter tipe lainnya.
+Saat argumen tipe dari salah satu tipe parameter tipe diketahui, maka
+batasan akan digunakan untuk menurunkan argumen tipe lainnya.
+
+Kasus umum yang mana hal ini dapat dipakai yaitu saat salah satu
+batasan tipe menggunakan bentuk `~type`, yang mana `type` tersebut
+ditulis menggunakan parameter tipe lainnya.
+Kita melihat hal ini dipakai pada contoh `Scale`.
+`S` adalah `~[]E`, yaitu `~` diikuti oleh tipe `[]E` ditulis dengan
+menggunakan parameter tipe lainnya.
+Jika kita mengetahui argumen tipe untuk `S` kita dapat menurunkan
+argumen tipe untuk `E`.
+`S` adalah tipe slice, dan `E` adalah tipe elemen dari slice tersebut.
+
+Hal ini adalah pengenalan dari inferensi tipe batasan.
+Untuk lebih lengkapnya lihat
+https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md[dokumen proposal^]
+atau
+https://go.home.local/go.dev/ref/spec[spesifikasi bahasa].
+
+
+=== Inferensi tipe di dunia nyata
+
+Penjelasan rinci tentang bagaimana inferensi tipe bekerja sangatlah
+kompleks, namun penggunaan-nya tidak: inferensi tipe bisa sukses, bisa
+gagal.
+Jika sukses, argumen tipe dapat diabaikan, dan memanggil fungsi
+generik tidak ada bedanya dengan memanggil fungsi seperti biasa.
+Jika inferensi tipe gagal, _compiler_ akan menampilkan pesan
+kesalahan, dan pada kasus tersebut kita dapat menyediakan argumen tipe
+yang diperlukan.
+
+Pada saat menambahkan inferensi tipe ke dalam bahasa, kami telah
+mencoba menyeimbangkan antara kompleksitas dan keuntungan dari
+inferensi tipe.
+Kami ingin memastikan bahwa saat _compiler_ menurunkan tipe, tipe-tipe
+tersebut akan terdeteksi.
+Kami mencoba berhati-hati, lebih memilih supaya gagal menurunkan tipe
+daripada memilih menurunkan tipe yang salah.
+Kami mungkin belum benar sepenuhnya, dan kami terus memperbaiki di
+setiap rilis selanjutnya.
+Efeknya adalah makin banyak program yang dapat ditulis tanpa argumen
+tipe yang eksplisit.
+Program yang tidak membutuhkan argumen tipe pada saat ini, tidak akan
+membutuhkan-nya di kemudian hari juga.
+
+
+== Kesimpulan
+
+Generik adalah fitur baru dalam bahasa Go 1.18.
+Perubahan yang baru tersebut membutuhkan begitu banyak kode baru
+yang belum sepenuhnya diuji dalam lingkungan _production_.
+Hal itu akan terjadi saat makin banyak orang menulis dan menggunakan
+kode generik.
+Kami percaya bahwa fitur ini diimplementasikan dengan benar dan dengan
+kualitas tinggi.
+Namun, tidak seperti kebanyakan aspek pada Go, kita tidak dapat
+membuktikan kepercayaan kita dengan pengalaman di dunia nyata.
+Oleh karena itu, kita mendorong penggunaan generik bila dibutuhkan,
+namun perhatikan lebih seksama saat merilis kode generik ke
+_production_.
+
+Terlepas dari peringatan tersebut, kami sangat gembira dengan adanya
+generik, dan kami harap ia membuat pemrograman Go lebih produktif.