Teknologian jakaminen

Yksityiskohtainen selitys Go Genericistä

2024-07-12

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

Johdanto

Jos haluamme kirjoittaa funktion vertaamaan kahden kokonaisluvun ja liukulukuluvun kokoa, meidän on kirjoitettava kaksi funktiota. seuraavasti:

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

Molemmilla toiminnoilla on täsmälleen sama käsittelylogiikka eri tietotyyppejä lukuun ottamatta. Onko mahdollista suorittaa yllä oleva toiminto yhdellä toiminnolla? Kyllä, se on yleistä.

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

Generics

Virallinen verkkosivusto asiakirja:https://go.dev/blog/intro-generics
Generics lisää kieleen kolme tärkeää uutta ominaisuutta:

  • Kirjoita parametrit funktioille ja tyypeille.
  • Määritä liitäntätyyppi tyyppien joukkona, mukaan lukien tyypit ilman menetelmiä.
  • Tyyppipäätelmä mahdollistaa monissa tapauksissa tyyppiparametrien jättämisen pois funktioita kutsuttaessa.

Tyyppi Parametrit

Funktioilla ja tyypeillä voi nyt olla tyyppiparametreja. Tyyppiparametriluettelo näyttää samanlaiselta kuin normaali parametriluettelo, paitsi että siinä käytetään hakasulkeita pyöreiden hakasulkeiden sijaan.
Lisää kuvan kuvaus tähän

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

Niistä constraints.Ordered on mukautettu tyyppi (lähdekoodia ei näytetä tässä).
Jos et ymmärrä, voit tilapäisesti korvata rajoitukset. Tilattu ·int | float64

Tyyppiparametrin (tässä tapauksessa int) antamista GMin:lle kutsutaan ilmentymiseksi. Instantiaatio tapahtuu kahdessa vaiheessa.

  • Ensin kääntäjä korvaa kaikki tyyppiargumentit vastaavilla tyyppiparametreillaan yleisessä funktiossa tai tyypissä.
  • Toiseksi kääntäjä varmistaa, että jokainen tyyppiparametri täyttää vastaavat rajoitukset.
    Katsotaan pian, mitä tämä tarkoittaa, mutta jos toinen vaihe epäonnistuu, toteutus epäonnistuu ja ohjelma on virheellinen.

Onnistuneen ilmentymisen jälkeen meillä on ei-yleinen funktio, jota voidaan kutsua kuten mitä tahansa muuta funktiota.Esimerkiksi koodissa kuten

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

Kaikki koodit ovat

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

Instantiointi GMin[float64] tuottaa itse asiassa alkuperäisen liukulukumin-funktiomme, jota voimme käyttää funktiokutsuissa.

Tyyppiparametreja voidaan käyttää myös tyyppien kanssa.

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

Täällä yleinen puutyyppi tallentaa tyyppiparametrin T arvon. Yleisillä tyypeillä voi olla menetelmiä, kuten Lookup tässä esimerkissä. Jos haluat käyttää yleisiä tyyppejä,Se on instantoituPuu[merkkijono] on esimerkki puun luomisesta tyyppiparametrin merkkijonolla.

Tyyppisarjat

Jokaisella tyyppiparametriluettelon tyyppiparametrilla on tyyppi. Koska tyyppiparametri itsessään on tyyppi, tyyppiparametrin tyyppi määrittää tyyppijoukon.Tätä metatyyppiä kutsutaantyyppirajoitukset
Yleisessä menetelmässä GMin tyyppirajoitukset tuodaan rajoituspaketista. Järjestetyt rajoitukset kuvaavat kaikentyyppisiä kokoelmia, joilla on arvoja, jotka voidaan järjestää, eli toisin sanoen verrata &lt;-operaattoriin (tai &lt;=, &gt; jne.). Tämä rajoitus varmistaa, että vain lajiteltavia arvoja sisältävät tyypit voidaan välittää GMinille. Tämä tarkoittaa myös sitä, että GMin-funktion rungossa tämän tyyppiparametrin arvoa voidaan käyttää vertailuun &lt;-operaattoriin.
Go-sovelluksessa tyyppirajoitusten on oltava rajapintoja . Toisin sanoen liitäntätyyppejä voidaan käyttää arvotyyppeinä tai metatyyppeinä. Liitännät määrittelevät menetelmiä, joten tietysti voimme ilmaista tyyppirajoituksia, jotka edellyttävät tiettyjen menetelmien läsnäoloa. Mutta constraints.Ordered on myös käyttöliittymätyyppi, eikä &lt;-operaattori ole menetelmä.
Käyttöliittymätyyppien kaksoistarkoitus on todellakin tärkeä käsite Go-kielessä. Ymmärretään syvällisesti ja havainnollistetaan esimerkein [1][2][3][4][5] väite "rajapintatyyppejä voidaan käyttää arvotyyppeinä ja myös metatyyppeinä".

  1. Käyttöliittymä arvotyyppinä:

Kun liitäntää käytetään arvotyyppinä, se määrittelee joukon menetelmiä, jotka voidaan määrittää liitäntämuuttujalle mikä tahansa tyyppi, joka toteuttaa nämä menetelmät. Tämä on yleisin käyttöliittymien käyttö.

Esimerkiksi:

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

Tässä esimerkissäStringer Rajapintoja käytetään arvotyypeinä,Person Tyyppi toteuttaaString() menetelmää, joten se voidaan määrittääStringer tyyppinen muuttuja.

  1. Käyttöliittymä metatyyppinä:

Kun käyttöliittymää käytetään metatyyppinä, se määrittelee joukon tyyppirajoituksia käytettäväksi yleisessä ohjelmoinnissa. Tämä on uusi käyttötapa geneeristen lääkkeiden käyttöönoton jälkeen Go 1.18:ssa.

Esimerkiksi:

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

Tässä esimerkissäOrdered Rajapintoja käytetään metatyypeinä, jotka määrittelevät joukon tyyppejä (kokonaisluvut, liukulukuluvut ja merkkijonot), joita voidaan verrata.Min Funktiot käyttävät tätä käyttöliittymää tyyppirajoitteena ja voivat hyväksyä minkä tahansaOrdered Rajoituksen tyyppi argumenttina.

Tämä kaksoistarkoitus tekee Go:n liitännöistä erittäin tehokkaita ja joustavia yleiseen ohjelmointiin. Ne eivät voi vain määritellä objektien käyttäytymistä (arvotyyppeinä), vaan myös tyyppikokoelmia (metatyypeinä), mikä parantaa suuresti koodin ilmaisukykyä ja uudelleenkäytettävyyttä säilyttäen samalla kielen yksinkertaisuuden.

Viime aikoihin asti Go-spesifikaatiossa sanottiin, että rajapinta määrittelee menetelmäjoukon, joka on karkeasti ottaen rajapinnassa lueteltujen menetelmien joukko. Mikä tahansa tyyppi, joka toteuttaa kaikki nämä menetelmät, toteuttaa tämän käyttöliittymän.
Lisää kuvan kuvaus tähän
Mutta toinen tapa tarkastella tätä on sanoa, että käyttöliittymä määrittelee joukon tyyppejä, eli tyyppejä, jotka toteuttavat nämä menetelmät. Tästä näkökulmasta katsottuna mikä tahansa tyyppi, joka on liitäntätyyppijoukon elementti, toteuttaa kyseisen rajapinnan.
Lisää kuvan kuvaus tähän
Molemmat näkymät johtavat samaan tulokseen: kullekin menetelmäjoukolle voimme kuvitella vastaavan tyyppijoukon, joka toteuttaa nämä menetelmät, eli rajapinnan määrittelemän tyyppijoukon.

Meidän kannaltamme tyyppijoukkonäkymällä on kuitenkin yksi etu menetelmäjoukkonäkymään verrattuna: voimme ohjata tyyppijoukkoa uusilla tavoilla lisäämällä tyyppejä kokoelmaan.

Laajensimme käyttöliittymätyyppien syntaksia tämän saavuttamiseksi. Esimerkiksi käyttöliittymä{ int|string|bool } määrittää tyyppijoukon, joka sisältää int-, merkkijono- ja bool-tyypit.
Lisää kuvan kuvaus tähän
Toinen tapa sanoa tämä on, että käyttöliittymä täyttää vain int, merkkijono tai bool.

Katsotaan nyt rajoitusten varsinaista määritelmää. Järjestetty:

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

Tämä ilmoitus osoittaa, että tilattu käyttöliittymä on kokoelma kokonaisluku-, liukuluku- ja merkkijonotyyppejä. Pystypalkit edustavat tyyppien liitoksia (tai tässä tapauksessa tyyppijoukkoja). Integer ja Float ovat rajapintatyyppejä, jotka on määritelty samalla tavalla rajoituspaketissa. Huomaa, että Tilattu käyttöliittymä ei määrittele mitään menetelmiä.

Tyyppirajoitusten osalta emme yleensä välitä tietyistä tyypeistä, kuten merkkijonoista, olemme kiinnostuneita kaikista merkkijonotyypeistä.Tämä on~ Tunnusmerkin tarkoitus.ilmaisu~string Edustaa kokoelmaa kaikkia tyyppejä, joiden taustalla oleva tyyppi on merkkijono.Tämä sisältää itse tyyppimerkkijonon ja kaikki määritelmillä ilmoitetut tyypit, esim.type MyString string

Tietenkin haluamme edelleen määrittää menetelmiä käyttöliittymässä ja haluamme olla taaksepäin yhteensopivia. Go 1.18:ssa käyttöliittymä voi sisältää menetelmiä ja sulautettuja rajapintoja kuten ennenkin, mutta se voi myös upottaa ei-rajapintatyyppejä, liitoksia ja taustalla olevien tyyppien joukkoja.

Rajoituksena käytettävä liitäntä voi olla joko nimetty (kuten Tilattu) tai olla kirjaimellinen liitäntä tyyppiparametriluettelossa. Esimerkiksi:

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

Tässä S:n on oltava slice-tyyppi, ja sen elementtityyppi voi olla mikä tahansa tyyppi.

Koska tämä on yleinen tilanne, paikkoja rajoittavissa liitännöissä suljettu käyttöliittymä{} voidaan jättää pois, ja voimme yksinkertaisesti kirjoittaa (Gon-kielellä geneeristen aineiden syntaksisokeri ja tyyppirajoitusten yksinkertaistettu kirjoittaminen):

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

Koska tyhjät liitännät ovat yleisiä tyyppiparametriluetteloissa sekä normaalissa Go-koodissa, Go 1.18 ottaa käyttöön uuden ennalta ilmoitetun tunnisteen any aliaksena tyhjille liitäntätyypeille. Siten saamme tämän idiomaattisen koodin:

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

Tyyppipäätelmä

Tyyppiparametreilla tyyppiparametrit on välitettävä, mikä voi johtaa monisanaiseen koodiin. Takaisin yleiseen GMin-toimintoomme: