2021-04-01 16:23:29 +00:00
|
|
|
generics
|
|
|
|
Лекция 8
|
|
|
|
|
|
|
|
Арсений Балобанов
|
|
|
|
|
2022-04-07 10:44:00 +00:00
|
|
|
* Generics
|
2021-04-01 16:23:29 +00:00
|
|
|
|
|
|
|
* New language features
|
|
|
|
|
2022-04-07 10:44:00 +00:00
|
|
|
- Type parameters for functions and types
|
|
|
|
- Type sets
|
|
|
|
- Type inference
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* 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)
|
|
|
|
|
2021-04-01 16:23:29 +00:00
|
|
|
* 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
|
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* 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
|
|
|
|
|
|
|
|
What happens when we call Sort?
|
|
|
|
|
|
|
|
Sort[book](bookshelf)
|
|
|
|
|
|
|
|
- Substitution. Substitute book for elem
|
|
|
|
|
|
|
|
Sort[Elem interface{ Less(y Elem) bool }] | (list []Elem)
|
|
|
|
|
|
|
|
Sort[book interface{ Less(y book) bool }] | (list []book)
|
|
|
|
|
|
|
|
- Verification. Verify that book satisfies the book parameter constraint
|
|
|
|
|
|
|
|
- Instantiate book-specific function
|
|
|
|
|
|
|
|
#Sort[book] | (list []book)
|
|
|
|
|
|
|
|
* Type-checking a generic call
|
|
|
|
|
|
|
|
Instantiation (new)
|
|
|
|
|
|
|
|
- replace type parameters with type arguments in entire signature
|
|
|
|
- verify that each type argument satisfies its constraint
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
* 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 }
|
|
|
|
|
2022-04-07 10:44:00 +00:00
|
|
|
* 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
|
|
|
|
|
2022-04-07 17:02:47 +00:00
|
|
|
- Type inference is complicated but usage is simple
|
2022-04-07 10:44:00 +00:00
|
|
|
- Programms that don't need type arguments today won't need them tomorrow
|
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* Scale
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
type Point []uint32
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
func (p Point) String() string { return "" }
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 16:01:48 +00:00
|
|
|
We'd like to write function ScaleAndPrint
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
func ScaleAndPrint(p Point) {
|
|
|
|
r := Scale(p, 2)
|
|
|
|
fmt.Println(r.String())
|
2021-04-01 16:23:29 +00:00
|
|
|
}
|
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* Scale, first attempt
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
.play -edit scale/wrong/scale.go /^func Scale/,/^}/
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* Scale, first attempt
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
.play -edit scale/wrong/scale.go /^func Scale/,/^}/
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
func ScaleAndPrint(p Point) {
|
|
|
|
r := Scale(p, 2)
|
|
|
|
fmt.Println(r.String())
|
|
|
|
}
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
- Compiler error: (type []uint32 has no field or method String)
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* Scale, fixed
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
.play -edit scale/fixed/scale.go /^func Scale/,/^}/
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* Scale, fixed
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
func Scale[S ~[]E, E constraints.Integer](s S, c E) S
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
vs
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
func Scale[E constraints.Integer](s []E, c E) []E
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* Inference
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
func ScaleAndPrint(p Point) {
|
|
|
|
r := Scale(p, 2)
|
|
|
|
fmt.Println(r.String())
|
2021-04-01 16:23:29 +00:00
|
|
|
}
|
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
Why don't we need explicit type parameters?
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
r := Scale[Point, int32](p, 2)
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* Inference
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
- argument type inference
|
|
|
|
- constraint type inference
|
2021-04-01 16:23:29 +00:00
|
|
|
|
|
|
|
* Argument type inference
|
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
func Scale[S ~[]E, E constraints.Integer](s S, c E) S
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
type Point []int32
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
Scale(p, 2)
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
- p is []Point => S is Point
|
|
|
|
- 2 is untyped constant => no info
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
* Constraint type inference
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
func Scale[S ~[]E, E constraints.Integer](s S, c E) S
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
type Point []int32
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
Scale(p, 2)
|
2021-04-01 16:23:29 +00:00
|
|
|
|
2022-04-07 15:04:16 +00:00
|
|
|
- S is Point (argument type inference)
|
|
|
|
- S is defined in terms of E => we can infer E
|
|
|
|
- E is int32
|
2021-04-01 16:23:29 +00:00
|
|
|
|
|
|
|
* 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 {
|
2022-04-07 16:25:34 +00:00
|
|
|
*T
|
2021-04-01 16:23:29 +00:00
|
|
|
}
|
|
|
|
|
2022-04-07 16:27:11 +00:00
|
|
|
func f[T any, PT Pointer[T]](p PT)
|
2021-04-01 16:23:29 +00:00
|
|
|
|
|
|
|
or with inlined constraint
|
|
|
|
|
2022-04-07 16:27:11 +00:00
|
|
|
func foo[T any, PT interface{*T}](p PT)
|
2021-04-01 16:23:29 +00:00
|
|
|
|
|
|
|
* 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
|