[lectures] Update generics slides.
This commit is contained in:
parent
181a2cff69
commit
ddf9df28dd
5 changed files with 195 additions and 144 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
8
lectures/08-generics/min/basic/min.go
Normal file
8
lectures/08-generics/min/basic/min.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
func min(x, y int) int {
|
||||||
|
if x < y {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
10
lectures/08-generics/min/generic/min.go
Normal file
10
lectures/08-generics/min/generic/min.go
Normal 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
|
||||||
|
}
|
11
lectures/08-generics/scale/fixed/scale.go
Normal file
11
lectures/08-generics/scale/fixed/scale.go
Normal 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
|
||||||
|
}
|
11
lectures/08-generics/scale/wrong/scale.go
Normal file
11
lectures/08-generics/scale/wrong/scale.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue