[lectures] Update generics slides.

This commit is contained in:
Arseny Balobanov 2022-04-07 18:04:16 +03:00
parent 181a2cff69
commit ddf9df28dd
5 changed files with 195 additions and 144 deletions

View file

@ -11,6 +11,29 @@ generics
- Type sets - Type sets
- Type inference - Type inference
* 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)
* Parameter lists * Parameter lists
An ordinary parameter list An ordinary parameter list
@ -23,6 +46,95 @@ A type parameter list
- Convention: Type parameter names are capitalized - Convention: Type parameter names are capitalized
* 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 }
* min * min
.play -edit min/basic/min.go /^func min/,/^}/ .play -edit min/basic/min.go /^func min/,/^}/
@ -125,185 +237,84 @@ A type parameter list
- Type intefence is complicated but usage is simple - Type intefence is complicated but usage is simple
- Programms that don't need type arguments today won't need them tomorrow - Programms that don't need type arguments today won't need them tomorrow
* Sorting in Go * Scale
what we have type Point []uint32
func Sort(data Interface) func (p Point) String() string { return "" }
type Interface interface { We'd like to write function scale and Println
Len() int
Less(i, j int) bool func ScaleAndPrint(p Point) {
Swap(i, j int) r := Scale(p, 2)
fmt.Println(r.String())
} }
what we really want * Scale, first attempt
func Sort(list []Elem) .play -edit scale/wrong/scale.go /^func Scale/,/^}/
// use * Scale, first attempt
Sort(myList)
* Type parameters to the rescue .play -edit scale/wrong/scale.go /^func Scale/,/^}/
func Sort[Elem ?](list []Elem) func ScaleAndPrint(p Point) {
r := Scale(p, 2)
* Constraints fmt.Println(r.String())
- 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. - Compiler error: (type []uint32 has no field or method String)
- 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 * Scale, fixed
Somewhere in library .play -edit scale/fixed/scale.go /^func Scale/,/^}/
func Sort[Elem interface{ Less(y Elem) bool }](list []Elem) * Scale, fixed
User code func Scale[S ~[]E, E constraints.Integer](s S, c E) S
type book struct{...} vs
func (x book) Less(y book) bool {...}
var bookshelf []book func Scale[E constraints.Integer](s []E, c E) []E
...
Sort[book](bookshelf) // generic function call
* Type-checking the Sort call: Instantiation * Inference
Sort[book] | (bookshelf) func ScaleAndPrint(p Point) {
r := Scale(p, 2)
pass type argument fmt.Println(r.String())
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{}") Why don't we need explicit type parameters?
* Sort, decomposed r := Scale[Point, int32](p, 2)
type Lesser[T any] interface{ * Inference
Less(y T) bool
}
func Sort[Elem Lesser[Elem]](list []Elem) - argument type inference
- constraint type inference
* 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 * Argument type inference
what we have func Scale[S ~[]E, E constraints.Integer](s S, c E) S
Sort[book](bookshelf) type Point []int32
what we want Scale(p, 2)
Sort(bookshelf) - p is []Point => S is Point
- 2 is untyped constant => no info
Type unification * Constraint type inference
bookshelf -> []book func Scale[S ~[]E, E constraints.Integer](s S, c E) S
Inference type Point []int32
func Sort[Elem ...]([]Elem) => Elem == book Scale(p, 2)
* Problems - S is Point (argument type inference)
- S is defined in terms of E => we can infer E
what we want - E is int32
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 * Different type parameters are different types

View file

@ -0,0 +1,8 @@
package basic
func min(x, y int) int {
if x < y {
return x
}
return y
}

View file

@ -0,0 +1,10 @@
package generic
import "golang.org/x/exp/constraints"
func min[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}

View file

@ -0,0 +1,11 @@
package wrong
import "golang.org/x/exp/constraints"
func Scale[S ~[]E, E constraints.Integer](s S, c E) S {
r := make(S, len(s))
for i, v := range s {
r[i] = v * c
}
return r
}

View file

@ -0,0 +1,11 @@
package wrong
import "golang.org/x/exp/constraints"
func Scale[E constraints.Integer](s []E, c E) []E {
r := make([]E, len(s))
for i, v := range s {
r[i] = v * c
}
return r
}