Berbagi teknologi

Penjelasan rinci tentang obat generik Go

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Perkenalan

Jika kita ingin menulis fungsi untuk membandingkan ukuran dua bilangan bulat dan bilangan floating point, kita harus menulis dua fungsi. sebagai berikut:

func Min(x, y float64) float64 {
    if x < y {
        return x
    }
    return y
}

func MinInt(x, y int) int {
	if x < y {
		return x
	}
	return y
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Kedua fungsi tersebut memiliki logika pemrosesan yang persis sama kecuali untuk tipe data yang berbeda. Apakah ada cara untuk menyelesaikan fungsi di atas dengan satu fungsi? Ya, itu umum.

func min[T int | float64](x, y T) T {
	if x < y {
		return x
	}
	return y
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Generik

Dokumen situs web resmi:https://go.dev/blog/intro-generik
Generik menambahkan tiga fitur baru yang penting ke bahasa ini:

  • Ketik parameter untuk fungsi dan tipe.
  • Definisikan tipe antarmuka sebagai sekumpulan tipe, termasuk tipe tanpa metode.
  • Ketik inferensi, dalam banyak kasus memungkinkan parameter tipe dihilangkan saat memanggil fungsi.

Ketik Parameter

Fungsi dan tipe sekarang diperbolehkan memiliki parameter tipe. Daftar parameter tipe terlihat mirip dengan daftar parameter normal, hanya saja daftar tersebut menggunakan tanda kurung siku, bukan tanda kurung bulat.
Masukkan deskripsi gambar di sini

package main

import (
	"fmt"
	"golang.org/x/exp/constraints"
)

func GMin[T constraints.Ordered](x, y T) T {
	if x < y {
		return x
	}
	return y
}

func main() {
	x := GMin[int](2, 3)
	fmt.Println(x) // 输出结果为2
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Diantaranya, kendala. Dipesan adalah tipe khusus (kode sumber tidak ditampilkan di sini).
Jika Anda tidak mengerti, Anda dapat mengganti kendala untuk sementara. Dipesan dengan ·int | float64

Memberikan parameter tipe (dalam hal ini int) ke GMin disebut instantiasi. Instansiasi terjadi dalam dua langkah.

  • Pertama, kompiler mengganti semua argumen tipe dengan parameter tipenya masing-masing di seluruh fungsi atau tipe generik.
  • Kedua, kompiler memverifikasi bahwa setiap parameter tipe memenuhi batasannya masing-masing.
    Kita akan segera melihat apa artinya ini, tetapi jika langkah kedua gagal, instantiasi akan gagal dan program menjadi tidak valid.

Setelah instantiasi berhasil, kita memiliki fungsi non-generik yang dapat dipanggil seperti fungsi lainnya.Misalnya dalam kode seperti

fmin := GMin[float64]
m := fmin(2.71, 3.14)
  • 1
  • 2

Semua kode adalah

package main

import (
	"fmt"
	"golang.org/x/exp/constraints"
)

func GMin[T constraints.Ordered](x, y T) T {
	if x < y {
		return x
	}
	return y
}

func main() {
	fmin := GMin[float64] // 相当于func GMin(x, y float64) float64{...}
	m := fmin(2.71, 3.14)
	fmt.Println(m) // 输出结果为2.71
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Membuat instance GMin[float64] sebenarnya menghasilkan fungsi Min floating point asli kita, yang dapat kita gunakan dalam pemanggilan fungsi.

Parameter tipe juga dapat digunakan dengan tipe.

type Tree[T interface{}] struct {
    left, right *Tree[T]
    value       T
}

func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }

var stringTree Tree[string]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Di sini tipe generik Pohon menyimpan nilai parameter tipe T. Tipe generik dapat memiliki metode, seperti Pencarian dalam contoh ini. Untuk menggunakan tipe generik,Itu harus dipakai; Tree[string] adalah contoh pembuatan instance Tree menggunakan parameter tipe string.

Ketik set

Setiap parameter tipe dalam daftar parameter tipe memiliki tipe. Karena parameter tipe itu sendiri adalah sebuah tipe, tipe dari parameter tipe mendefinisikan kumpulan tipe.Metatipe ini disebutbatasan tipe
Dalam metode generik GMin, batasan tipe diimpor dari paket batasan. Batasan terurut menggambarkan semua jenis koleksi yang memiliki nilai yang dapat diurutkan, atau dengan kata lain dibandingkan dengan operator &lt; (atau &lt;=, &gt;, dll.). Batasan ini memastikan bahwa hanya tipe dengan nilai yang dapat diurutkan yang dapat diteruskan ke GMin. Ini juga berarti bahwa di isi fungsi GMin, nilai parameter tipe ini dapat digunakan untuk perbandingan dengan operator &lt;.
Di Go, batasan tipe harus berupa antarmuka . Artinya, tipe antarmuka dapat digunakan sebagai tipe nilai atau tipe meta. Antarmuka mendefinisikan metode, jadi jelas kita dapat menyatakan batasan tipe yang memerlukan kehadiran metode tertentu. Namun kendala.Ordered juga merupakan tipe antarmuka, dan operator &lt; bukanlah sebuah metode.
Tujuan ganda dari tipe antarmuka memang merupakan konsep penting dalam bahasa Go. Mari kita pahami secara mendalam dan ilustrasikan dengan contoh pernyataan “tipe antarmuka dapat digunakan sebagai tipe nilai dan juga sebagai metatipe” [1] [2] [3] [4] [5].

  1. Antarmuka sebagai tipe nilai:

Ketika antarmuka digunakan sebagai tipe nilai, ia mendefinisikan sekumpulan metode sehingga tipe apa pun yang mengimplementasikan metode ini dapat ditetapkan ke variabel antarmuka. Ini adalah penggunaan antarmuka yang paling umum.

Misalnya:

type Stringer interface {
    String() string
}

type Person struct {
    Name string
}

func (p Person) String() string {
    return p.Name
}

var s Stringer = Person{"Alice"} // Person 实现了 Stringer 接口
fmt.Println(s.String()) // 输出: Alice
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Dalam contoh ini,Stringer Antarmuka digunakan sebagai tipe nilai,Person Tipe mengimplementasikanString() metode, sehingga dapat ditugaskan keStringer tipe variabel.

  1. Antarmuka sebagai tipe meta:

Ketika sebuah antarmuka digunakan sebagai metatipe, ia mendefinisikan sekumpulan batasan tipe untuk digunakan dalam pemrograman generik. Ini adalah penggunaan baru setelah diperkenalkannya obat generik di Go 1.18.

Misalnya:

type Ordered interface {
    int | float64 | string
}

func Min[T Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

fmt.Println(Min(3, 5))       // 输出: 3
fmt.Println(Min(3.14, 2.71)) // 输出: 2.71
fmt.Println(Min("a", "b"))   // 输出: a
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Dalam contoh ini,Ordered Antarmuka digunakan sebagai metatipe, yang menentukan sekumpulan tipe (bilangan bulat, angka floating point, dan string) yang dapat dibandingkan.Min Fungsi menggunakan antarmuka ini sebagai batasan tipe dan dapat menerima apa punOrdered Jenis batasan sebagai argumen.

Tujuan ganda ini membuat antarmuka Go sangat kuat dan fleksibel untuk pemrograman generik. Mereka tidak hanya dapat mendefinisikan perilaku objek (sebagai tipe nilai), tetapi juga mendefinisikan kumpulan tipe (sebagai metatipe), sehingga sangat meningkatkan ekspresi dan penggunaan kembali kode sambil menjaga kesederhanaan bahasa.

Sampai saat ini, spesifikasi Go mengatakan bahwa sebuah antarmuka mendefinisikan kumpulan metode, yang secara kasar merupakan kumpulan metode yang disebutkan dalam antarmuka. Tipe apa pun yang mengimplementasikan semua metode ini mengimplementasikan antarmuka ini.
Masukkan deskripsi gambar di sini
Namun cara lain untuk melihat hal ini adalah dengan mengatakan bahwa antarmuka mendefinisikan sekumpulan tipe, yaitu tipe yang mengimplementasikan metode ini. Dari perspektif ini, tipe apa pun yang merupakan elemen dari kumpulan tipe antarmuka mengimplementasikan antarmuka tersebut.
Masukkan deskripsi gambar di sini
Kedua tampilan tersebut menghasilkan hasil yang sama: untuk setiap kumpulan metode, kita dapat membayangkan kumpulan tipe terkait yang mengimplementasikan metode ini, yaitu kumpulan tipe yang ditentukan oleh antarmuka.

Namun, untuk tujuan kita, tampilan kumpulan tipe memiliki satu keunggulan dibandingkan tampilan kumpulan metode: kita dapat mengontrol kumpulan tipe dengan cara baru dengan secara eksplisit menambahkan tipe ke koleksi.

Kami memperluas sintaks tipe antarmuka untuk mencapai hal ini. Misalnya, interface{ int|string|bool } mendefinisikan kumpulan tipe yang berisi tipe int, string, dan bool.
Masukkan deskripsi gambar di sini
Cara lain untuk mengatakan ini adalah bahwa antarmuka hanya dipenuhi oleh int, string, atau bool.

Sekarang mari kita lihat definisi sebenarnya dari batasan. Diurutkan:

type Ordered interface {
    Integer|Float|~string
}
  • 1
  • 2
  • 3

Deklarasi ini menunjukkan bahwa antarmuka Ordered adalah kumpulan semua tipe integer, floating point, dan string. Bilah vertikal mewakili gabungan tipe (atau kumpulan tipe dalam kasus ini). Integer dan Float adalah tipe antarmuka yang didefinisikan serupa dalam paket batasan. Perhatikan bahwa antarmuka Ordered tidak mendefinisikan metode apa pun.

Untuk batasan tipe, kami biasanya tidak peduli dengan tipe tertentu, misalnya string; kami tertarik pada semua tipe string.Ini~ Tujuan dari token tersebut.ekspresi~string Mewakili kumpulan semua tipe yang tipe dasarnya adalah string.Ini termasuk tipe string itu sendiri dan semua tipe yang dideklarasikan dengan definisi, mis.type MyString string

Tentu saja kami masih ingin menentukan metode di antarmuka, dan kami ingin kompatibel ke belakang. Di Go 1.18, sebuah antarmuka dapat berisi metode dan antarmuka yang tertanam seperti sebelumnya, namun juga dapat menyematkan tipe non-antarmuka, gabungan, dan kumpulan tipe yang mendasarinya.

Antarmuka yang digunakan sebagai batasan dapat diberi nama (seperti Diurutkan) atau menjadi antarmuka literal sebaris dalam daftar parameter tipe. Misalnya:

[S interface{~[]E}, E interface{}]
  • 1

Di sini S harus bertipe irisan, dan tipe elemennya bisa bertipe apa saja.

Karena ini adalah situasi umum, untuk antarmuka yang membatasi posisi, antarmuka tertutup{} bisa dihilangkan, dan kita cukup menulis (sintaks gula generik dalam bahasa Go dan penulisan batasan tipe yang disederhanakan):

[S ~[]E, E interface{}]
  • 1

Karena antarmuka kosong umum ditemukan dalam daftar parameter tipe dan juga dalam kode Go normal, Go 1.18 memperkenalkan pengidentifikasi any yang telah dideklarasikan sebelumnya sebagai alias untuk tipe antarmuka kosong. Jadi, kita mendapatkan kode idiomatik ini:

[S ~[]E, E any]
  • 1

Ketik inferensi

Dengan parameter tipe, parameter tipe harus diteruskan, yang dapat menyebabkan kode verbose. Kembali ke fungsi GMin umum kita: