From ddf9df28dde079f2117dd975a00a8b1739634c83 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Thu, 7 Apr 2022 18:04:16 +0300 Subject: [PATCH] [lectures] Update generics slides. --- lectures/08-generics/lecture.slide | 299 +++++++++++----------- lectures/08-generics/min/basic/min.go | 8 + lectures/08-generics/min/generic/min.go | 10 + lectures/08-generics/scale/fixed/scale.go | 11 + lectures/08-generics/scale/wrong/scale.go | 11 + 5 files changed, 195 insertions(+), 144 deletions(-) create mode 100644 lectures/08-generics/min/basic/min.go create mode 100644 lectures/08-generics/min/generic/min.go create mode 100644 lectures/08-generics/scale/fixed/scale.go create mode 100644 lectures/08-generics/scale/wrong/scale.go diff --git a/lectures/08-generics/lecture.slide b/lectures/08-generics/lecture.slide index 8211d27..5e04dce 100644 --- a/lectures/08-generics/lecture.slide +++ b/lectures/08-generics/lecture.slide @@ -11,6 +11,29 @@ generics - Type sets - 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 An ordinary parameter list @@ -23,6 +46,95 @@ A type parameter list - 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 .play -edit min/basic/min.go /^func min/,/^}/ @@ -125,185 +237,84 @@ A type parameter list - Type intefence is complicated but usage is simple - 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 { - Len() int - Less(i, j int) bool - Swap(i, j int) +We'd like to write function scale and Println + + func ScaleAndPrint(p Point) { + 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 - Sort(myList) +* Scale, first attempt -* Type parameters to the rescue +.play -edit scale/wrong/scale.go /^func Scale/,/^}/ - 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) { - ... + func ScaleAndPrint(p Point) { + r := Scale(p, 2) + fmt.Println(r.String()) } -- 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 +- Compiler error: (type []uint32 has no field or method String) -* 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{...} - func (x book) Less(y book) bool {...} +vs - var bookshelf []book - ... - Sort[book](bookshelf) // generic function call + func Scale[E constraints.Integer](s []E, c E) []E -* Type-checking the Sort call: Instantiation +* Inference - 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 + func ScaleAndPrint(p Point) { + r := Scale(p, 2) + fmt.Println(r.String()) } -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{ - Less(y T) bool - } +* Inference - 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 +- constraint 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 - -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 - } +- S is Point (argument type inference) +- S is defined in terms of E => we can infer E +- E is int32 * Different type parameters are different types diff --git a/lectures/08-generics/min/basic/min.go b/lectures/08-generics/min/basic/min.go new file mode 100644 index 0000000..b1ae8b1 --- /dev/null +++ b/lectures/08-generics/min/basic/min.go @@ -0,0 +1,8 @@ +package basic + +func min(x, y int) int { + if x < y { + return x + } + return y +} diff --git a/lectures/08-generics/min/generic/min.go b/lectures/08-generics/min/generic/min.go new file mode 100644 index 0000000..b1bae95 --- /dev/null +++ b/lectures/08-generics/min/generic/min.go @@ -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 +} diff --git a/lectures/08-generics/scale/fixed/scale.go b/lectures/08-generics/scale/fixed/scale.go new file mode 100644 index 0000000..cf579e5 --- /dev/null +++ b/lectures/08-generics/scale/fixed/scale.go @@ -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 +} diff --git a/lectures/08-generics/scale/wrong/scale.go b/lectures/08-generics/scale/wrong/scale.go new file mode 100644 index 0000000..d22633e --- /dev/null +++ b/lectures/08-generics/scale/wrong/scale.go @@ -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 +}