Κοινή χρήση τεχνολογίας

Λεπτομερής επεξήγηση των Go generics

2024-07-12

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

Εισαγωγή

Αν θέλουμε να γράψουμε μια συνάρτηση για να συγκρίνουμε τα μεγέθη δύο ακεραίων και αριθμών κινητής υποδιαστολής αντίστοιχα, πρέπει να γράψουμε δύο συναρτήσεις. ως εξής:

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

Οι δύο συναρτήσεις έχουν ακριβώς την ίδια λογική επεξεργασίας εκτός από τους διαφορετικούς τύπους δεδομένων. Υπάρχει κάποιος τρόπος να πραγματοποιηθεί η παραπάνω λειτουργία με μία λειτουργία; Ναι, αυτό είναι γενικό.

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

Γενόσημα

Επίσημο έγγραφο της ιστοσελίδας:https://go.dev/blog/intro-generics
Τα Generics προσθέτουν τρία σημαντικά νέα χαρακτηριστικά στη γλώσσα:

  • Πληκτρολογήστε παραμέτρους για συναρτήσεις και τύπους.
  • Ορίστε έναν τύπο διεπαφής ως ένα σύνολο τύπων, συμπεριλαμβανομένων τύπων χωρίς μεθόδους.
  • Το συμπέρασμα τύπου, σε πολλές περιπτώσεις επιτρέπει την παράλειψη παραμέτρων τύπου κατά την κλήση συναρτήσεων.

Τύπος Παράμετροι

Οι συναρτήσεις και οι τύποι επιτρέπεται πλέον να έχουν παραμέτρους τύπου. Μια λίστα παραμέτρων τύπου μοιάζει με μια κανονική λίστα παραμέτρων, με τη διαφορά ότι χρησιμοποιεί αγκύλες αντί για στρογγυλές αγκύλες.
Εισαγάγετε την περιγραφή της εικόνας εδώ

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

Μεταξύ αυτών, οι περιορισμοί. Το Ordered είναι προσαρμοσμένος τύπος (ο πηγαίος κώδικας δεν εμφανίζεται εδώ).
Εάν δεν καταλαβαίνετε, μπορείτε να αντικαταστήσετε προσωρινά περιορισμούς. Παραγγελία με ·int | float64

Η παροχή μιας παραμέτρου τύπου (int σε αυτήν την περίπτωση) σε ένα GMin ονομάζεται instantiation. Η εγκατάσταση πραγματοποιείται σε δύο βήματα.

  • Πρώτον, ο μεταγλωττιστής αντικαθιστά όλα τα ορίσματα τύπου με τις αντίστοιχες παραμέτρους τύπου τους σε όλη τη γενική συνάρτηση ή τύπο.
  • Δεύτερον, ο μεταγλωττιστής επαληθεύει ότι κάθε παράμετρος τύπου ικανοποιεί τους αντίστοιχους περιορισμούς της.
    Θα δούμε τι σημαίνει αυτό σύντομα, αλλά αν το δεύτερο βήμα αποτύχει, η εγκατάσταση θα αποτύχει και το πρόγραμμα θα είναι άκυρο.

Μετά την επιτυχή εγκατάσταση, έχουμε μια μη γενική συνάρτηση που μπορεί να κληθεί όπως οποιαδήποτε άλλη συνάρτηση.Για παράδειγμα, σε κώδικα όπως

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

Όλοι οι κωδικοί είναι

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

Η εκκίνηση του GMin[float64] παράγει στην πραγματικότητα την αρχική μας συνάρτηση κινητής υποδιαστολής Min, την οποία μπορούμε να χρησιμοποιήσουμε σε κλήσεις συναρτήσεων.

Οι παράμετροι τύπου μπορούν επίσης να χρησιμοποιηθούν με τύπους.

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. Οι γενικοί τύποι μπορούν να έχουν μεθόδους, όπως η Αναζήτηση σε αυτό το παράδειγμα. Για να χρησιμοποιηθούν γενικοί τύποι,Πρέπει να δημιουργηθείΤο δέντρο[string] είναι ένα παράδειγμα δημιουργίας του δέντρου χρησιμοποιώντας τη συμβολοσειρά παραμέτρου τύπου.

Σύνολα τύπων

