| # Contracts — Draft Design | |
| Ian Lance Taylor\ | |
| Robert Griesemer\ | |
| August 27, 2018 | |
| ## Abstract | |
| We suggest extending the Go language to add optional type parameters | |
| to types and functions. | |
| Type parameters may be constrained by contracts: they may be used as | |
| ordinary types that only support the operations described by the | |
| contracts. | |
| Type inference via a unification algorithm is supported to permit | |
| omitting type arguments from function calls in many cases. | |
| Depending on a detail, the design can be fully backward compatible | |
| with Go 1. | |
| For more context, see the [generics problem overview](go2draft-generics-overview.md). | |
| ## Background | |
| There have been many [requests to add additional support for generic | |
| programming](https://golang.org/wiki/ExperienceReports#generics) | |
| in Go. | |
| There has been extensive discussion on | |
| [the issue tracker](https://golang.org/issue/15292) and on | |
| [a living document](https://docs.google.com/document/d/1vrAy9gMpMoS3uaVphB32uVXX4pi-HnNjkMEgyAHX4N4/view). | |
| There have been several proposals for adding type parameters, which | |
| can be found by looking through the links above. | |
| Many of the ideas presented here have appeared before. | |
| The main new features described here are the syntax and the careful | |
| examination of contracts. | |
| This draft design suggests extending the Go language to add a form of | |
| parametric polymorphism, where the type parameters are bounded not by | |
| a subtyping relationship but by explicitly defined structural | |
| constraints. | |
| Among other languages that support parametric polymorphism this | |
| design is perhaps most similar to CLU or Ada, although the syntax is | |
| completely different. | |
| Contracts also somewhat resemble C++ concepts. | |
| This design does not support template metaprogramming or any other | |
| form of compile-time programming. | |
| As the term _generic_ is widely used in the Go community, we will | |
| use it below as a shorthand to mean a function or type that takes type | |
| parameters. | |
| Don’t confuse the term generic as used in this design with the same | |
| term in other languages like C++, C#, Java, or Rust; they have | |
| similarities but are not always the same. | |
| ## Design | |
| We will describe the complete design in stages based on examples. | |
| ### Type parameters | |
| Generic code is code that is written using types that will be | |
| specified later. | |
| Each unspecified type is called a _type parameter_. | |
| When the code is used the type parameter is set to a _type argument_. | |
| Here is a function that prints out each element of a slice, where the | |
| element type of the slice, here called `T`, is unknown. | |
| This is a trivial example of the kind of function we want to permit in | |
| order to support generic programming. | |
| ```Go | |
| // Print prints the elements of a slice. | |
| // It should be possible to call this with any slice value. | |
| func Print(s []T) { // Just an example, not the suggested syntax. | |
| for _, v := range s { | |
| fmt.Println(v) | |
| } | |
| } | |
| ``` | |
| As you can see, the first decision to make is: how should the type | |
| parameter `T` be declared? | |
| In a language like Go, we expect every identifier to be declared in | |
| some way. | |
| Here we make a design decision: type parameters are similar to | |
| ordinary non-type function parameters, and as such should be listed | |
| along with other parameters. | |
| However, type parameters are not the same as non-type parameters, so | |
| although they appear in the parameters we want to distinguish them. | |
| That leads to our next design decision: we define an additional, | |
| optional, parameter list, describing type parameters. | |
| This parameter list appears before the regular parameters. | |
| It starts with the keyword `type` and lists type parameters. | |
| ```Go | |
| func Print(type T)(s []T) { | |
| // same as above | |
| } | |
| ``` | |
| This says that within the function `Print` the identifier `T` is a | |
| type parameter, a type that is currently unknown but that will be | |
| known when the function is called. | |
| Since `Print` has a type parameter, when we call it we must pass a | |
| type argument. | |
| Type arguments are passed much like type parameters are declared: as a | |
| separate list of arguments. | |
| At the call site, the `type` keyword is not used. | |
| ```Go | |
| Print(int)([]int{1, 2, 3}) | |
| ``` | |
| ### Type contracts | |
| Let’s make our example slightly more complicated. | |
| Let’s turn it into a function that converts a slice of any type into a | |
| `[]string` by calling a `String` method on each element. | |
| ```Go | |
| func Stringify(type T)(s []T) (ret []string) { | |
| for _, v := range s { | |
| ret = append(ret, v.String()) // INVALID | |
| } | |
| return ret | |
| } | |
| ``` | |
| This might seem OK at first glance, but in this example, `v` has type | |
| `T`, and we don’t know anything about `T`. | |
| In particular, we don’t know that `T` has a `String` method. | |
| So the call `v.String()` is invalid. | |
| Naturally, the same issue arises in other languages that support | |
| generic programming. | |
| In C++, for example, a generic function (in C++ terms, a function | |
| template) can call any method on a value of generic type. | |
| That is, in the C++ approach, calling `v.String()` is fine. | |
| If the function is called with a type that does not have a `String` | |
| method, the error is reported at the point of the function call. | |
| These errors can be lengthy, as there may be several layers of generic | |
| function calls before the error occurs, all of which must be reported | |
| for complete clarity. | |
| The C++ approach would be a poor choice for Go. | |
| One reason is the style of the language. | |
| In Go we don’t refer to names, such as, in this case, `String`, and | |
| hope that they exist. | |
| Go resolves all names to their declarations when they are seen. | |
| Another reason is that Go is designed to support programming at | |
| scale. | |
| We must consider the case in which the generic function definition | |
| (`Stringify`, above) and the call to the generic function (not shown, | |
| but perhaps in some other package) are far apart. | |
| In general, all generic code has a contract that type arguments need | |
| to implement. | |
| In this case, the contract is pretty obvious: the type has to have a | |
| `String() string` method. | |
| In other cases it may be much less obvious. | |
| We don’t want to derive the contract from whatever `Stringify` happens | |
| to do. | |
| If we did, a minor change to `Stringify` might change the contract. | |
| That would mean that a minor change could cause code far away, that | |
| calls the function, to unexpectedly break. | |
| It’s fine for `Stringify` to deliberately change its contract, and | |
| force users to change. | |
| What we want to avoid is `Stringify` changing its contract | |
| accidentally. | |
| This is an important rule that we believe should apply to any attempt | |
| to define generic programming in Go: there should be an explicit | |
| contract between the generic code and calling code. | |
| ### Contract syntax | |
| In this design, a contract has the same general form as a function. | |
| The function body is never executed. | |
| Instead, it describes, by example, a set of types. | |
| For the `Stringify` example, we need to write a contract that says | |
| that the type has a `String` method that takes no arguments and | |
| returns a value of type `string`. | |
| Here is one way to write that: | |
| ```Go | |
| contract stringer(x T) { | |
| var s string = x.String() | |
| } | |
| ``` | |
| A contract is introduced with a new keyword `contract`. | |
| The definition of a contract looks like the definition of a function, | |
| except that the parameter types must be simple identifiers. | |
| ### Using a contract to verify type arguments | |
| A contract serves two purposes. | |
| First, contracts are used to validate a set of type arguments. | |
| As shown above, when a function with type parameters is called, it | |
| will be called with a set of type arguments. | |
| When the compiler sees the function call, it will use the contract to | |
| validate the type arguments. | |
| If the type arguments are invalid, the compiler will report a type | |
| error: the call is using types that the function’s contract does not | |
| permit. | |
| To validate the type arguments, each of the contract’s parameter types | |
| is replaced with the corresponding type argument (there must be | |
| exactly as many type arguments as there are parameter types; contracts | |
| may not be variadic). | |
| The body of the contract is then type checked as though it were an | |
| ordinary function. | |
| If the type checking succeeds, the type arguments are valid. | |
| In the example of the `stringer` contract seen earlier, we can see | |
| that the type argument used for `T` must have a `String` method (or it | |
| must be a struct with a `String` field of function type). | |
| The `String` method must not take any arguments, and it must return a | |
| value of a type that is assignable to `string`. (As it happens, the | |
| only type assignable to `string` is, in fact, `string`.) | |
| If any of those statements about the type argument are not true, the | |
| contract body will fail when it is type checked. | |
| ### The party of the second part | |
| A contract is not used only at the call site. | |
| It is also used to describe what the function using the contract, the | |
| function with type parameters, is permitted to do with those type | |
| parameters. | |
| In a function with type parameters that does not use a contract, such | |
| as the `Print` example shown earlier, the function is only permitted | |
| to use those type parameters in ways that any type may be used in Go. | |
| That is, operations like: | |
| * declare variables of those types | |
| * assign other values of the same type to those variables | |
| * pass those variables to functions or return them from functions | |
| * take the address of those variables | |
| * define and use other types that use those types, such as a slice of | |
| that type | |
| If the function wants to take any more specific action with the type | |
| parameter, or a value of the type parameter, the contract must permit | |
| it. | |
| Basically, if the contract body uses a type in a certain way, the | |
| actual function is permitted to use the type in the same way. | |
| This is described in more detail later. | |
| For now, look at the `stringer` contract example above. | |
| The single statement in the contract body shows that given a value of | |
| type `T`, the function using the `stringer` contract is permitted to | |
| call a method of type `String`, passing no arguments, to get a value | |
| of type `string`. | |
| That is, naturally, exactly the operation that the `Stringify` | |
| function needs. | |
| ### Using a contract | |
| We’ve seen how the `stringer` contract can be used to verify that a | |
| type argument is suitable for the `Stringify` function, and we’ve seen | |
| how the contract permits the `Stringify` function to call the `String` | |
| method that it needs. | |
| The final step is showing how the `Stringify` function uses the | |
| `stringer` contract. | |
| This is done by naming the contract at the end of the list of type | |
| parameters. | |
| ```Go | |
| func Stringify(type T stringer)(s []T) (ret []string) { | |
| for _, v := range s { | |
| ret = append(ret, v.String()) // now valid | |
| } | |
| return ret | |
| } | |
| ``` | |
| The list of type parameters (in this case, a list of one element) is | |
| followed by an optional contract name. | |
| The contract must have the same number of parameters as the function | |
| has type parameters; when validating the contract, the type parameters | |
| are passed to the function in the order in which they appear in the | |
| function definition. | |
| ### Contract syntactic details | |
| Before we continue, let’s cover a few details of the contract syntax. | |
| #### Passing explicit types to a contract | |
| Although the normal case is for a function to validate the contract | |
| with its exact list of type parameters, the contract can also be used | |
| with a different set of types. | |
| For example, this simple contract says that a value of type `From` may | |
| be converted to the type `To`. | |
| ```Go | |
| contract convertible(_ To, f From) { | |
| To(f) | |
| } | |
| ``` | |
| Note that this contract body is quite simple. | |
| It is a single statement expression that consists of a conversion | |
| expression. | |
| Since the contract body is never executed, it doesn’t matter that the | |
| result of the conversion is not assigned to anything. | |
| All that matters is whether the conversion expressed can be type | |
| checked. | |
| For example, this contract would permit the type arguments `(int64, | |
| int32)` but would forbid the type arguments `([]int, complex64)`. | |
| Given this contract, we can write this function, which may be invoked | |
| with any type that can be converted to `uint64`. | |
| ```Go | |
| func FormatUnsigned(type T convertible(uint64, T))(v T) string { | |
| return strconv.FormatUint(uint64(v), 10) | |
| } | |
| ``` | |
| This could be called as, for example, | |
| ```Go | |
| s := FormatUnsigned(rune)('a') | |
| ``` | |
| This isn’t too useful with what we’ve described so far, but it will be | |
| a bit more convenient when we get to type inference. | |
| #### Restrictions on contract bodies | |
| Although a contract looks like a function body, contracts may not | |
| themselves have type parameters. | |
| Contracts may also not have result parameters, and it follows that | |
| they may not use a `return` statement to return values. | |
| The body of a contract may not refer to any name defined in the | |
| current package. | |
| This rule is intended to make it harder to accidentally change the | |
| meaning of a contract. | |
| As a compromise, a contract is permitted to refer to names imported | |
| from other packages, permitting a contract to easily say things like | |
| "this type must support the `io.Reader` interface:" | |
| ```Go | |
| contract Readable(r T) { | |
| io.Reader(r) | |
| } | |
| ``` | |
| It is likely that this rule will have to be adjusted as we gain more | |
| experience with this design. | |
| For example, perhaps we should permit contracts to refer to exported | |
| names defined in the same package, but not unexported names. | |
| Or maybe we should have no such restriction and just rely on | |
| correct programming supported by tooling. | |
| As contract bodies are not executed, there are no restrictions about | |
| unreachable statements, or `goto` statements across declarations, or | |
| anything along those lines. | |
| Of course, it is completely pointless to use a `goto` statement, or a | |
| `break`, `continue`, or `fallthrough` statement, in a contract body, | |
| as these statements do not say anything about the type arguments. | |
| #### The contract keyword | |
| Contracts may only appear at the top level of a package. | |
| While contracts could be defined to work within the body of a | |
| function, it’s hard to think of realistic examples in which they would | |
| be useful. | |
| We see this as similar to the way that methods can not be defined | |
| within the body of a function. | |
| A minor point is that only permitting contracts at the top level | |
| permits the design to be Go 1 compatible. | |
| There are a few ways to handle the syntax: | |
| * We could make `contract` be a keyword only at the start of a | |
| top-level declaration, and otherwise be a normal identifier. | |
| * We could declare that if you use `contract` at the start of a | |
| top-level declaration, then it becomes a keyword for the duration of | |
| that package. | |
| * We could make `contract` always be a keyword, albeit one that can | |
| only appear in one place, in which case this design is not Go 1 | |
| compatible. | |
| #### Exported contracts | |
| Like other top level declarations, a contract is exported if its name | |
| starts with an upper-case letter. | |
| An exported contract may be used by functions, types, or contracts in other | |
| packages. | |
| ### Multiple type parameters | |
| Although the examples shown so far only use a single type parameter, | |
| naturally functions may have multiple type parameters. | |
| ```Go | |
| func Print2(type T1, T2)(s1 []T1, s2 []T2) { ... } | |
| ``` | |
| Compare this to | |
| ```Go | |
| func Print2Same(type T1)(s1 []T1, s2 []T1) { ... } | |
| ``` | |
| In `Print2` `s1` and `s2` may be slices of different types. | |
| In `Print2Same` `s1` and `s2` must be slices of the same element | |
| type. | |
| Although functions may have multiple type parameters, they may only | |
| have a single contract. | |
| ```Go | |
| contract viaStrings(t To, f From) { | |
| var x string = f.String() | |
| t.Set(string("")) // could also use t.Set(x) | |
| } | |
| func SetViaStrings(type To, From viaStrings)(s []From) []To { | |
| r := make([]To, len(s)) | |
| for i, v := range s { | |
| r[i].Set(v.String()) | |
| } | |
| return r | |
| } | |
| ``` | |
| ### Parameterized types | |
| We want more than just generic functions: we also want generic types. | |
| We suggest that types be extended to take type parameters. | |
| ```Go | |
| type Vector(type Element) []Element | |
| ``` | |
| A type’s parameters are just like a function’s type parameters. | |
| Within the type definition, the type parameters may be used like any | |
| other type. | |
| To use a parameterized type, you must supply type arguments. | |
| This looks like a function call, except that the function in this case | |
| is actually a type. | |
| This is called _instantiation_. | |
| ```Go | |
| var v Vector(int) | |
| ``` | |
| Parameterized types can have methods. | |
| The receiver type of a method must list the type parameters. | |
| They are listed without the `type` keyword or any contract. | |
| ```Go | |
| func (v *Vector(Element)) Push(x Element) { *v = append(*v, x) } | |
| ``` | |
| A parameterized type can refer to itself in cases where a type can | |
| ordinarily refer to itself, but when it does so the type arguments | |
| must be the type parameters. | |
| This restriction avoids an infinite recursion of type instantiation. | |
| ```Go | |
| // This is OK. | |
| type List(type Element) struct { | |
| next *List(Element) | |
| val Element | |
| } | |
| // This is INVALID. | |
| type P(type Element1, Element2) struct { | |
| F *P(Element2, Element1) // INVALID; must be (Element1, Element2) | |
| } | |
| ``` | |
| (Note: with more understanding of how people want to write code, it | |
| may be possible to relax the reference rule to permit some cases that | |
| use different type arguments.) | |
| When a parameterized type is a struct, and the type parameter is | |
| embedded as a field in the struct, the name of the field is the name | |
| of the type parameter, not the name of the type argument. | |
| ```Go | |
| type Lockable(type T) struct { | |
| T | |
| mu sync.Mutex | |
| } | |
| func (l *Lockable(T)) Get() T { | |
| l.mu.Lock() | |
| defer l.mu.Unlock() | |
| return l.T | |
| } | |
| ``` | |
| (Note: this works poorly if you write `Lockable(X)` in the method | |
| declaration: should the method return `l.T` or `l.X`? | |
| Perhaps we should simply ban embedding a type parameter in a struct.) | |
| ### Parameterized type aliases | |
| Type aliases may have parameters. | |
| ```Go | |
| type Ptr(type Target) = *Target | |
| ``` | |
| Type aliases may refer to parameterized types, in which case any uses | |
| of the type alias (other than in another type alias declaration) must | |
| provide type arguments. | |
| ```Go | |
| type V = Vector | |
| var v2 V(int) | |
| ``` | |
| Type aliases may refer to instantiated types. | |
| ```Go | |
| type VectorInt = Vector(int) | |
| ``` | |
| ### Methods may not take additional type arguments | |
| Although methods of a parameterized type may use the type’s | |
| parameters, methods may not themselves have (additional) type | |
| parameters. | |
| Where it would be useful to add type arguments to a method, people | |
| will have to write a top-level function. | |
| Making this decision avoids having to specify the details of exactly | |
| when a method with type arguments implements an interface. | |
| (This is a feature that can perhaps be added later if it proves | |
| necessary.) | |
| ### Contract embedding | |
| A contract may embed another contract, by listing it in the | |
| contract body with type arguments. | |
| This will look like a function call in the contract body, but since | |
| the call is to a contract it is handled as if the called contract’s | |
| body were embedded in the calling contract, with the called contract’s | |
| type parameters replaced by the type arguments provided in the | |
| contract call. | |
| This contract embeds the contract `stringer` defined earlier. | |
| ```Go | |
| contract PrintStringer(x X) { | |
| stringer(X) | |
| x.Print() | |
| } | |
| ``` | |
| This is roughly equivalent to | |
| ```Go | |
| contract PrintStringer(x X) { | |
| var s string = x.String() | |
| x.Print() | |
| } | |
| ``` | |
| It’s not exactly equivalent: the contract can’t refer to the variable | |
| `s` after embedding `stringer(X)`. | |
| ### Using types that refer to themselves in contracts | |
| Although this is implied by what has already been discussed, it’s | |
| worth pointing out explicitly that a contract may require a method to | |
| have an argument whose type is one of the contract’s type parameters. | |
| ```Go | |
| package comparable | |
| // The equal contract describes types that have an Equal method for | |
| // the same type. | |
| contract equal(v T) { | |
| // All that matters is type checking, so reusing v as the argument | |
| // means that the type argument must have a Equal method such that | |
| // the type argument itself is assignable to the Equal method’s | |
| // parameter type. | |
| var x bool = v.Equal(v) | |
| } | |
| // Index returns the index of e in s, or -1. | |
| func Index(type T equal)(s []T, e T) int { | |
| for i, v := range s { | |
| // Both e and v are type T, so it’s OK to call e.Equal(v). | |
| if e.Equal(v) { | |
| return i | |
| } | |
| } | |
| return -1 | |
| } | |
| ``` | |
| This function can be used with any type that has an `Equal` method | |
| whose single parameter type is the type itself. | |
| ```Go | |
| import "comparable" | |
| type EqualInt int | |
| // The Equal method lets EqualInt implement the comparable.equal contract. | |
| func (a EqualInt) Equal(b EqualInt) bool { return a == b } | |
| func Index(s []EqualInt, e EqualInt) int { | |
| return comparable.Index(EqualInt)(s, e) | |
| } | |
| ``` | |
| In this example, when we pass `EqualInt` to `comparable.Index`, we | |
| check whether `EqualInt` satisfies the contract `comparable.equal`. | |
| We replace `T` in the body of `comparable.equal` with `EqualInt`, and | |
| see whether the result type checks. | |
| `EqualInt` has a method `Equal` that accepts a parameter of type | |
| `EqualInt`, so all is well, and the compilation succeeds. | |
| ### Mutually referential type parameters | |
| Within a contract body, expressions may arbitrarily combine values of | |
| any type parameter. | |
| For example, consider a generic graph package that contains generic | |
| algorithms that work with graphs. | |
| The algorithms use two types, `Node` and `Edge`. | |
| `Node` is expected to have a method `Edges() []Edge`. | |
| `Edge` is expected to have a method `Nodes() (Node, Node)`. | |
| A graph can be represented as a `[]Node`. | |
| This simple representation is enough to implement graph algorithms | |
| like finding the shortest path. | |
| ```Go | |
| package graph | |
| contract G(n Node, e Edge) { | |
| var _ []Edge = n.Edges() | |
| var from, to Node = e.Nodes() | |
| } | |
| type Graph(type Node, Edge G) struct { ... } | |
| func New(type Node, Edge G)(nodes []Node) *Graph(Node, Edge) { ... } | |
| func (*Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { ... } | |
| ``` | |
| At first glance it might be hard to see how this differs from similar | |
| code using interface types. | |
| The difference is that although `Node` and `Edge` have specific | |
| methods, they are not interface types. | |
| In order to use `graph.Graph`, the type arguments used for `Node` and | |
| `Edge` have to define methods that follow a certain pattern, but they | |
| don’t have to actually use interface types to do so. | |
| For example, consider these type definitions in some other package: | |
| ```Go | |
| type Vertex struct { ... } | |
| func (v *Vertex) Edges() []*FromTo { ... } | |
| type FromTo struct { ... } | |
| type (ft *FromTo) Nodes() (*Vertex, *Vertex) { ... } | |
| ``` | |
| There are no interface types here, but we can instantiate | |
| `graph.Graph` using the type arguments `*Vertex` and `*FromTo`: | |
| ```Go | |
| var g = graph.New(*Vertex, *FromTo)([]*Vertex{ ... }) | |
| ``` | |
| `*Vertex` and `*FromTo` are not interface types, but when used | |
| together they define methods that implement the contract `graph.G`. | |
| Because of the way that the contract is written, we could also use the | |
| non-pointer types `Vertex` and `FromTo`; the contract implies that the | |
| function body will always be able to take the address of the argument | |
| if necessary, and so will always be able to call the pointer method. | |
| Although `Node` and `Edge` do not have to be instantiated with | |
| interface types, it is also OK to use interface types if you like. | |
| ```Go | |
| type NodeInterface interface { Edges() []EdgeInterface } | |
| type EdgeInterface interface { Nodes() (NodeInterface, NodeInterface) } | |
| ``` | |
| We could instantiate `graph.Graph` with the types `NodeInterface` and | |
| `EdgeInterface`, since they implement the `graph.G` contract. | |
| There isn’t much reason to instantiate a type this way, but it is | |
| permitted. | |
| This ability for type parameters to refer to other type parameters | |
| illustrates an important point: it should be a requirement for any | |
| attempt to add generics to Go that it be possible to instantiate | |
| generic code with multiple type arguments that refer to each other in | |
| ways that the compiler can check. | |
| ### Values of type parameters are not boxed | |
| In the current implementations of Go, interface values always hold | |
| pointers. | |
| Putting a non-pointer value in an interface variable causes the value | |
| to be _boxed_. | |
| That means that the actual value is stored somewhere else, on the heap | |
| or stack, and the interface value holds a pointer to that location. | |
| In contrast to interface values, values of instantiated polymorphic types are not boxed. | |
| For example, let’s consider a function that works for any type `T` | |
| with a `Set(string)` method that initializes the value based on a | |
| string, and uses it to convert a slice of `string` to a slice of `T`. | |
| ```Go | |
| package from | |
| contract setter(x T) { | |
| var _ error = x.Set(string) | |
| } | |
| func Strings(type T setter)(s []string) ([]T, error) { | |
| ret := make([]T, len(s)) | |
| for i, v := range s { | |
| if err := ret[i].Set(v); err != nil { | |
| return nil, err | |
| } | |
| } | |
| return ret, nil | |
| } | |
| ``` | |
| Now let’s see some code in a different package. | |
| ```Go | |
| type Settable int | |
| func (p *Settable) Set(s string) (err error) { | |
| *p, err = strconv.Atoi(s) | |
| return err | |
| } | |
| func F() { | |
| // The type of nums is []Settable. | |
| nums, err := from.Strings(Settable)([]string{"1", "2"}) | |
| // Settable can be converted directly to int. | |
| // This will set first to 1. | |
| first := int(nums[0]) | |
| ... | |
| } | |
| ``` | |
| When we call `from.Strings` with the type `Settable` we get back a | |
| `[]Settable` (and an error). | |
| The values in that slice will be `Settable` values, which is to say, | |
| they will be integers. | |
| They will not be boxed as pointers, even though they were created and | |
| set by a generic function. | |
| Similarly, when a parameterized type is instantiated it will have the | |
| expected types as fields. | |
| ```Go | |
| package pair | |
| type Pair(type carT, cdrT) struct { | |
| f1 carT | |
| f2 cdrT | |
| } | |
| ``` | |
| When this is instantiated, the fields will not be boxed, and no | |
| unexpected memory allocations will occur. | |
| The type `pair.Pair(int, string)` is convertible to `struct { f1 int; | |
| f2 string }`. | |
| ### Function argument type inference | |
| In many cases, when calling a function with type parameters, we can | |
| use type inference to avoid having to explicitly write out the type | |
| arguments. | |
| Go back to the example of a call to our simple `Print` function: | |
| ```Go | |
| Print(int)([]int{1, 2, 3}) | |
| ``` | |
| The type argument `int` in the function call can be inferred from the | |
| type of the non-type argument. | |
| This can only be done when all the function’s type parameters are used | |
| for the types of the function’s (non-type) input parameters. | |
| If there are some type parameters that are used only for the | |
| function’s result parameter types, or only in the body of the | |
| function, then it is not possible to infer the type arguments for the | |
| function. | |
| For example, when calling `from.Strings` as defined earlier, the type | |
| parameters cannot be inferred because the function’s type parameter | |
| `T` is not used for an input parameter, only for a result. | |
| When the function’s type arguments can be inferred, the language uses | |
| type unification. | |
| On the caller side we have the list of types of the actual (non-type) | |
| arguments, which for the `Print` example here is simply `[]int`. | |
| On the function side is the list of the types of the function’s | |
| non-type parameters, which here is `[]T`. | |
| In the lists, we discard arguments for which the function side does | |
| not use a type parameter. | |
| We must then unify the remaining argument types. | |
| Type unification is a two pass algorithm. | |
| In the first pass, untyped constants on the caller side, and their | |
| corresponding types in the function definition, are ignored. | |
| Corresponding types in the lists are compared. | |
| Their structure must be identical, except that type parameters on the | |
| function side match the type that appears on the caller side at the | |
| point where the type parameter occurs. | |
| If the same type parameter appears more than once on the function | |
| side, it will match multiple argument types on the caller side. | |
| Those caller types must be identical, or type unification fails, and | |
| we report an error. | |
| After the first pass, check any untyped constants on the caller side. | |
| If there are no untyped constants, or if the type parameters in the | |
| corresponding function types have matched other input types, then | |
| type unification is complete. | |
| Otherwise, for the second pass, for any untyped constants whose | |
| corresponding function types are not yet set, determine the default | |
| type of the untyped constant in [the usual | |
| way](https://golang.org/ref/spec#Constants). | |
| Then run the type unification algorithm again, this time with no | |
| untyped constants. | |
| In this example | |
| ```Go | |
| s1 := []int{1, 2, 3} | |
| Print(s1) | |
| ``` | |
| we compare `[]int` with `[]T`, match `T` with `int`, and we are done. | |
| The single type parameter `T` is `int`, so we infer that the call | |
| to `Print` is really a call to `Print(int)`. | |
| For a more complex example, consider | |
| ```Go | |
| package transform | |
| func Slice(type From, To)(s []From, f func(From) To) []To { | |
| r := make([]To, len(s)) | |
| for i, v := range s { | |
| r[i] = f(v) | |
| } | |
| return r | |
| } | |
| ``` | |
| The two type parameters `From` and `To` are both used for input | |
| parameters, so type inference is possible. | |
| In the call | |
| ```Go | |
| strs := transform.Slice([]int{1, 2, 3}, strconv.Itoa) | |
| ``` | |
| we unify `[]int` with `[]From`, matching `From` with `int`. | |
| We unify the type of `strconv.Itoa`, which is `func(int) string`, | |
| with `func(From) To`, matching `From` with `int` and `To` with | |
| `string`. | |
| `From` is matched twice, both times with `int`. | |
| Unification succeeds, changing the call from `transform.Slice` to | |
| `transform.Slice(int, string)`. | |
| To see the untyped constant rule in effect, consider | |
| ```Go | |
| package pair | |
| func New(type T)(f1, f2 T) *Pair(T) { ... } | |
| ``` | |
| In the call `pair.New(1, 2)` both arguments are untyped constants, so | |
| both are ignored in the first pass. | |
| There is nothing to unify. | |
| We still have two untyped constants after the first pass. | |
| Both are set to their default type, `int`. | |
| The second run of the type unification pass unifies `T` with `int`, | |
| so the final call is `pair.New(int)(1, 2)`. | |
| In the call `pair.New(1, int64(2))` the first argument is an untyped | |
| constant, so we ignore it in the first pass. | |
| We then unify `int64` with `T`. | |
| At this point the type parameter corresponding to the untyped constant | |
| is fully determined, so the final call is `pair.New(int64)(1, int64(2))`. | |
| In the call `pair.New(1, 2.5)` both arguments are untyped constants, | |
| so we move on the second pass. | |
| This time we set the first constant to `int` and the second to | |
| `float64`. | |
| We then try to unify `T` with both `int` and `float64`, so | |
| unification fails, and we report a compilation error. | |
| Note that type inference is done without regard to contracts. | |
| First we use type inference to determine the type arguments to use for | |
| the package, and then, if that succeeds, we check whether those type | |
| arguments implement the contract. | |
| Note that after successful type inference, the compiler must still | |
| check that the arguments can be assigned to the parameters, as for any | |
| function call. | |
| This need not be the case when untyped constants are involved. | |
| (Note: Type inference is a convenience feature. | |
| Although we think it is an important feature, it does not add any | |
| functionality to the design, only convenience in using it. | |
| It would be possible to omit it from the initial implementation, and | |
| see whether it seems to be needed. | |
| That said, this feature doesn’t require additional syntax, and is | |
| likely to significantly reduce the stutter of repeated type arguments | |
| in code.) | |
| (Note: We could also consider supporting type inference in composite | |
| literals. | |
| ```Go | |
| type Pair(type T) struct { f1, f2 T } | |
| var V = Pair{1, 2} // inferred as Pair(int){1, 2} | |
| ``` | |
| It’s not clear how often this will arise in real code.) | |
| ### Instantiating a function | |
| Go normally permits you to refer to a function without passing any | |
| arguments, producing a value of function type. | |
| You may not do this with a function that has type parameters; all type | |
| arguments must be known at compile time. | |
| However, you can instantiate the function, by passing type arguments, | |
| without passing any non-type arguments. | |
| This will produce an ordinary function value with no type parameters. | |
| ```Go | |
| // PrintInts will have type func([]int). | |
| var PrintInts = Print(int) | |
| ``` | |
| ### Type assertions and switches | |
| A useful function with type parameters will support any type argument | |
| that implements the contract. | |
| Sometimes, though, it’s possible to use a more efficient | |
| implementation for some type arguments. | |
| The language already has mechanisms for code to find out what type it | |
| is working with: type assertions and type switches. | |
| Those are normally only permitted with interface types. | |
| In this design, functions are also permitted to use them with values | |
| whose types are type parameters, or based on type parameters. | |
| This doesn’t add any functionality, as the function could get the same | |
| information using the reflect package. | |
| It’s merely occasionally convenient, and it may result in more | |
| efficient code. | |
| For example, this code is permitted even if it is called with a type | |
| argument that is not an interface type. | |
| ```Go | |
| contract byteReader(x T) { | |
| // This expression says that x is convertible to io.Reader, or, | |
| // in other words, that x has a method Read([]byte) (int, error). | |
| io.Reader(x) | |
| } | |
| func ReadByte(type T byteReader)(r T) (byte, error) { | |
| if br, ok := r.(io.ByteReader); ok { | |
| return br.ReadByte() | |
| } | |
| var b [1]byte | |
| _, err := r.Read(b[:]) | |
| return b[0], err | |
| } | |
| ``` | |
| ### Instantiating types in type literals | |
| When instantiating a type at the end of a type literal, there is a | |
| parsing ambiguity. | |
| ```Go | |
| x1 := []T(v1) | |
| x2 := []T(v2){} | |
| ``` | |
| In this example, the first case is a type conversion of `v1` to the | |
| type `[]T`. | |
| The second case is a composite literal of type `[]T(v2)`, where `T` is | |
| a parameterized type that we are instantiating with the type argument | |
| `v2`. | |
| The ambiguity is at the point where we see the parenthesis: at that | |
| point the parser doesn’t know whether it is seeing a type conversion | |
| or something like a composite literal. | |
| To avoid this ambiguity, we require that type instantiations at the | |
| end of a type literal be parenthesized. | |
| In other words, we always parse `[]T(v1)` as a type conversion, not as | |
| a potential instantiation of `T`. | |
| To write a type literal that is a slice of a type instantiation, you | |
| must write `[](T(v1))`. | |
| This only applies to slice, array, map, chan, and func type literals | |
| ending in a type name. | |
| Of course it is always possible to use a separate type declaration to | |
| give a name to the instantiated type, and to use that. | |
| This is only an issue when the type is instantiated in place. | |
| ### Reflection | |
| We do not propose to change the reflect package in any way. | |
| When a type or function is instantiated, all of the type parameters | |
| will become ordinary non-generic types. | |
| The `String` method of a `reflect.Type` value of an instantiated type | |
| will return the name with the type arguments in parentheses. | |
| For example, `List(int)`. | |
| It’s impossible for non-generic code to refer to generic code without | |
| instantiating it, so there is no reflection information for | |
| uninstantiated generic types or functions. | |
| ### Contracts details | |
| Let’s take a deeper look at contracts. | |
| Operations on values whose type is a type parameter must be permitted | |
| by the type parameter’s contract. | |
| This means that the power of generic functions is tied precisely to | |
| the interpretation of the contract body. | |
| It also means that the language requires a precise definition of the | |
| operations that are permitted by a given contract. | |
| The general guideline is straightforward: if a statement appears in a | |
| contract, then that same statement may appear in a function using that | |
| contract. | |
| However, that guideline is clearly too limiting; it essentially | |
| requires that the function body be copied into the contract body, | |
| which makes the contract pointless. | |
| Therefore, what needs to be clearly spelled out is the ways in which a | |
| statement in a contract body can permit other kinds of expressions or | |
| statements in the function body. | |
| We don’t need to explain the meaning of every statement that can | |
| appear in a contract body, only the ones that permit operations other | |
| than an exact copy of the statement. | |
| #### Methods | |
| All the contracts we’ve seen so far show only method calls and type | |
| conversions in the contract body. | |
| If a method call appears in the contract body, that method may be | |
| called on an addressable value in any statement or expression in the | |
| function body. | |
| It will take argument and result types as shown in the contract body. | |
| The examples above use `var` declarations to specify the types of the | |
| result parameters. | |
| While it is valid to use a short declaration like `s := x.String()` in | |
| a contract body, such a declaration says nothing about the result | |
| type. | |
| This would match a type argument with a `String` method that returns a | |
| single result of any type. | |
| It would not permit the function using the contract to use the result | |
| of `x.String()`, since the type would not be known. | |
| There are a few aspects to a method call that can not be shown in a | |
| simple assignment statement like the ones shown above. | |
| * There is no way to specify that a method does not return any | |
| values. | |
| * There is no way to specify that a method takes variadic arguments. | |
| * There is no way to distinguish a method call from a call of a struct | |
| field with function type. | |
| When a contract needs to describe one of these cases, it can use a | |
| type conversion to an interface type. | |
| The interface type permits the method to be precisely described. | |
| If the conversion to the interface type passes the type checker, then | |
| the type argument must have a method of that exact type. | |
| An explicit method call, or a conversion to an interface type, can not | |
| be used to distinguish a pointer method from a value method. | |
| When the function body calls a method on an addressable value, this | |
| doesn’t matter; since all value methods are part of the pointer type’s | |
| method set, an addressable value can call either pointer methods or | |
| value methods. | |
| However, it is possible to write a function body that can only call | |
| a value method, not a pointer method. For example: | |
| ```Go | |
| contract adjustable(x T) { | |
| var _ T = x.Adjust() | |
| x.Apply() | |
| } | |
| func Apply(type T adjustable)(v T) { | |
| v.Adjust().Apply() // INVALID | |
| } | |
| ``` | |
| In this example, the `Apply` method is not called on an addressable | |
| value. | |
| This can only work if the `Apply` method is a value method. | |
| But writing `x.Apply()` in the contract permits a pointer method. | |
| In order to use a value method in the function body, the contract must | |
| express that the type has a value method rather than a pointer | |
| method. | |
| That can be done like this: | |
| ```Go | |
| contract adjustable(x T) { | |
| var _ T = x.Adjust() | |
| var f func() T | |
| f().Apply() | |
| } | |
| ``` | |
| The rule is that if the contract body contains a method call on a | |
| non-addressable value, then the function body may call the method on a | |
| non-addressable value. | |
| #### Operators | |
| Method calls are not sufficient for everything we want to express. | |
| Consider this simple function that reports whether a parameterized | |
| slice contains an element. | |
| ```Go | |
| func Contains(type T)(s []T, e T) bool { | |
| for _, v := range s { | |
| if v == e { // INVALID | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| ``` | |
| Any reasonable generics implementation should let you write this | |
| function. | |
| The problem is the expression `v == e`. | |
| That assumes that `T` supports the `==` operator, but there is no | |
| contract requiring that. | |
| Without a contract the function body can only use operations that are | |
| available for all types, but not all Go types support `==` (you can | |
| not use `==` to compare values of slice, map, or function type). | |
| This is easy to address using a contract. | |
| ```Go | |
| contract comparable(x T) { | |
| x == x | |
| } | |
| func Contains(type T comparable)(s []T, e T) bool { | |
| for _, v := range s { | |
| if v == e { // now valid | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| ``` | |
| In general, using an operator in the contract body permits using the | |
| same operator with the same types anywhere in the function body. | |
| For convenience, some operators also permit additional operations. | |
| Using a binary operator in the contract body permits not only using | |
| that operator by itself, but also the assignment version with `=` | |
| appended if that exists. | |
| That is, an expression like `x * x` in a contract body means that | |
| generic code, given variables `a` and `b` of the type of `x`, may | |
| write `a * b` and may also write `a *= b`. | |
| Using the `==` operator with values of some type as both the left and | |
| right operands means that the type must be comparable, and implies | |
| that both `==` and `!=` may be used with values of that type. | |
| Similarly, `!=` permits `==`. | |
| Using the `<` operator with values of some type as both the left and | |
| right operators means that the type must ordered, and implies that all | |
| of `==`, `!=`, `<`, `<=`, `>=`, and `>` may be used with values of | |
| that type. | |
| Similarly for `<=`, `>=`, and `>`. | |
| These additional operations permit a little more freedom when writing | |
| the body of a function with type parameters: one can convert from `a = | |
| a * b` to `a *= b`, or make the other changes listed above, without | |
| having to modify the contract. | |
| #### Type conversions | |
| As already shown, the contract body may contain type conversions. | |
| A type conversion in the contract body means that the function body | |
| may use the same type conversion in any expression. | |
| Here is an example that implements a checked conversion between | |
| numeric types: | |
| ```Go | |
| package check | |
| contract convert(t To, f From) { | |
| To(f) | |
| From(t) | |
| f == f | |
| } | |
| func Convert(type To, From convert)(from From) To { | |
| to := To(from) | |
| if From(to) != from { | |
| panic("conversion out of range") | |
| } | |
| return to | |
| } | |
| ``` | |
| Note that the contract needs to explicitly permit both converting `To` | |
| to `From` and converting `From` to `To`. | |
| The ability to convert one way doesn’t necessarily imply being able to | |
| convert the other way; consider `check.Convert(int, interface{})(0, 0)`. | |
| #### Untyped constants | |
| Some functions are most naturally written using untyped constants. | |
| The contract body needs ways to say that it is possible to convert an | |
| untyped constant to some type. | |
| This is most naturally written as an assignment from an untyped | |
| constant. | |
| ```Go | |
| contract untyped(x T) { | |
| x = 0 | |
| } | |
| ``` | |
| A contract of this form must be used in order to write code like `var | |
| v T = 0`. | |
| If a contract body has assignments with string (`x = ""`) or bool (`x | |
| = false`) untyped constants, the function body is permitted to use any | |
| untyped `string` or `bool` constant, respectively, with values of the | |
| type. | |
| For numeric types, the use of a single untyped constant only permits | |
| using the exact specified value. | |
| Using two untyped constant assignments for a type permits using those | |
| constants and any value in between. | |
| For complex untyped constants, the real and imaginary values may vary | |
| to any values between the two constants. | |
| Here is an example that adds 1000 to each element of a slice. | |
| If the contract did not say `x = 1000`, the expression `v + 1000` would be | |
| invalid. | |
| ```Go | |
| contract add1K(x T) { | |
| x = 1000 | |
| x + x | |
| } | |
| func Add1K(type T add1K)(s []T) { | |
| for i, v := range s { | |
| s[i] = v + 1000 | |
| } | |
| } | |
| ``` | |
| These untyped constant rules are not strictly required. | |
| A type conversion expression such as `T(int)` permits converting any | |
| `int` value to the type `T`, so it would permit code like `var x T = | |
| T(1000)`. | |
| What the untyped constant expressions permit is `var x T = 1000`, | |
| without the explicit type conversion. | |
| (Note that `int8` satisfies the `untyped` contract but not `add1K`, | |
| since 1000 is out of range for `int8`.) | |
| #### Booleans | |
| In order to use a value of a type parameter as a condition in an `if` | |
| or `for` statement, write an `if` or `for` statement in the contract | |
| body: `if T {}` or `for T {}`. | |
| This is only useful to instantiate a type parameter with a named | |
| boolean type, and as such is unlikely to arise much in practice. | |
| #### Sequences | |
| Some simple operations in the contract body make it easier for the | |
| function body to work on various sorts of sequences. | |
| Using an index expression `x[y]` in a contract body permits using an | |
| index expression with those types anywhere in the function body. | |
| For anything other than a map type, the type of `y` will normally be | |
| `int`; that case may also be written as `x[0]`. | |
| Naturally, `z = x[y]` permits an index expression yielding the | |
| specified type. | |
| A statement like `x[y] = z` in the contract body permits the function | |
| body to assign to an index element using the given types. | |
| Again `x[0] = z` permits assignment using any `int` index. | |
| A statement like `for x, y = range z {}` in the contract body permits | |
| using either a one-element or a two-element `range` clause in a `for` | |
| statement in the function body. | |
| Using an expression like `len(x)` or `cap(x)` in the contract body | |
| permits those builtin functions to be used with values of that type | |
| anywhere in the function body. | |
| Using contracts of this sort can permit operations on generic sequence | |
| types. | |
| For example, here is a version of `Join` that may be instantiated with | |
| either `[]byte` or `string`. | |
| This example is imperfect in that in the `string` case it will do some | |
| unnecessary conversions to `[]byte` in order to call `append` and | |
| `copy`, but perhaps the compiler can eliminate those. | |
| ```Go | |
| contract strseq(x T) { | |
| []byte(x) | |
| T([]byte{}) | |
| len(x) | |
| } | |
| func Join(type T strseq)(a []T, sep T) (ret T) { | |
| if len(a) == 0 { | |
| // Use the result parameter as a zero value; | |
| // see discussion of zero value below. | |
| return ret | |
| } | |
| if len(a) == 1 { | |
| return T(append([]byte(nil), []byte(a[0])...)) | |
| } | |
| n := len(sep) * (len(a) - 1) | |
| for i := 0; i < len(a); i++ { | |
| n += len(a[i]) | |
| } | |
| b := make([]byte, n) | |
| bp := copy(b, []byte(a[0])) | |
| for _, s := range a[1:] { | |
| bp += copy(b[bp:], []byte(sep)) | |
| bp += copy(b[bp:], []byte(s)) | |
| } | |
| return T(b) | |
| } | |
| ``` | |
| #### Fields | |
| Using `x.f` in a contract body permits referring to the field in any | |
| expression in the function body. | |
| The contract body can use `var y = x.f` to describe the field’s type. | |
| ```Go | |
| package move | |
| contract counter(x T) { | |
| var _ int = x.Count | |
| } | |
| contract counters(T1, T2) { // as with a func, parameter names may be omitted. | |
| // Use contract embedding to say that both types must have a | |
| // Count field of type int. | |
| counter(T1) | |
| counter(T2) | |
| } | |
| func Corresponding(type T1, T2 counters)(p1 *T1, p2 *T2) { | |
| p1.Count = p2.Count | |
| } | |
| ``` | |
| The function `move.Corresponding` will copy the `Count` field from one | |
| struct to the other. | |
| The structs may be entirely different types, as long as they both have | |
| a `Count` field with type `int`. | |
| A field reference in a contract body also permits using a keyed | |
| composite literal in the function body, as in `T1{Count: 0}`. | |
| #### Impossible contracts | |
| It is possible to write a contract body that cannot be implemented by | |
| any Go type. | |
| This is not forbidden. | |
| An error will be reported not when the contract or function or type is | |
| compiled, but on any attempt to instantiate it. | |
| This eliminates the need for the language spec to provide an | |
| exhaustive set of rules describing when a contract body cannot be | |
| satisfied. | |
| It may be appropriate to add a vet check for this, if possible. | |
| ### Implementation | |
| Russ Cox [famously observed](https://research.swtch.com/generic) that | |
| generics require choosing among slow programmers, slow compilers, or | |
| slow execution times. | |
| We believe that this design permits different implementation choices. | |
| Code may be compiled separately for each set of type arguments, or it | |
| may be compiled as though each type argument is handled similarly to | |
| an interface type with method calls, or there may be some combination | |
| of the two. | |
| In other words, this design permits people to stop choosing slow | |
| programmers, and permits the implementation to decide between slow | |
| compilers (compile each set of type arguments separately) or slow | |
| execution times (use method calls for each operation on a value of a | |
| type argument). | |
| ### Summary | |
| While this design is long and detailed, it reduces to a few major | |
| points. | |
| * Functions and types can have type parameters, which are defined | |
| using optional contracts. | |
| * Contracts describe the operations permitted for a type parameter | |
| and required for a type argument. | |
| * Type inference can sometimes permit omitting type arguments when | |
| calling functions with type parameters. | |
| This design is completely backward compatible, in that any valid Go 1 | |
| program will still be valid if this design is adopted (assuming | |
| `contract` is treated as a pseudo-keyword that is only meaningful at | |
| top level). | |
| We believe that this design addresses people’s needs for generic | |
| programming in Go, without making the language any more complex than | |
| necessary. | |
| We can’t truly know the impact on the language without years of | |
| experience with this design. | |
| That said, here are some speculations. | |
| #### Complexity | |
| One of the great aspects of Go is its simplicity. | |
| Clearly this design makes the language more complex. | |
| We believe that the increased complexity is minor for people reading | |
| generic code, rather than writing it. | |
| Naturally people must learn the new syntax for declaring type | |
| parameters. | |
| The code within a generic function reads like ordinary Go code, as can | |
| be seen in the examples below. | |
| It is an easy shift to go from `[]int` to `[]T`. | |
| Type parameter contracts serve effectively as documentation, | |
| describing the type. | |
| We expect that most people will not write generic code themselves, but | |
| many people are likely to write packages that use generic code written | |
| by others. | |
| In the common case, generic functions work exactly like non-generic | |
| functions: you simply call them. | |
| Type inference means that you do not have to write out the type | |
| arguments explicitly. | |
| The type inference rules are designed to be unsurprising: either the | |
| type arguments are deduced correctly, or the call fails and requires | |
| explicit type parameters. | |
| Type inference uses type identity, with no attempt to resolve two | |
| types that are similar but not identical, which removes significant | |
| complexity. | |
| People using generic types will have to pass explicit type arguments. | |
| The syntax for this is familiar. | |
| The only change is passing arguments to types rather than only to | |
| functions. | |
| For the minority of people writing generic packages, we expect that | |
| the most complicated part will be writing correct contract bodies. | |
| Good compiler error messages will be essential, and they seem entirely | |
| feasible. | |
| We can’t deny the additional complexity here, but we believe that the | |
| design avoids confusing cases and provides the facilities that | |
| people need to write whatever generic code is desired. | |
| In general, we have tried to avoid surprises in the design. | |
| Only time will tell whether we succeeded. | |
| #### Pervasiveness | |
| We expect that a few new packages will be added to the standard | |
| library. | |
| A new `slices` packages will be similar to the existing bytes and | |
| strings packages, operating on slices of any element type. | |
| New `maps` and `chans` packages will provide simple algorithms that | |
| are currently duplicated for each element type. | |
| A `set` package may be added. | |
| Packages like `container/list` and `container/ring`, and types like | |
| `sync.Map`, will be updated to be compile-time type-safe. | |
| The `math` package will be extended to provide a set of simple | |
| standard algorithms for all numeric types, such as the ever popular | |
| `Min` and `Max` functions. | |
| It is likely that new special purpose compile-time type-safe container | |
| types will be developed, and some may become widely used. | |
| We do not expect approaches like the C++ STL iterator types to become | |
| widely used. | |
| In Go that sort of idea is more naturally expressed using an interface | |
| type. | |
| In C++ terms, using an interface type for an iterator can be seen as | |
| carrying an abstraction penalty, in that run-time efficiency will be | |
| less than C++ approaches that in effect inline all code; we believe | |
| that Go programmers will continue to find that sort of penalty to be | |
| acceptable. | |
| As we get more container types, we may develop a standard `Iterator` | |
| interface. | |
| That may in turn lead to pressure to modify the language to add some | |
| mechanism for using an `Iterator` with the `range` clause. | |
| That is very speculative, though. | |
| #### Efficiency | |
| It is not clear what sort of efficiency people expect from generic | |
| code. | |
| Generic functions, rather than generic types, can probably be compiled | |
| using an interface-based approach. | |
| That will optimize compile time, in that the package is only compiled | |
| once, but there will be some run-time cost. | |
| Generic types may most naturally be compiled multiple times for each | |
| set of type arguments. | |
| This will clearly carry a compile time cost, but there shouldn’t be | |
| any run-time cost. | |
| Compilers can also choose to implement generic types similarly to | |
| interface types, using special purpose methods to access each element | |
| that depends on a type parameter. | |
| Only experience will show what people expect in this area. | |
| #### Omissions | |
| We believe that this design covers the basic requirements for | |
| generic programming. | |
| However, there are a number of programming constructs that are not | |
| supported. | |
| * No specialization. | |
| There is no way to write multiple versions of a generic function | |
| that are designed to work with specific type arguments (other than | |
| using type assertions or type switches). | |
| * No metaprogramming. | |
| There is no way to write code that is executed at compile time to | |
| generate code to be executed at run time. | |
| * No higher-level abstraction. | |
| There is no way to speak about a function with type arguments other | |
| than to call it or instantiate it. | |
| There is no way to speak about a parameterized type other than to | |
| instantiate it. | |
| * No covariance or contravariance. | |
| * No operator methods. | |
| You can write a generic container that is compile-time type-safe, | |
| but you can only access it with ordinary methods, not with syntax | |
| like `c[k]`. | |
| Similarly, there is no way to use `range` with a generic container | |
| type. | |
| * No currying. | |
| There is no way to specify only some of the type arguments, other | |
| than by using a type alias or a helper function. | |
| * No adaptors. | |
| There is no way for a contract to define adaptors that could be used | |
| to support type arguments that do not already satisfy the contract, | |
| such as, for example, defining an `==` operator in terms of an | |
| `Equal` method. | |
| * No parameterization on non-type values. | |
| This arises most obviously for arrays, where it might sometimes be | |
| convenient to write `type Matrix(type n int) [n][n]float64`. | |
| It might also sometimes be useful to specify significant values for | |
| a container type, such as a default value for elements. | |
| #### Issues | |
| There are some issues with this design that deserve a more detailed | |
| discussion. | |
| We think these issues are relatively minor compared with the design | |
| as a whole, but they still deserve a complete hearing and discussion. | |
| ##### The zero value | |
| This design has no simple expression for the zero value of a type | |
| parameter. | |
| For example, consider this implementation of optional values by using | |
| pointers: | |
| ```Go | |
| type Optional(type T) struct { | |
| p *T | |
| } | |
| func (o Optional(T)) Val() T { | |
| if o.p != nil { | |
| return *o.p | |
| } | |
| var zero T | |
| return zero | |
| } | |
| ``` | |
| In the case where `o.p == nil`, we want to return the zero value of | |
| `T`, but we have no way to write that. | |
| It would be nice to be able to write `return nil`, but that wouldn’t | |
| work if `T` is, say, `int`; in that case we would have to write | |
| `return 0`. | |
| Some approaches to this are: | |
| * Use `var zero T`, as above, which works with the existing design | |
| but requires an extra statement. | |
| * Use `*new(T)`, which is ugly but works with the existing design. | |
| * Extend the design to permit using `nil` as the zero value of any | |
| generic type (but see [issue 22729](https://golang.org/issue/22729)). | |
| * Extend the design to permit using `T{}`, where `T` is a type | |
| parameter, to indicate the zero value of the type. | |
| * Change the language to permit using `_` on the right hand of an | |
| assignment (including `return` or a function call) as proposed in | |
| [issue 19642](https://golang.org/issue/19642). | |
| We feel that more experience with this design is needed before | |
| deciding what, if anything, to do here. | |
| ##### Lots of irritating silly parentheses | |
| Calling a function with type parameters requires an additional list of | |
| type arguments if the type arguments can not be inferred. | |
| If the function returns a function, and we call that, we get still | |
| more parentheses. | |
| ```Go | |
| F(int, float64)(x, y)(s) | |
| ``` | |
| We experimented with other syntaxes, such as using a colon to separate | |
| the type arguments from the regular arguments. | |
| The current design seems to be the best, but perhaps something | |
| better is possible. | |
| ##### What does := mean in a contract body? | |
| If a contract body uses a short declaration, such as | |
| ```Go | |
| s := x.String() | |
| ``` | |
| this does not provide any information about the result parameter of | |
| the `String` method. | |
| This contract body would match any type with a `String` method that | |
| returns a single result of any type. | |
| It’s less clear what it permits in the function using this contract. | |
| For example, does it permit the function to call the `String` method | |
| and assign the result to a variable of empty interface type? | |
| ##### Pointer vs. value methods in contracts | |
| It seems that the natural ways to write a contract calling for certain | |
| methods to exist will accept either a pointer method or a value | |
| method. | |
| That may be confusing, in that it will prevent writing a function body | |
| that requires a value method. | |
| We will have to judge from experience how much this confuses people in | |
| practice. | |
| ##### Copying the function body into the contract body | |
| The simplest way to ensure that a function only performs the | |
| operations permitted by its contract is to simply copy the function | |
| body into the contract body. | |
| In other words, to make the function body be its own contract, much as | |
| C++ does. | |
| If people take this path, then this design in effect creates a lot of | |
| additional complexity for no benefit. | |
| We think this is unlikely because we believe that most people will not | |
| write generic function, and we believe that most generic functions | |
| will have only non-existent or trivial requirements on their type | |
| parameters. | |
| More experience will be needed to see whether this is a problem. | |
| #### Discarded ideas | |
| This design is not perfect, and it will be changed as we gain | |
| experience with it. | |
| That said, there are many ideas that we’ve already considered in | |
| detail. | |
| This section lists some of those ideas in the hopes that it will help | |
| to reduce repetitive discussion. | |
| The ideas are presented in the form of a FAQ. | |
| ##### Why not use interfaces instead of contracts? | |
| _The interface method syntax is familiar._ | |
| _Writing contract bodies with `x + x` is ordinary Go syntax, but it_ | |
| _is stylized, repetitive, and looks weird._ | |
| It is unclear how to represent operators using interface methods. | |
| We considered syntaxes like `+(T, T) T`, but that is confusing and | |
| repetitive. | |
| Also, a minor point, but `==(T, T) bool` does not correspond to the | |
| `==` operator, which returns an untyped boolean value, not `bool`. | |
| We also considered writing simply `+` or `==`. | |
| That seems to work but unfortunately the semicolon insertion rules | |
| require writing a semicolon after each operator at the end of a line. | |
| Using contracts that look like functions gives us a familiar syntax at | |
| the cost of some repetition. | |
| These are not fatal problems, but they are difficulties. | |
| ##### Why not put type parameters on packages? | |
| We investigated this extensively. | |
| It becomes problematic when you want to write a `list` package, and | |
| you want that package to include a `Transform` function that converts | |
| a `List` of one element type to a `List` of another element type. | |
| It’s very awkward for a function in one instantiation of a package to | |
| return a type that requires a different instantiation of the package. | |
| It also confuses package boundaries with type definitions. | |
| There is no particular reason to think that the uses of parameterized | |
| types will break down neatly into packages. | |
| Sometimes they will, sometimes they won’t. | |
| ##### Why not use `F<T>` like C++ and Java? | |
| When parsing code within a function, such as `v := F<T>`, at the point | |
| of seeing the `<` it’s ambiguous whether we are seeing a type | |
| instantiation or an expression using the `<` operator. | |
| Resolving that requires effectively unbounded lookahead. | |
| In general we strive to keep the Go parser simple. | |
| ##### Why not use `F[T]`? | |
| When parsing a type declaration `type A [T] int` it’s ambiguous | |
| whether this is a parameterized type defined (uselessly) as `int` or | |
| whether it is an array type with `T` elements. | |
| ##### Why not use `F«T»`? | |
| We considered it but we couldn’t bring ourselves to require | |
| non-ASCII. | |
| ##### Why not define contracts in a standard package? | |
| _Instead of writing out contracts, use names like_ | |
| _`contracts.Arithmetic` and `contracts.Comparable`._ | |
| Listing all the possible combinations of types gets rather lengthy. | |
| It also introduces a new set of names that not only the writer of | |
| generic code, but, more importantly, the reader, must remember. | |
| One of the driving goals of this design is to not introduce new | |
| names. | |
| Instead we introduce one new keyword and some new syntax. | |
| We expect that if people find such names useful, we can introduce a | |
| package `contracts` that defines the useful names in the form of | |
| contracts that can be used by other types and functions and embedded | |
| in other contracts. | |
| #### Comparison with Java | |
| Most complaints about Java generics center around type erasure. | |
| This design does not have type erasure. | |
| The reflection information for a generic type will include the full | |
| compile-time type information. | |
| In Java type wildcards (`List<? extends Number>`, `List<? super | |
| Number>`) implement covariance and contravariance. | |
| These concepts are missing from Go, which makes generic types much | |
| simpler. | |
| #### Comparison with C++ | |
| C++ templates do not enforce any constraints on the type arguments | |
| (unless the concept proposal is adopted). | |
| This means that changing template code can accidentally break far-off | |
| instantiations. | |
| It also means that error messages are reported only at instantiation | |
| time, and can be deeply nested and difficult to understand. | |
| This design avoids these problems through explicit contracts. | |
| C++ supports template metaprogramming, which can be thought of as | |
| ordinary programming done at compile time using a syntax that is | |
| completely different than that of non-template C++. | |
| This design has no similar feature. | |
| This saves considerable complexity while losing some power and | |
| run-time efficiency. | |
| ### Examples | |
| The following sections are examples of how this design could be used. | |
| This is intended to address specific areas where people have created | |
| user experience reports concerned with Go’s lack of generics. | |
| #### sort | |
| Before the introduction of `sort.Slice`, a common complaint was the | |
| need for boilerplate definitions in order to use `sort.Sort`. | |
| With this design, we can add to the sort package as follows: | |
| ```Go | |
| contract ordered(e Ele) { e < e } | |
| type orderedSlice(type Ele ordered) []Ele | |
| func (s orderedSlice(Ele)) Len() int { return len(s) } | |
| func (s orderedSlice(Ele)) Less(i, j int) bool { return s[i] < s[j] } | |
| func (s orderedSlice(Ele)) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
| // OrderedSlice sorts the slice s in ascending order. | |
| // The elements of s must be ordered using the < operator. | |
| func OrderedSlice(type Ele ordered)(s []Ele) { | |
| sort.Sort(orderedSlice(Ele)(s)) | |
| } | |
| ``` | |
| Now we can write: | |
| ```Go | |
| sort.OrderedSlice(int32)([]int32{3, 5, 2}) | |
| ``` | |
| We can rely on type inference to omit the type argument list: | |
| ```Go | |
| sort.OrderedSlice([]string{"a", "c", "b"}) | |
| ``` | |
| Along the same lines, we can add a function for sorting using a | |
| comparison function, similar to `sort.Slice` but writing the function | |
| to take values rather than slice indexes. | |
| ```Go | |
| type sliceFn(type Ele) struct { | |
| s []Ele | |
| f func(Ele, Ele) bool | |
| } | |
| func (s sliceFn(Ele)) Len() int { return len(s.s) } | |
| func (s sliceFn(Ele)) Less(i, j int) bool { return s.f(s.s[i], s.s[j]) } | |
| func (s sliceFn(Ele)) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] } | |
| // SliceFn sorts the slice s according to the function f. | |
| func SliceFn(type Ele)(s []Ele, f func(Ele, Ele) bool) { | |
| Sort(sliceFn(Ele){s, f}) | |
| } | |
| ``` | |
| An example of calling this might be: | |
| ```Go | |
| var s []*Person | |
| // ... | |
| sort.SliceFn(s, func(p1, p2 *Person) bool { return p1.Name < p2.Name }) | |
| ``` | |
| #### map keys | |
| Here is how to get a slice of the keys of any map. | |
| ```Go | |
| package maps | |
| contract mappable(k K, _ V) { k == k } | |
| func Keys(type K, V mappable)(m map[K]V) []K { | |
| r := make([]K, 0, len(m)) | |
| for k := range m { | |
| r = append(r, k) | |
| } | |
| return r | |
| } | |
| ``` | |
| In typical use the types will be inferred. | |
| ```Go | |
| k := maps.Keys(map[int]int{1:2, 2:4}) // sets k to []int{1, 2} (or {2, 1}) | |
| ``` | |
| #### map/reduce/filter | |
| Here is an example of how to write map, reduce, and filter functions | |
| for slices. | |
| These functions are intended to correspond to the similar functions in | |
| Lisp, Python, Java, and so forth. | |
| ```Go | |
| // Package slices implements various slice algorithms. | |
| package slices | |
| // Map turns a []T1 to a []T2 using a mapping function. | |
| func Map(type T1, T2)(s []T1, f func(T1) T2) []T2 { | |
| r := make([]T2, len(s)) | |
| for i, v := range s { | |
| r[i] = f(v) | |
| } | |
| return r | |
| } | |
| // Reduce reduces a []T1 to a single value using a reduction function. | |
| func Reduce(type T1, T2)(s []T1, initializer T2, f func(T2, T1) T2) T2 { | |
| r := initializer | |
| for _, v := range s { | |
| r = f(r, v) | |
| } | |
| return r | |
| } | |
| // Filter filters values from a slice using a filter function. | |
| func Filter(type T)(s []T, f func(T) bool) []T { | |
| var r []T | |
| for _, v := range s { | |
| if f(v) { | |
| r = append(r, v) | |
| } | |
| } | |
| return r | |
| } | |
| ``` | |
| Example calls: | |
| ```Go | |
| s := []int{1, 2, 3} | |
| floats := slices.Map(s, func(i int) float64 { return float64(i) }) | |
| sum := slices.Reduce(s, 0, func(i, j int) int { return i + j }) | |
| evens := slices.Filter(s, func(i int) bool { return i%2 == 0 }) | |
| ``` | |
| #### sets | |
| Many people have asked for Go’s builtin map type to be extended, or | |
| rather reduced, to support a set type. | |
| Here is a type-safe implementation of a set type, albeit one that uses | |
| methods rather than operators like `[]`. | |
| ```Go | |
| // Package set implements sets of any type. | |
| package set | |
| contract comparable(Ele) { Ele == Ele } | |
| type Set(type Ele comparable) map[Ele]struct{} | |
| func Make(type Ele comparable)() Set(Ele) { | |
| return make(Set(Ele)) | |
| } | |
| func (s Set(Ele)) Add(v Ele) { | |
| s[v] = struct{}{} | |
| } | |
| func (s Set(Ele)) Delete(v Ele) { | |
| delete(s, v) | |
| } | |
| func (s Set(Ele)) Contains(v Ele) bool { | |
| _, ok := s[v] | |
| return ok | |
| } | |
| func (s Set(Ele)) Len() int { | |
| return len(s) | |
| } | |
| func (s Set(Ele)) Iterate(f func(Ele)) { | |
| for v := range s { | |
| f(v) | |
| } | |
| } | |
| ``` | |
| Example use: | |
| ```Go | |
| s := set.Make(int) | |
| s.Add(1) | |
| if s.Contains(2) { panic("unexpected 2") } | |
| ``` | |
| This example, like the sort examples above, shows how to use this | |
| design to provide a compile-time type-safe wrapper around an | |
| existing API. | |
| #### channels | |
| Many simple general purpose channel functions are never written, | |
| because they must be written using reflection and the caller must type | |
| assert the results. | |
| With this design they become easy to write. | |
| ```Go | |
| package chans | |
| import "runtime" | |
| // Ranger returns a Sender and a Receiver. The Receiver provides a | |
| // Next method to retrieve values. The Sender provides a Send method | |
| // to send values and a Close method to stop sending values. The Next | |
| // method indicates when the Sender has been closed, and the Send | |
| // method indicates when the Receiver has been freed. | |
| // | |
| // This is a convenient way to exit a goroutine sending values when | |
| // the receiver stops reading them. | |
| func Ranger(type T)() (*Sender(T), *Receiver(T)) { | |
| c := make(chan T) | |
| d := make(chan bool) | |
| s := &Sender(T){values: c, done: d} | |
| r := &Receiver(T){values: c, done: d} | |
| runtime.SetFinalizer(r, (*Receiver(T)).finalize) | |
| return s, r | |
| } | |
| // A sender is used to send values to a Receiver. | |
| type Sender(type T) struct { | |
| values chan<- T | |
| done <-chan bool | |
| } | |
| // Send sends a value to the receiver. It reports whether any more | |
| // values may be sent; if it returns false the value was not sent. | |
| func (s *Sender(T)) Send(v T) bool { | |
| select { | |
| case s.values <- v: | |
| return true | |
| case <-s.done: | |
| return false | |
| } | |
| } | |
| // Close tells the receiver that no more values will arrive. | |
| // After Close is called, the Sender may no longer be used. | |
| func (s *Sender(T)) Close() { | |
| close(s.values) | |
| } | |
| // A Receiver receives values from a Sender. | |
| type Receiver(type T) struct { | |
| values <-chan T | |
| done chan<- bool | |
| } | |
| // Next returns the next value from the channel. The bool result | |
| // indicates whether the value is valid, or whether the Sender has | |
| // been closed and no more values will be received. | |
| func (r *Receiver(T)) Next() (T, bool) { | |
| v, ok := <-r.values | |
| return v, ok | |
| } | |
| // finalize is a finalizer for the receiver. | |
| func (r *Receiver(T)) finalize() { | |
| close(r.done) | |
| } | |
| ``` | |
| There is an example of using this function in the next section. | |
| #### containers | |
| One of the frequent requests for generics in Go is the ability to | |
| write compile-time type-safe containers. | |
| This design makes it easy to write a compile-time type-safe wrapper | |
| around an existing container; we won’t write out an example for that. | |
| This design also makes it easy to write a compile-time type-safe | |
| container that does not use boxing. | |
| Here is an example of an ordered map implemented as a binary tree. | |
| The details of how it works are not too important. | |
| The important points are: | |
| * The code is written in a natural Go style, using the key and value | |
| types where needed. | |
| * The keys and values are stored directly in the nodes of the tree, | |
| not using pointers and not boxed as interface values. | |
| ```Go | |
| // Package orderedmap provides an ordered map, implemented as a binary tree. | |
| package orderedmap | |
| import "chans" | |
| // Map is an ordered map. | |
| type Map(type K, V) struct { | |
| root *node(K, V) | |
| compare func(K, K) int | |
| } | |
| // node is the type of a node in the binary tree. | |
| type node(type K, V) struct { | |
| key K | |
| val V | |
| left, right *node(K, V) | |
| } | |
| // New returns a new map. | |
| func New(type K, V)(compare func(K, K) int) *Map(K, V) { | |
| return &Map(K, V){compare: compare} | |
| } | |
| // find looks up key in the map, and returns either a pointer | |
| // to the node holding key, or a pointer to the location where | |
| // such a node would go. | |
| func (m *Map(K, V)) find(key K) **node(K, V) { | |
| pn := &m.root | |
| for *pn != nil { | |
| switch cmp := m.compare(key, (*pn).key); { | |
| case cmp < 0: | |
| pn = &(*pn).left | |
| case cmp > 0: | |
| pn = &(*pn).right | |
| default: | |
| return pn | |
| } | |
| } | |
| return pn | |
| } | |
| // Insert inserts a new key/value into the map. | |
| // If the key is already present, the value is replaced. | |
| // Returns true if this is a new key, false if already present. | |
| func (m *Map(K, V)) Insert(key K, val V) bool { | |
| pn := m.find(key) | |
| if *pn != nil { | |
| (*pn).val = val | |
| return false | |
| } | |
| *pn = &node(K, V){key: key, val: val} | |
| return true | |
| } | |
| // Find returns the value associated with a key, or zero if not present. | |
| // The found result reports whether the key was found. | |
| func (m *Map(K, V)) Find(key K) (V, bool) { | |
| pn := m.find(key) | |
| if *pn == nil { | |
| var zero V // see the discussion of zero values, above | |
| return zero, false | |
| } | |
| return (*pn).val, true | |
| } | |
| // keyValue is a pair of key and value used when iterating. | |
| type keyValue(type K, V) struct { | |
| key K | |
| val V | |
| } | |
| // InOrder returns an iterator that does an in-order traversal of the map. | |
| func (m *Map(K, V)) InOrder() *Iterator(K, V) { | |
| sender, receiver := chans.Ranger(keyValue(K, V))() | |
| var f func(*node(K, V)) bool | |
| f = func(n *node(K, V)) bool { | |
| if n == nil { | |
| return true | |
| } | |
| // Stop sending values if sender.Send returns false, | |
| // meaning that nothing is listening at the receiver end. | |
| return f(n.left) && | |
| sender.Send(keyValue(K, V){n.key, n.val}) && | |
| f(n.right) | |
| } | |
| go func() { | |
| f(m.root) | |
| sender.Close() | |
| }() | |
| return &Iterator{receiver} | |
| } | |
| // Iterator is used to iterate over the map. | |
| type Iterator(type K, V) struct { | |
| r *chans.Receiver(keyValue(K, V)) | |
| } | |
| // Next returns the next key and value pair, and a boolean indicating | |
| // whether they are valid or whether we have reached the end. | |
| func (it *Iterator(K, V)) Next() (K, V, bool) { | |
| keyval, ok := it.r.Next() | |
| if !ok { | |
| var zerok K | |
| var zerov V | |
| return zerok, zerov, false | |
| } | |
| return keyval.key, keyval.val, true | |
| } | |
| ``` | |
| This is what it looks like to use this package: | |
| ```Go | |
| import "container/orderedmap" | |
| var m = orderedmap.New(string, string)(strings.Compare) | |
| func Add(a, b string) { | |
| m.Insert(a, b) | |
| } | |
| ``` | |
| #### append | |
| The predeclared `append` function exists to replace the boilerplate | |
| otherwise required to grow a slice. | |
| Before `append` was added to the language, there was a function `Add` | |
| in the bytes package with the signature | |
| ```Go | |
| func Add(s, t []byte) []byte | |
| ``` | |
| that appended two `[]byte` values together, returning a new slice. | |
| That was fine for `[]byte`, but if you had a slice of some other | |
| type, you had to write essentially the same code to append more | |
| values. | |
| If this design were available back then, perhaps we would not have | |
| added `append` to the language. | |
| Instead, we could write something like this: | |
| ```Go | |
| package slices | |
| // Append adds values to the end of a slice, returning a new slice. | |
| func Append(type T)(s []T, t ...T) []T { | |
| lens := len(s) | |
| tot := lens + len(t) | |
| if tot <= cap(s) { | |
| s = s[:tot] | |
| } else { | |
| news := make([]T, tot, tot + tot/2) | |
| copy(news, s) | |
| s = news | |
| } | |
| copy(s[lens:tot], t) | |
| return s | |
| } | |
| ``` | |
| That example uses the predeclared `copy` function, but that’s OK, we | |
| can write that one too: | |
| ```Go | |
| // Copy copies values from t to s, stopping when either slice is | |
| // full, returning the number of values copied. | |
| func Copy(type T)(s, t []T) int { | |
| i := 0 | |
| for ; i < len(s) && i < len(t); i++ { | |
| s[i] = t[i] | |
| } | |
| return i | |
| } | |
| ``` | |
| These functions can be used as one would expect: | |
| ```Go | |
| s := slices.Append([]int{1, 2, 3}, 4, 5, 6) | |
| slices.Copy(s[3:], []int{7, 8, 9}) | |
| ``` | |
| This code doesn’t implement the special case of appending or copying a | |
| `string` to a `[]byte`, and it’s unlikely to be as efficient as the | |
| implementation of the predeclared function. | |
| Still, this example shows that using this design would permit append | |
| and copy to be written generically, once, without requiring any | |
| additional special language features. | |
| #### metrics | |
| In a [Go experience | |
| report](https://medium.com/@sameer_74231/go-experience-report-for-generics-google-metrics-api-b019d597aaa4) | |
| Sameer Ajmani describes a metrics implementation. | |
| Each metric has a value and one or more fields. | |
| The fields have different types. | |
| Defining a metric requires specifying the types of the fields, and | |
| creating a value with an Add method. | |
| The Add method takes the field types as arguments, and records an | |
| instance of that set of fields. | |
| The C++ implementation uses a variadic template. | |
| The Java implementation includes the number of fields in the name of | |
| the type. | |
| Both the C++ and Java implementations provide compile-time type-safe | |
| Add methods. | |
| Here is how to use this design to provide similar functionality in | |
| Go with a compile-time type-safe Add method. | |
| Because there is no support for a variadic number of type arguments, | |
| we must use different names for a different number of arguments, as in | |
| Java. | |
| This implementation only works for comparable types. | |
| A more complex implementation could accept a comparison function to | |
| work with arbitrary types. | |
| ```Go | |
| package metrics | |
| import "sync" | |
| contract comparable(v T) { | |
| v == v | |
| } | |
| contract cmp1(T) { | |
| comparable(T) // contract embedding | |
| } | |
| type Metric1(type T cmp1) struct { | |
| mu sync.Mutex | |
| m map[T]int | |
| } | |
| func (m *Metric1(T)) Add(v T) { | |
| m.mu.Lock() | |
| defer m.mu.Unlock() | |
| if m.m == nil { | |
| m.m = make(map[T]int) | |
| } | |
| m[v]++ | |
| } | |
| contract cmp2(T1, T2) { | |
| comparable(T1) | |
| comparable(T2) | |
| } | |
| type key2(type T1, T2 cmp2) struct { | |
| f1 T1 | |
| f2 T2 | |
| } | |
| type Metric2(type T1, T2 cmp2) struct { | |
| mu sync.Mutex | |
| m map[key2(T1, T2)]int | |
| } | |
| func (m *Metric2(T1, T2)) Add(v1 T1, v2 T2) { | |
| m.mu.Lock() | |
| defer m.mu.Unlock() | |
| if m.m == nil { | |
| m.m = make(map[key2(T1, T2)]int) | |
| } | |
| m[key(T1, T2){v1, v2}]++ | |
| } | |
| contract cmp3(T1, T2, T3) { | |
| comparable(T1) | |
| comparable(T2) | |
| comparable(T3) | |
| } | |
| type key3(type T1, T2, T3 cmp3) struct { | |
| f1 T1 | |
| f2 T2 | |
| f3 T3 | |
| } | |
| type Metric3(type T1, T2, T3 cmp3) struct { | |
| mu sync.Mutex | |
| m map[key3(T1, T2, T3)]int | |
| } | |
| func (m *Metric3(T1, T2, T3)) Add(v1 T1, v2 T2, v3 T3) { | |
| m.mu.Lock() | |
| defer m.mu.Unlock() | |
| if m.m == nil { | |
| m.m = make(map[key3]int) | |
| } | |
| m[key(T1, T2, T3){v1, v2, v3}]++ | |
| } | |
| // Repeat for the maximum number of permitted arguments. | |
| ``` | |
| Using this package looks like this: | |
| ```Go | |
| import "metrics" | |
| var m = metrics.Metric2(string, int){} | |
| func F(s string, i int) { | |
| m.Add(s, i) // this call is type checked at compile time | |
| } | |
| ``` | |
| This package implementation does have a certain amount of repetition | |
| due to the lack of support for variadic package type parameters. | |
| Using the package, though, is easy and type safe. | |
| #### list transform | |
| While slices are efficient and easy to use, there are occasional cases | |
| where a linked list is appropriate. | |
| This example primarily shows transforming a linked list of one type to | |
| another type, as an example of using different instantiations of the | |
| same parameterized type. | |
| ```Go | |
| package list | |
| // List is a linked list. | |
| type List(type T) struct { | |
| head, tail *element(T) | |
| } | |
| // An element is an entry in a linked list. | |
| type element(type T) struct { | |
| next *element(T) | |
| val T | |
| } | |
| // Push pushes an element to the end of the list. | |
| func (lst *List(T)) Push(v T) { | |
| if lst.tail == nil { | |
| lst.head = &element(T){val: v} | |
| lst.tail = lst.head | |
| } else { | |
| lst.tail.next = &element(T){val: v } | |
| lst.tail = lst.tail.next | |
| } | |
| } | |
| // Iterator ranges over a list. | |
| type Iterator(type T) struct { | |
| next **element(T) | |
| } | |
| // Range returns an Iterator starting at the head of the list. | |
| func (lst *List(T)) Range() *Iterator(T) { | |
| return Iterator(T){next: &lst.head} | |
| } | |
| // Next advances the iterator. | |
| // It reports whether there are more elements. | |
| func (it *Iterator(T)) Next() bool { | |
| if *it.next == nil { | |
| return false | |
| } | |
| it.next = &(*it.next).next | |
| return true | |
| } | |
| // Val returns the value of the current element. | |
| // The bool result reports whether the value is valid. | |
| func (it *Iterator(T)) Val() (T, bool) { | |
| if *it.next == nil { | |
| var zero T | |
| return zero, false | |
| } | |
| return (*it.next).val, true | |
| } | |
| // Transform runs a transform function on a list, returning a new list. | |
| func Transform(type T1, T2)(lst *List(T1), f func(T1) T2) *List(T2) { | |
| ret := &List(T2){} | |
| it := lst.Range() | |
| for { | |
| if v, ok := it.Val(); ok { | |
| ret.Push(f(v)) | |
| } | |
| it.Next() | |
| } | |
| return ret | |
| } | |
| ``` | |
| #### context | |
| The standard "context" package provides a `Context.Value` method to | |
| fetch a value from a context. | |
| The method returns `interface{}`, so using it normally requires a type | |
| assertion to the correct type. | |
| Here is an example of how we can add type parameters to the "context" | |
| package to provide a type-safe wrapper around `Context.Value`. | |
| ```Go | |
| // Key is a key that can be used with Context.Value. | |
| // Rather than calling Context.Value directly, use Key.Load. | |
| // | |
| // The zero value of Key is not ready for use; use NewKey. | |
| type Key(type V) struct { | |
| name string | |
| } | |
| // NewKey returns a key used to store values of type V in a Context. | |
| // Every Key returned is unique, even if the name is reused. | |
| func NewKey(type V)(name string) *Key { | |
| return &Key(V){name: name} | |
| } | |
| // WithValue returns a new context with v associated with k. | |
| func (k *Key(V)) WithValue(parent Context, v V) Context { | |
| return WithValue(parent, k, v) | |
| } | |
| // Value loads the value associated with k from ctx and reports | |
| //whether it was successful. | |
| func (k *Key(V)) Value(ctx Context) (V, bool) { | |
| v, present := ctx.Value(k).(V) | |
| return v.(V), present | |
| } | |
| // String returns the name and expected value type. | |
| func (k *Key(V)) String() string { | |
| var v V | |
| return fmt.Sprintf("%s(%T)", k.name, v) | |
| } | |
| ``` | |
| To see how this might be used, consider the net/http package’s | |
| `ServerContextKey`: | |
| ```Go | |
| var ServerContextKey = &contextKey{"http-server"} | |
| // used as: | |
| ctx := context.Value(ServerContextKey, srv) | |
| s, present := ctx.Value(ServerContextKey).(*Server) | |
| ``` | |
| This could be written instead as | |
| ```Go | |
| var ServerContextKey = context.NewKey(*Server)("http_server") | |
| // used as: | |
| ctx := ServerContextKey.WithValue(ctx, srv) | |
| s, present := ServerContextKey.Value(ctx) | |
| ``` | |
| Code that uses `Key.WithValue` and `Key.Value` instead of | |
| `context.WithValue` and `context.Value` does not need any type | |
| assertions and is compile-time type-safe. |