generics Лекция 8 Арсений Балобанов * Generics * New language features - Type parameters for functions and types - Type sets - Type inference * Parameter lists An ordinary parameter list (x, y aType, z anotherType) A type parameter list [P, Q aConstraint, R anotherConstraint] - Convention: Type parameter names are capitalized * min .play -edit min/basic/min.go /^func min/,/^}/ * Generic min .play -edit min/basic/min.go /^func min/,/^}/ .play -edit min/generic/min.go /^func min/,/^}/ * Calling generic min m := min[int](1, 2) * Instantiation fmin := min[float64] // non generic m := fmin(2.71, 3.14) * Generic type type Tree[T interface{}] struct { left, right *Tree[T] data T } func (t *Tree[T]) Lookup(x T) *Tree[T] var stringTree Tree[string] - Methods cannot have it's own type parameters * Type sets func min(x, y float64) float64 - float64 defines a set of values x and y can have func min[T constraints.Ordered](x, y T) T { - constraints.Ordered defines a set of values T can have * constaints.Ordered // Ordered is a constraint that permits any ordered type: any type // that supports the operators < <= >= >. // If future releases of Go add new ordered types, // this constraint will be modified to include them. type Ordered interface { Integer | Float | ~string } - The < operator is supported by every type in this subset - ~T means with underlying type T * constaints type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } type Unsigned interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } type Integer interface { Signed | Unsigned } type Float interface { ~float32 | ~float64 } type Complex interface { ~complex64 | ~complex128 } * Constaints & type sets [T aConstraint] - aConstraint is an interface - interface has a type set - type set defines the types that are permissible * Constaint literals [S interface{~[]E}, E interface{}] - interface{E} could be rewritten as E in a constraint [S ~[]E, E interface{}] - any is a new predeclared identifier — an alias for interface{} in a constraint [S ~[]E, E any] * Type inference - Type intefence is complicated but usage is simple - Programms that don't need type arguments today won't need them tomorrow * Sorting in Go what we have func Sort(data Interface) type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) } what we really want func Sort(list []Elem) // use Sort(myList) * Type parameters to the rescue func Sort[Elem ?](list []Elem) * Constraints - A constraint specifies the requirements which a type argument must satisfy. - In generic Go, constraints are interfaces - A type argument is valid if it implements its constraint. * Generic Sort func Sort[Elem interface{ Less(y Elem) bool }](list []Elem) { ... } - The constraint is an interface, but the actual type argument can be any type that implements that interface. - The scope of a type parameter starts at the opening "[" and ends at the end of the generic type or function declaration * Using generic Sort Somewhere in library func Sort[Elem interface{ Less(y Elem) bool }](list []Elem) User code type book struct{...} func (x book) Less(y book) bool {...} var bookshelf []book ... Sort[book](bookshelf) // generic function call * Type-checking the Sort call: Instantiation Sort[book] | (bookshelf) pass type argument Sort[Elem interface{ Less(y Elem) bool }] | (list []Elem) substitute book for elem Sort[book interface{ Less(y book) bool }] | (list []book) verify that book satisfies the book parameter constraint #Sort[book] | (list []book) A generic function or type must be instantiated before it can be used. * Type-checking a generic call Instantiation (new) - replace type parameters with type arguments in entire signature - verify that each type argument satisfies its constraintThen, using the instantiated signature. Invocation (as usual) - verify that each ordinary argument can be assigned to its parameter * Types can be generic, too type Lesser[T any] interface{ Less(y T) bool } any stands for "no constraint" (same as "interface{}") * Sort, decomposed type Lesser[T any] interface{ Less(y T) bool } func Sort[Elem Lesser[Elem]](list []Elem) * Sort internals func Sort[Elem interface{ Less(y Elem) bool }](list []Elem) { ... var i, j int ... if list[i].Less(List[j]) {...} ... } - type of list[i], list[j] is Elem - Elem is NOT an interface type! - A type parameter is a real type. It is not an interface type. * Argument type inference what we have Sort[book](bookshelf) what we want Sort(bookshelf) Type unification bookshelf -> []book Inference func Sort[Elem ...]([]Elem) => Elem == book * Problems what we want Sort([]int{1, 2, 3}) int does not implement Elem constraint (no Less method) what we could do type myInt int func (x myInt) Less(y myInt) bool { return x < y } * Type lists A constraint interface may have a list of types (besides methods): type Float interface { type float32, float64 } // Sin computes sin(x) for x of type float32 or float64. func Sin[T Float](x T) T Satisfying a type list An argument type satisfies a constraint with a type list if - The argument type implements the methods of the constraint - The argument type or its underlying type is found in the type list. As usual, the satisfaction check happens after substitution. * Generic min function type Ordered interface { type int, int8, int16, ..., uint, uint8, uint16, ..., float32, float64, string } min internals func min[T Ordered](x, y T) T { if x < y { return x } return y } * Different type parameters are different types func invalid[Tx, Ty Ordered](x Tx, y Ty) Tx { ... if x < y { ...// INVALID ... } - "<" requires that both operands have the same type * Relationships between type parameters type Pointer[T any] interface { type *T } func f[T any, PT Pointer[T]](x T) or with inlined constraint func foo[T any, PT interface{type *T}](x T) * When to use generics - Improved static type safety. - More efficient memory use. - (Significantly) better performance. * Summary Generics are type-checked macros. Declarations - Type parameter lists are like ordinary parameter lists with "[" "]". - Function and type declarations may have type parameter lists. - Type parameters are constrained by interfaces. Use - Generic functions and types must be instantiated when used. - Type inference (if applicable) makes function instantiation implicit. - Instantiation is valid if the type arguments satisfy their constraints. * Ссылки .link https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-contracts.md - generics design draft .link https://blog.golang.org/why-generics - The Go Blog - Why Generics? .link https://www.youtube.com/watch?v=TborQFPY2IM - GopherCon 2020, Robert Griesemer - Typing [Generic] Go