Κάθε παράμετρος τύπου στη λίστα παραμέτρων τύπου έχει έναν τύπο. Εφόσον μια παράμετρος τύπου είναι η ίδια ένας τύπος, ο τύπος της παραμέτρου τύπου ορίζει το σύνολο των τύπων.Αυτός ο μετατύπος ονομάζεταιπεριορισμούς τύπου
Στη γενική μέθοδο GMin, οι περιορισμοί τύπου εισάγονται από το πακέτο περιορισμών. Οι περιορισμοί παραγγελίας περιγράφουν όλους τους τύπους συλλογών που έχουν τιμές που μπορούν να παραγγελθούν, ή με άλλα λόγια, να συγκριθούν με τον τελεστή &lt; (ή &lt;=, &gt;, κ.λπ.). Αυτός ο περιορισμός διασφαλίζει ότι μόνο οι τύποι με τιμές με δυνατότητα ταξινόμησης μπορούν να περάσουν στο GMin. Αυτό σημαίνει επίσης ότι στο σώμα συνάρτησης GMin, η τιμή αυτής της παραμέτρου τύπου μπορεί να χρησιμοποιηθεί για σύγκριση με τον τελεστή &lt;.
Στο Go, οι περιορισμοί τύπων πρέπει να είναι διεπαφές . Δηλαδή, οι τύποι διεπαφής μπορούν να χρησιμοποιηθούν ως τύποι τιμών ή μετα-τύποι. Οι διεπαφές ορίζουν μεθόδους, επομένως προφανώς μπορούμε να εκφράσουμε περιορισμούς τύπου που απαιτούν την παρουσία ορισμένων μεθόδων. Αλλά το constraints.Ordered είναι επίσης ένας τύπος διεπαφής και ο τελεστής &lt; δεν είναι μέθοδος.
Ο διπλός σκοπός των τύπων διεπαφής είναι πράγματι μια σημαντική έννοια στη γλώσσα Go. Ας κατανοήσουμε σε βάθος και ας επεξηγήσουμε με παραδείγματα τη δήλωση "οι τύποι διεπαφής μπορούν να χρησιμοποιηθούν ως τύποι τιμών και επίσης ως μετατύποι" [1][2][3][4][5].

  1. Διεπαφή ως τύπος τιμής:

Όταν μια διεπαφή χρησιμοποιείται ως τύπος τιμής, ορίζει ένα σύνολο μεθόδων που οποιοσδήποτε τύπος που εφαρμόζει αυτές τις μεθόδους μπορεί να αντιστοιχιστεί στη μεταβλητή διεπαφής. Αυτή είναι η πιο κοινή χρήση διεπαφών.

Για παράδειγμα:

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

Σε αυτό το παράδειγμα,Stringer Οι διεπαφές χρησιμοποιούνται ως τύποι τιμών,Person Ο τύπος υλοποιείString() μέθοδο, ώστε να μπορεί να αντιστοιχιστεί σεStringer μεταβλητή τύπου.

  1. Διεπαφή ως μετα-τύπου:

Όταν μια διεπαφή χρησιμοποιείται ως μετατύπος, ορίζει ένα σύνολο περιορισμών τύπου για χρήση σε γενικό προγραμματισμό. Αυτή είναι μια νέα χρήση μετά την εισαγωγή των γενόσημων στο Go 1.18.

Για παράδειγμα:

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

Σε αυτό το παράδειγμα,Ordered Οι διεπαφές χρησιμοποιούνται ως μετατύποι, οι οποίοι ορίζουν ένα σύνολο τύπων (ακέραιοι, αριθμοί κινητής υποδιαστολής και συμβολοσειρές) που μπορούν να συγκριθούν.Min Οι συναρτήσεις χρησιμοποιούν αυτήν τη διεπαφή ως περιορισμό τύπου και μπορούν να δεχτούν οποιονδήποτεOrdered Το είδος του περιορισμού ως όρισμα.

Αυτός ο διπλός σκοπός καθιστά τις διεπαφές της Go πολύ ισχυρές και ευέλικτες για γενικό προγραμματισμό. Μπορούν όχι μόνο να ορίσουν τη συμπεριφορά των αντικειμένων (ως τύπους τιμών), αλλά και να ορίσουν συλλογές τύπων (ως μετατύπους), ενισχύοντας έτσι σημαντικά την εκφραστικότητα και την επαναχρησιμοποίηση του κώδικα διατηρώντας παράλληλα την απλότητα της γλώσσας.

Μέχρι πρόσφατα, η προδιαγραφή Go έλεγε ότι μια διεπαφή ορίζει ένα σύνολο μεθόδων, το οποίο είναι κατά προσέγγιση το σύνολο των μεθόδων που απαριθμούνται στη διεπαφή. Οποιοσδήποτε τύπος που υλοποιεί όλες αυτές τις μεθόδους υλοποιεί αυτήν τη διεπαφή.
Εισαγάγετε την περιγραφή της εικόνας εδώ
Αλλά ένας άλλος τρόπος για να το δούμε αυτό είναι να πούμε ότι μια διεπαφή ορίζει ένα σύνολο τύπων, δηλαδή τύπους που υλοποιούν αυτές τις μεθόδους. Από αυτή την άποψη, κάθε τύπος που είναι στοιχείο ενός συνόλου τύπων διεπαφής υλοποιεί αυτήν τη διεπαφή.
Εισαγάγετε την περιγραφή της εικόνας εδώ
Και οι δύο απόψεις οδηγούν στο ίδιο αποτέλεσμα: για κάθε σύνολο μεθόδων, μπορούμε να φανταστούμε το αντίστοιχο σύνολο τύπων που υλοποιούν αυτές τις μεθόδους, δηλαδή το σύνολο τύπων που ορίζονται από τη διεπαφή.

Για τους σκοπούς μας, ωστόσο, η προβολή συνόλου τύπων έχει ένα πλεονέκτημα έναντι της προβολής συνόλου μεθόδων: μπορούμε να ελέγξουμε το σύνολο τύπων με νέους τρόπους προσθέτοντας ρητά τύπους στη συλλογή.

Επεκτείναμε τη σύνταξη των τύπων διεπαφής για να το πετύχουμε. Για παράδειγμα, η διεπαφή{ int|string|bool } ορίζει ένα σύνολο τύπων που περιέχει τους τύπους int, string και bool.
Εισαγάγετε την περιγραφή της εικόνας εδώ
Ένας άλλος τρόπος να το πούμε αυτό είναι ότι η διεπαφή ικανοποιείται μόνο με int, string ή bool.

Τώρα ας δούμε τον πραγματικό ορισμό των περιορισμών. Παραγγελία:

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

Αυτή η δήλωση υποδεικνύει ότι η διεπαφή Ordered είναι μια συλλογή όλων των τύπων ακεραίων, κινητής υποδιαστολής και συμβολοσειράς. Οι κάθετες ράβδοι αντιπροσωπεύουν ενώσεις τύπων (ή σύνολα τύπων σε αυτήν την περίπτωση). Το Integer και το Float είναι τύποι διεπαφής που ορίζονται παρόμοια στο πακέτο περιορισμών. Σημειώστε ότι η διεπαφή Ordered δεν ορίζει καμία μέθοδο.

Για περιορισμούς τύπου συνήθως δεν μας ενδιαφέρουν συγκεκριμένοι τύποι, όπως οι συμβολοσειρές, μας ενδιαφέρουν όλοι οι τύποι συμβολοσειρών.Αυτό είναι~ Ο σκοπός του διακριτικού.έκφραση~string Αντιπροσωπεύει μια συλλογή όλων των τύπων των οποίων ο υποκείμενος τύπος είναι συμβολοσειρά.Αυτό περιλαμβάνει την ίδια τη συμβολοσειρά τύπου και όλους τους τύπους που δηλώνονται με ορισμούς, π.χ.type MyString string

Φυσικά, εξακολουθούμε να θέλουμε να καθορίσουμε μεθόδους στη διεπαφή και θέλουμε να είμαστε συμβατοί προς τα πίσω. Στο Go 1.18, μια διεπαφή μπορεί να περιέχει μεθόδους και ενσωματωμένες διεπαφές όπως πριν, αλλά μπορεί επίσης να ενσωματώσει τύπους, ενώσεις και σύνολα υποκείμενων τύπων χωρίς διεπαφή.

Η διεπαφή που χρησιμοποιείται ως περιορισμός μπορεί είτε να ονομαστεί (όπως Ordered) είτε να είναι μια κυριολεκτική διεπαφή ενσωματωμένη στη λίστα παραμέτρων τύπου. Για παράδειγμα:

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

Εδώ το S πρέπει να είναι τύπος φέτας και ο τύπος στοιχείου του μπορεί να είναι οποιοσδήποτε τύπος.

Επειδή αυτή είναι μια συνηθισμένη κατάσταση, για διεπαφές που περιορίζουν θέσεις, η κλειστή διεπαφή{} μπορεί να παραλειφθεί και μπορούμε απλώς να γράψουμε (συντακτικό σάκχαρο γενικών στη γλώσσα Go και απλοποιημένη γραφή περιορισμών τύπου):

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

Επειδή οι κενές διεπαφές είναι κοινές στις λίστες παραμέτρων τύπου καθώς και στον κανονικό κώδικα Go, το Go 1.18 εισάγει ένα νέο προδηλωμένο αναγνωριστικό οποιοδήποτε ως ψευδώνυμο για κενούς τύπους διεπαφής. Έτσι, παίρνουμε αυτόν τον ιδιωματικό κώδικα:

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

Συμπέρασμα τύπου

Με τις παραμέτρους τύπου, πρέπει να περάσουν οι παράμετροι τύπου, κάτι που μπορεί να οδηγήσει σε αναλυτικό κώδικα. Επιστροφή στη γενική μας λειτουργία GMin: