260 lines
5.3 KiB
Text
260 lines
5.3 KiB
Text
|
generics
|
||
|
Лекция 8
|
||
|
|
||
|
Арсений Балобанов
|
||
|
|
||
|
* Generics (draft)
|
||
|
|
||
|
* New language features
|
||
|
|
||
|
- Mechanism to parameterize a type or function by types.
|
||
|
- Constraints mechanism to express requirements on type parameters.
|
||
|
- Type inference (optional)
|
||
|
|
||
|
* 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
|
||
|
|
||
|
* 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.
|
||
|
|
||
|
* How to try?
|
||
|
|
||
|
.link https://go2goplay.golang.org/ - playground
|
||
|
.link https://go.googlesource.com/go/+/refs/heads/dev.go2go/README.go2go.md - dev branch
|
||
|
|
||
|
* Ссылки
|
||
|
|
||
|
.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
|