| # Contracts — Draft Design |
| |
| Ian Lance Taylor\ |
| Robert Griesemer\ |
| July 31, 2019 |
| |
| ## Superseded |
| |
| We will not be pursuing the approach outlined in this design draft. |
| It has been replaced by a [new |
| proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md). |
| This document exists for historical context. |
| |
| ## 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 permitted 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. |
| |
| ## Background |
| |
| This version of the design draft is similar to the one presented on |
| August 27, 2018, except that the syntax of contracts is completely |
| different. |
| |
| There have been many [requests to add additional support for generic |
| programming](https://github.com/golang/go/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 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 design draft 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 parameteric polymorphism this |
| design is perhaps most similar to Ada, although the syntax is |
| completely different. |
| |
| 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 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 running the generic code, the type parameter will be 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) |
| } |
| } |
| ``` |
| |
| With this approach, 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 list of 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 required. |
| |
| ```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 |
| // This function is INVALID. |
| 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 to `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 implies 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 introduction |
| |
| In this design, a contract describes the requirements of a set of |
| types. |
| We'll discuss contracts further later, but for now we'll just say that |
| one of the things that a contract can do is specify that a type |
| argument must implement a particular method. |
| |
| For the `Stringify` example, we need to write a contract that says |
| that the single type parameter has a `String` method that takes no |
| arguments and returns a value of type `string`. |
| We write that like this: |
| |
| ```Go |
| contract stringer(T) { |
| T String() string |
| } |
| ``` |
| |
| A contract is introduced with a new keyword `contract`, followed by a |
| name and a list of identifiers. |
| The identifiers name the types that the contract will specify. |
| Specifying a required method looks like defining a method in an |
| interface type, except that the receiver type must be explicitly |
| provided. |
| |
| ### 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 don't satisfy the requirements specified by the |
| contract, the compiler will report a type error: the call is using |
| types that the function's contract does not permit. |
| |
| The `stringer` contract seen earlier requires that the type argument |
| used for `T` has a `String` method that takes no arguments and |
| returns a value of type `string`. |
| |
| ### 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 |
| explicitly support that action. |
| In the `stringer` example seen earlier, the contract provides the |
| ability to call a method `String` on a value of the type parameter. |
| 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 with the single |
| element `T`) is followed by an optional contract name. |
| When just the contract name is listed, as above, 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 contract in the order in which they appear in the |
| function signature. |
| Later we'll discuss passing explicit type parameters to the contract. |
| |
| ### Multiple type parameters |
| |
| Although the examples we've seen so far use only a single type |
| parameter, 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(To, From) { |
| To Set(string) |
| From String() string |
| } |
| |
| 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, listed in the same order. |
| This restriction prevents infinite recursion of type instantiation. |
| |
| ```Go |
| // This is OK. |
| type List(type Element) struct { |
| next *List(Element) |
| val Element |
| } |
| |
| // This type 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 this rule to permit some cases that use |
| different type arguments.) |
| |
| The type parameter of a parameterized type may have contracts. |
| |
| ```Go |
| type StringableVector(type T stringer) []T |
| |
| func (s StringableVector(T)) String() string { |
| var sb strings.Builder |
| sb.WriteString("[") |
| for i, v := range s { |
| if i > 0 { |
| sb.WriteString(", ") |
| } |
| sb.WriteString(v.String()) |
| } |
| sb.WriteString("]") |
| return sb.String() |
| } |
| ``` |
| |
| 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 |
| } |
| ``` |
| |
| ### Parameterized type aliases |
| |
| Type aliases may not have parameters. |
| This restriction exists because it is unclear how to handle a type |
| alias with type parameters that have a contract. |
| |
| Type aliases may refer to instantiated types. |
| |
| ```Go |
| type VectorInt = Vector(int) |
| ``` |
| |
| If a type alias refers to a parameterized type, it must provide type |
| arguments. |
| |
| ### 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 suitably parameterized top-level function. |
| |
| This restriction 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 a bit like a method definition in the contract body, |
| but it will be different because there will be no receiver type. |
| It is handled as if the embedded contract's body were placed into the |
| calling contract, with the embedded contract's type parameters |
| replaced by the embedded type arguments. |
| |
| This contract embeds the contract `stringer` defined earlier. |
| |
| ```Go |
| contract PrintStringer(X) { |
| stringer(X) |
| X Print() |
| } |
| ``` |
| |
| This is equivalent to |
| |
| ```Go |
| contract PrintStringer(X) { |
| X String() string |
| X Print() |
| } |
| ``` |
| |
| ### 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 the same as the method's receiver |
| type. |
| |
| ```Go |
| package compare |
| |
| // The equal contract describes types that have an Equal method with |
| // an argument of the same type as the receiver type. |
| contract equal(T) { |
| T Equal(T) bool |
| } |
| |
| // 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 same as the receiver type. |
| |
| ```Go |
| import "compare" |
| |
| type EqualInt int |
| |
| // The Equal method lets EqualInt satisfy the compare.equal contract. |
| func (a EqualInt) Equal(b EqualInt) bool { return a == b } |
| |
| func Index(s []EqualInt, e EqualInt) int { |
| return compare.Index(EqualInt)(s, e) |
| } |
| ``` |
| |
| In this example, when we pass `EqualInt` to `compare.Index`, we |
| check whether `EqualInt` satisfies the contract `compare.equal`. |
| We replace `T` with `EqualInt` in the declaration of the `Equal` |
| method in the `equal` contract, and see whether `EqualInt` has a |
| matching method. |
| `EqualInt` has a method `Equal` that accepts a parameter of type |
| `EqualInt`, so all is well, and the compilation succeeds. |
| |
| ### Mutually referencing type parameters |
| |
| Within a contract, methods may refer to any of the contract's type |
| parameters. |
| |
| 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(Node, Edge) { |
| Node Edges() []Edge |
| Edge Nodes() (from Node, to Node) |
| } |
| |
| type Graph(type Node, Edge G) struct { ... } |
| func New(type Node, Edge G)(nodes []Node) *Graph(Node, Edge) { ... } |
| func (g *Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { ... } |
| ``` |
| |
| While at first glance this may look like a typical use of interface |
| types, `Node` and `Edge` are non-interface types with specific |
| methods. |
| 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 { ... } |
| func (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`. |
| Note that we couldn't use plain `Vertex` or `FromTo`, since the |
| required methods are pointer methods, not value methods. |
| |
| 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. |
| |
| As it is a common observation that contracts share some |
| characteristics of interface types, it's worth stressing that this |
| capability is one that contracts provide but interface types do not. |
| |
| ### Passing parameters to a contract |
| |
| As mentioned earlier, by default the type parameters are passed to the |
| contract in the order in which they appear in the function signature. |
| It is also possible to explicitly pass type parameters to a contract |
| as though they were arguments. |
| This is useful if the contract and the generic function take type |
| parameters in a different order, or if only some parameters need a |
| contract. |
| |
| In this example the type parameter `E` can be any type, but the type |
| parameter `M` must implement the `String` method. |
| The function passes just `M` to the `stringer` contract, leaving `E` |
| as though it had no constraints. |
| |
| ```Go |
| func MapAndPrint(type E, M stringer(M))(s []E, f(E) M) []string { |
| r := make([]string, len(s)) |
| for i, v := range s { |
| r[i] = f(v).String() |
| } |
| return r |
| } |
| ``` |
| |
| ### Contract syntactic details |
| |
| 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 entire |
| 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. |
| |
| Like other top level declarations, a contract is exported if its name |
| starts with an uppercase letter. |
| If exported it may be used by functions, types, or contracts in other |
| packages. |
| |
| ### 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 this design, values of generic 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(T) { |
| T Set(string) error |
| } |
| |
| 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"}) |
| if err != nil { ... } |
| // 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 components. |
| |
| ```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, since there is no value from which to infer the types. |
| 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 respective 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, we ignore untyped constants on the caller side and |
| their corresponding types in the function definition. |
| |
| We compare corresponding types in the lists. |
| 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, we 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, we determine the default |
| type of the untyped constant in [the usual |
| way](https://golang.org/ref/spec#Constants). |
| Then we 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, so the call written as `transform.Slice` is a |
| call of `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 for |
| composite literals of parameterized types. |
| |
| ```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 be 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 |
| function 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 are 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 reader(T) { |
| T Read([]byte) (int, error) |
| } |
| |
| func ReadByte(type T reader)(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 open 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. |
| To write a type literal that is a slice of a type instantiation, you |
| must write `[](T(v1))`. |
| Without those parentheses, `[]T(x)` is parsed as `([]T)(x)`, not as |
| `[](T(x))`. |
| 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. |
| |
| ### Using parameterized types as unnamed function parameter types |
| |
| When parsing a parameterized type as an unnamed function parameter |
| type, there is a parsing ambiguity. |
| |
| ```Go |
| var f func(x(T)) |
| ``` |
| |
| In this example we don't know whether the function has a single |
| unnamed parameter of the parameterized type `x(T)`, or whether this is |
| a named parameter `x` of the type `(T)` (written with parentheses). |
| |
| For backward compatibility, we treat this as the latter case: `x(T)` |
| is a parameter `x` of type `(T)`. |
| In order to describe a function with a single unnamed parameter of |
| type `x(T)`, either the parameter must be named, or extra parentheses |
| must be used. |
| |
| ```Go |
| var f1 func(_ x(T)) |
| var f2 func((x(T))) |
| ``` |
| |
| ### Embedding a parameterized type in a struct |
| |
| There is a parsing ambiguity when embedding a parameterized type |
| in a struct type. |
| |
| ```Go |
| type S1(type T) struct { |
| f T |
| } |
| |
| type S2 struct { |
| S1(int) |
| } |
| ``` |
| |
| In this example we don't know whether struct `S2` has a single |
| field named `S1` of type `(int)`, or whether we |
| are trying to embed the instantiated type `S1(int)` into `S2`. |
| |
| For backward compatibility, we treat this as the former case: `S2` has |
| a field named `S1`. |
| |
| In order to embed an instantiated type in a struct, we could require that |
| extra parentheses be used. |
| |
| ```Go |
| type S2 struct { |
| (S1(int)) |
| } |
| ``` |
| |
| This is currently not supported by the language, so this would suggest |
| generally extending the language to permit types embedded in structs to |
| be parenthesized. |
| |
| ### Embedding a parameterized interface type in an interface |
| |
| There is a parsing ambiguity when embedding a parameterized interface |
| type in another interface type. |
| |
| ```Go |
| type I1(type T) interface { |
| M(T) |
| } |
| |
| type I2 interface { |
| I1(int) |
| } |
| ``` |
| |
| In this example we don't know whether interface `I2` has a single |
| method named `I1` that takes an argument of type `int`, or whether we |
| are trying to embed the instantiated type `I1(int)` into `I2`. |
| |
| For backward compatibility, we treat this as the former case: `I2` has |
| a method named `I1`. |
| |
| In order to embed an instantiated interface, we could require that |
| extra parentheses be used. |
| |
| ```Go |
| type I2 interface { |
| (I1(int)) |
| } |
| ``` |
| |
| This is currently not supported by the language, so this would suggest |
| generally extending the language to permit embedded interface types to |
| be parenthesized. |
| |
| ### 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. |
| |
| #### Methods |
| |
| All the contracts we've seen so far show only method calls in the |
| contract body. |
| If a method call appears in the contract body, that method may be |
| called on a value in any statement or expression in the function |
| body. |
| It will take argument and result types as specified in the contract |
| body. |
| |
| #### Pointer methods |
| |
| In some cases we need to require that a method be a pointer method. |
| This will happen when a function needs to declare variables whose |
| type is the type parameter, and also needs to call methods that are |
| defined for the pointer to the type parameter. |
| |
| For example: |
| |
| ```Go |
| contract setter(T) { |
| T Set(string) |
| } |
| |
| func Init(type T setter)(s string) T { |
| var r T |
| r.Set(s) |
| return r |
| } |
| |
| type MyInt int |
| |
| func (p *MyInt) Set(s string) { |
| v, err := strconv.Atoi(s) |
| if err != nil { |
| log.Fatal("Init failed", err) |
| } |
| *p = MyInt(v) |
| } |
| |
| // INVALID |
| // MyInt does not have a Set method, only *MyInt has one. |
| var Init1 = Init(MyInt)("1") |
| |
| // DOES NOT WORK |
| // r in Init is type *MyInt with value nil, |
| // so the Set method does a nil pointer deference. |
| var Init2 = Init(*MyInt)("2") |
| ``` |
| |
| The function `Init` cannot be instantiated with the type `MyInt`, as |
| that type does not have a method `Set`; only `*MyInt` has `Set`. |
| |
| But instantiating `Init` with `*MyInt` doesn't work either, as then |
| the local variable `r` in `Init` is a value of type `*MyInt` |
| initialized to the zero value, which for a pointer is `nil`. |
| The `Init` function then invokes the `Set` method on a `nil` pointer, |
| causing a `nil` pointer dereference at the line `*p = MyInt(v)`. |
| |
| In order to permit this kind of code, contracts permit specifying that |
| for a type parameter `T` the pointer type `*T` has a method. |
| |
| ```Go |
| contract setter(T) { |
| *T Set(string) |
| } |
| ``` |
| |
| With this definition of `setter`, instantiating `Init` with `MyInt` is |
| valid and the code works. |
| The local variable `r` has type `MyInt`, and the address of `r` is |
| passed as the receiver of the `Set` pointer method. |
| Instantiating `Init` with `*MyInt` is now invalid, as the type |
| `**MyInt` does not have a method `Set`. |
| |
| Listing a `*T` method in a contract means that the method must be on |
| the type `*T`, and it means that the parameterized function is only |
| permitted to call the method on an addressable value of type `T`. |
| |
| #### Pointer or value methods |
| |
| If a method is listed in a contract with a plain `T` rather than `*T`, |
| then it may be either a pointer method or a value method of `T`. |
| In order to avoid worrying about this distinction, in a generic |
| function body all method calls will be pointer method calls. |
| If necessary, the function body will insert temporary variables, |
| not seen by the user, in order to get an addressable variable to use |
| to call the method. |
| |
| For example, this code is valid, even though `LookupAsString` calls |
| `String` in a context that requires a value method, and `MyInt` only |
| has a pointer method. |
| |
| ```Go |
| func LookupAsString(type T stringer)(m map[int]T, k int) string { |
| return m[k].String() // Note: calls method on value of type T |
| } |
| |
| type MyInt int |
| func (p *MyInt) String() { return strconv.Itoa(int(*p)) } |
| func F(m map[int]MyInt) string { |
| return LookupAsString(MyInt)(m, 0) |
| } |
| ``` |
| |
| This makes it easier to understand which types satisfy a contract, and |
| how a contract may be used. |
| It has the drawback that in some cases a pointer method that modifies |
| the value to which the receiver points may be called on a temporary |
| variable that is discarded after the method completes. |
| It may be possible to add a vet warning for a case where a generic |
| function uses a temporary variable for a method call and the function |
| is instantiated with a type that has only a pointer method, not a |
| value method. |
| |
| (Note: we should revisit this decision if it leads to confusion or |
| incorrect code.) |
| |
| #### Operators |
| |
| Method calls are not sufficient for everything we want to express. |
| Consider this simple function that returns the smallest element of a |
| slice of values, where the slice is assumed to be non-empty. |
| |
| ```Go |
| // This function is INVALID. |
| func Smallest(type T)(s []T) T { |
| r := s[0] // panics if slice is empty |
| for _, v := range s[1:] { |
| if v < r { // INVALID |
| r = v |
| } |
| } |
| return r |
| } |
| ``` |
| |
| Any reasonable generics implementation should let you write this |
| function. |
| The problem is the expression `v < r`. |
| This 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 `<`. |
| |
| It follows that we need a way to write a contract that accepts only |
| types that support `<`. |
| In order to do that, we observe that, aside from two exceptions that |
| we will discuss later, all the arithmetic, comparison, and logical |
| operators defined by the language may only be used with types that are |
| predeclared by the language, or with defined types whose underlying |
| type is one of those predeclared types. |
| That is, the operator `<` can only be used with a predeclared type |
| such as `int` or `float64`, or a defined type whose underlying type is |
| one of those types. |
| Go does not permit using `<` with an aggregate type or with an |
| arbitrary defined type. |
| |
| This means that rather than try to write a contract for `<`, we can |
| approach this the other way around: instead of saying which operators |
| a contract should support, we can say which (underlying) types a |
| contract should accept. |
| |
| #### Types in contracts |
| |
| A contract may list explicit types that may be used as type |
| arguments. |
| These are expressed in the form `type-parameter-name type, type...`. |
| The `type` must be a predeclared type, such as `int`, or an aggregate |
| as discussed below. |
| For example, |
| |
| ```Go |
| contract SignedInteger(T) { |
| T int, int8, int16, int32, int64 |
| } |
| ``` |
| |
| This contract specifies that the type argument must be one of the |
| listed types (`int`, `int8`, and so forth), or it must be a defined |
| type whose underlying type is one of the listed types. |
| |
| When a parameterized function using this contract has a value of type |
| `T`, it may use any operation that is permitted by all of the listed |
| types. |
| This can be an operation like `<`, or for aggregate types an operation |
| like `range` or `<-`. |
| If the function can be compiled successfully using each type listed in |
| the contract, then the operation is permitted. |
| |
| For the `Smallest` example shown earlier, we could use a contract like |
| this: |
| |
| ```Go |
| contract Ordered(T) { |
| T int, int8, int16, int32, int64, |
| uint, uint8, uint16, uint32, uint64, uintptr, |
| float32, float64, |
| string |
| } |
| ``` |
| |
| (In practice this contract would likely be defined and exported in a |
| new standard library package, `contracts`, so that it could be used by |
| function and type and contract definitions.) |
| |
| Given that contract, we can write this function, now valid: |
| |
| ```Go |
| func Smallest(type T Ordered)(s []T) T { |
| r := s[0] // panics if slice is empty |
| for _, v := range s[1:] { |
| if v < r { |
| r = v |
| } |
| } |
| return r |
| } |
| ``` |
| |
| #### Conjunction and disjunction in contracts |
| |
| The use of comma to separate types is a general mechanism. |
| A contract can be considered as a set of constraints, where the |
| constraints are either methods or types. |
| Separating constraints by a semicolon or newline means that the |
| constraints are a conjunction: each constraint must be satisfied. |
| Separating constraints by a comma means that the constraints are a |
| disjunction: at least one of the constraints must be satisified. |
| |
| With a conjunction of constraints in a contract, a generic function |
| may use any operation permitted by at least one of the constraints. |
| With a disjunction, a generic function may use any operation permitted |
| by all of the constraints. |
| |
| Syntactically, the type parameter being constrained must be listed for |
| each individual conjunction constraint, but only once for the |
| disjunction constraints. |
| |
| Normally methods will be listed as a conjunction, separated by a |
| semicolon or newline. |
| |
| ```Go |
| // PrintStringer1 and PrintStringer2 are equivalent. |
| contract PrintStringer1(T) { |
| T String() string |
| T Print() |
| } |
| |
| contract PrintStringer2(T) { |
| T String() string; T Print() |
| } |
| ``` |
| |
| Normally builtin types will be listed as a disjunction, separated by |
| commas. |
| |
| ```Go |
| contract Float(T) { |
| T float32, float64 |
| } |
| ``` |
| |
| However, this is not required. |
| For example: |
| |
| ```Go |
| contract IOCloser(S) { |
| S Read([]byte) (int, error), // note trailing comma |
| Write([]byte) (int, error) |
| S Close() error |
| } |
| ``` |
| |
| This contract accepts any type that has a `Close` method and also has |
| either a `Read` or a `Write` method (or both). |
| To put it another way, it accepts any type that implements either |
| `io.ReadCloser` or `io.WriteCloser` (or both). |
| In a generic function using this contract permits calling the |
| `Close` method, but calling the `Read` or `Write` method requires a |
| type assertion to an interface type. |
| It's not clear whether this is useful, but it is valid. |
| |
| Another, pedantic, example: |
| |
| ```Go |
| contract unsatisfiable(T) { |
| T int |
| T uint |
| } |
| ``` |
| |
| This contract permits any type that is both `int` and `uint`. |
| Since there is no such type, the contract does not permit any type. |
| This is valid but useless. |
| |
| #### Both types and methods in contracts |
| |
| A contract may list both builtin types and methods, typically using |
| conjunctions and disjunctions as follows: |
| |
| ```Go |
| contract StringableSignedInteger(T) { |
| T int, int8, int16, int32, int64 |
| T String() string |
| } |
| ``` |
| |
| This contract permits any type defined as one of the listed types, |
| provided it also has a `String() string` method. |
| Although the `StringableSignedInteger` contract explicitly lists |
| `int`, the type `int` is not permitted as a type argument, since `int` |
| does not have a `String` method. |
| An example of a type argument that would be permitted is `MyInt`, |
| defined as: |
| |
| ```Go |
| type MyInt int |
| func (mi MyInt) String() string { |
| return fmt.Sprintf("MyInt(%d)", mi) |
| } |
| ``` |
| |
| #### Aggregate types in contracts |
| |
| A type in a contract need not be a predeclared type; it can be a type |
| literal composed of predeclared types. |
| |
| ```Go |
| contract byteseq(T) { |
| T string, []byte |
| } |
| ``` |
| |
| The same rules apply. |
| The type argument for this contract may be `string` or `[]byte` or a |
| type whose underlying type is one of those. |
| A parameterized function with this contract may use any operation |
| permitted by both `string` and `[]byte`. |
| |
| Given these definitions |
| |
| ```Go |
| type MyByte byte |
| type MyByteAlias = byte |
| ``` |
| |
| the `byteseq` contract is satisfied by any of `string`, `[]byte`, |
| `[]MyByte`, `[]MyByteAlias`. |
| |
| The `byteseq` contract permits writing generic functions that work |
| for either `string` or `[]byte` types. |
| |
| ```Go |
| func Join(type T byteseq)(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), a[0]...)) |
| } |
| n := len(sep) * (len(a) - 1) |
| for i := 0; i < len(a); i++ { |
| n += len(a[i]) // len works for both string and []byte |
| } |
| |
| b := make([]byte, n) |
| bp := copy(b, a[0]) |
| for _, s := range a[1:] { |
| bp += copy(b[bp:], sep) |
| bp += copy(b[bp:], s) |
| } |
| return T(b) |
| } |
| ``` |
| |
| #### Aggregates of type parameters in contracts |
| |
| A type literal in a contract can refer not only to predeclared types, |
| but also to type parameters. |
| In this example, the `Slice` contract takes two parameters. |
| The first type parameter is required to be a slice of the second. |
| There are no constraints on the second type parameter. |
| |
| ```Go |
| contract Slice(S, Element) { |
| S []Element |
| } |
| ``` |
| |
| We can use the `Slice` contract to define a function that takes an |
| argument of a slice type and returns a result of that same type. |
| |
| ```Go |
| func Map(type S, Element Slice)(s S, f func(Element) Element) S { |
| r := make(S, len(s)) |
| for i, v := range s { |
| r[i] = f(v) |
| } |
| return r |
| } |
| |
| type MySlice []int |
| |
| func DoubleMySlice(s MySlice) MySlice { |
| v := Map(MySlice, int)(s, func(e int) int { return 2 * e }) |
| // Here v has type MySlice, not type []int. |
| return v |
| } |
| ``` |
| |
| (Note: the type inference rules described above do not permit |
| inferring both `MySlice` and `int` when `DoubleMySlice` calls `Map`. |
| It may be worth extending them, to make it easier to use functions |
| that are careful to return the same result type as input type. |
| Similarly, we would consider extending the type inference rules to |
| permit inferring the type `Edge` from the type `Node` in the |
| `graph.New` example shown earlier.) |
| |
| To avoid a parsing ambiguity, when a type literal in a contract refers |
| to a parameterized type, extra parentheses are required, so that it is |
| not confused with a method. |
| |
| ```Go |
| type M(type T) []T |
| |
| contract C(T) { |
| T M(T) // T must implement the method M with an argument of type T |
| T (M(T)) // T must be the type M(T) |
| } |
| ``` |
| |
| #### Comparable types in contracts |
| |
| Earlier we mentioned that there are two exceptions to the rule that |
| operators may only be used with types that are predeclared by the |
| language. |
| The exceptions are `==` and `!=`, which are permitted for struct, |
| array, and interface types. |
| These are useful enough that we want to be able to write a contract |
| that accepts any comparable type. |
| |
| To do this we introduce a new predeclared contract: `comparable`. |
| The `comparable` contract takes a single type parameter. |
| It accepts as a type argument any comparable type. |
| It permits in a parameterized function the use of `==` and `!=` with |
| values of that type parameter. |
| |
| As a predeclared contract, `comparable` may be used in a function or |
| type definition, or it may be embedded in another contract. |
| |
| For example, this function may be instantiated with any comparable |
| type: |
| |
| ```Go |
| func Index(type T comparable)(s []T, x T) int { |
| for i, v := range s { |
| if v == x { |
| return i |
| } |
| } |
| return -1 |
| } |
| ``` |
| |
| #### Observations on types in contracts |
| |
| It may seem awkward to explicitly list types in a contract, but it is |
| clear both as to which type arguments are permitted at the call site, |
| and which operations are permitted by the parameterized function. |
| |
| If the language later changes to support operator methods (there are |
| no such plans at present), then contracts will handle them as they do |
| any other kind of method. |
| |
| There will always be a limited number of predeclared types, and a |
| limited number of operators that those types support. |
| Future language changes will not fundamentally change those facts, so |
| this approach will continue to be useful. |
| |
| This approach does not attempt to handle every possible operator. |
| For example, there is no way to usefully express the struct field |
| reference `.` or the general index operator `[]`. |
| The expectation is that those will be handled using aggregate types in |
| a parameterized function definition, rather than requiring aggregate |
| types as a type argument. |
| For example, we expect functions that want to index into a slice to be |
| parameterized on the slice element type `T`, and to use parameters or |
| variables of type `[]T`. |
| |
| As shown in the `DoubleMySlice` example above, this approach makes it |
| awkward to write generic functions that accept and return an aggregate |
| type and want to return the same result type as their argument type. |
| Defined aggregate types are not common, but they do arise. |
| This awkwardness is a weakness of this approach. |
| |
| #### Type conversions |
| |
| In a function with two type parameters `From` and `To`, a value of |
| type `From` may be converted to a value of type `To` if all the |
| types accepted by `From`'s contract can be converted to all the |
| types accepted by `To`'s contract. |
| If either type parameter does not accept types, then type conversions |
| are not permitted. |
| |
| For example: |
| |
| ```Go |
| contract integer(T) { |
| T int, int8, int16, int32, int64, |
| uint, uint8, uint16, uint32, uint64, uintptr |
| } |
| |
| contract integer2(T1, T2) { |
| integer(T1) |
| integer(T2) |
| } |
| |
| func Convert(type To, From integer2)(from From) To { |
| to := To(from) |
| if From(to) != from { |
| panic("conversion out of range") |
| } |
| return to |
| } |
| ``` |
| |
| The type conversions in `Convert` are permitted because Go permits |
| every integer type to be converted to every other integer type. |
| |
| #### Untyped constants |
| |
| Some functions use untyped constants. |
| An untyped constant is permitted with a value of some type parameter |
| if it is permitted with every type accepted by the type parameter's |
| contract. |
| |
| ```Go |
| contract integer(T) { |
| T int, int8, int16, int32, int64, |
| uint, uint8, uint16, uint32, uint64, uintptr |
| } |
| |
| func Add10(type T integer)(s []T) { |
| for i, v := range s { |
| s[i] = v + 10 // OK: 10 can convert to any integer type |
| } |
| } |
| |
| // This function is INVALID. |
| func Add1024(type T integer)(s []T) { |
| for i, v := range s { |
| s[i] = v + 1024 // INVALID: 1024 not permitted by int8/uint8 |
| } |
| } |
| ``` |
| |
| ### 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 document is long and detailed, the actual design reduces to |
| a few major points. |
| |
| * Functions and types can have type parameters, which are defined |
| using optional contracts. |
| * Contracts describe the methods required and the builtin types |
| permitted for a type argument. |
| * Contracts describe the methods and operations permitted for a type |
| parameter. |
| * Type inference will often 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 small for people reading |
| well written 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. |
| |
| 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. |
| |
| A new `contracts` packages will provide standard embeddable contracts, |
| such as contracts that permit all integer types or all numeric types. |
| |
| 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 general type description. |
| For operator support contracts use specific types, rather than |
| describing the characteristics that a type must have. |
| This is easy to understand but may be limiting at times. |
| * 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, or vice-versa. |
| * No parameterization on non-type values such as constants. |
| 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 to 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 that uses |
| 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`. |
| And, of course, there is no contract to support either `return nil` or |
| `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. |
| * For results only, name the result parameter `_`, and use a naked |
| `return` statement to return the zero value. |
| * 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 nicest, but perhaps something |
| better is possible. |
| |
| ##### Pointer vs. value methods in contracts |
| |
| Contracts do not provide a way to distinguish between pointer and |
| value methods, so types that provide either will satisfy a contract. |
| This in turn requires that parameterized functions always permit |
| either kind of method. |
| This may be confusing, in that a parameterized function may invoke a |
| pointer method on a temporary value; if the pointer method changes the |
| value to which the receiver points, those changes will be lost. |
| We will have to judge from experience how much this confuses people in |
| practice. |
| |
| ##### Defined aggregate types |
| |
| As discussed above, an extra type parameter is required for a function |
| to take, as an argument, a defined type whose underlying type is an |
| aggregate type, and to return the same defined type as a result. |
| |
| For example, this function will map a function across a slice. |
| |
| ```Go |
| func Map(type Element)(s []Element, f func(Element) Element) []Element { |
| r := make([]Element, len(s)) |
| for i, v := range s { |
| r[i] = f(v) |
| } |
| return r |
| } |
| ``` |
| |
| However, when called on a defined type, it will return a slice of the |
| element type of that type, rather than the defined type itself. |
| |
| ```Go |
| type MySlice []int |
| |
| func DoubleMySlice(s MySlice) MySlice { |
| s2 := Map(s, func(e int) int { return 2 * e }) |
| // Here s2 is type []int, not type MySlice. |
| return MySlice(s2) |
| } |
| ``` |
| |
| As discussed above with an example, this can be avoided by using an |
| extra type parameter for `Map`, and using a contract that describes |
| the required relationship between the slice and element types. |
| This works but is awkward. |
| |
| ##### Identifying the matched predeclared type |
| |
| In this design we suggest permitting type assertions and type switches |
| on values whose types are based on type parameters, but those type |
| assertions and switches would always test the actual type argument. |
| The design doesn't provide any way to test the contract type matched |
| by the type argument. |
| |
| Here is an example that shows the difference. |
| |
| ```Go |
| contract Float(F) { |
| F float32, float64 |
| } |
| |
| func NewtonSqrt(type F Float)(v F) F { |
| var iterations int |
| switch v.(type) { |
| case float32: |
| iterations = 4 |
| case float64: |
| iterations = 5 |
| default: |
| panic(fmt.Sprintf("unexpected type %T", v)) |
| } |
| // Code omitted. |
| } |
| |
| type MyFloat float32 |
| |
| var G = NewtonSqrt(MyFloat(64)) |
| ``` |
| |
| This code will panic when initializing `G`, because the type of `v` in |
| the `NewtonSqrt` function will be `MyFloat`, not `float32` or |
| `float64`. |
| What this function actually wants to test is not the type of `v`, but |
| the type that `v` matched in the contract. |
| |
| One way to handle this would be to permit type switches on the type |
| `F`, rather than the value `v`, with the proviso that the type `F` |
| would always match a type defined in the contract. |
| This kind of type switch would only be permitted if the contract does |
| list explicit types, and only types listed in the contract would be |
| permitted as cases. |
| |
| If we took this approach, we would stop permitting type assertions and |
| switches on values whose type is based on a type parameter. |
| Those assertions and switches can always be done by first converting |
| the value to the empty interface type. |
| |
| A different approach would be that if a contract specifies any types |
| for a type parameter, then let type switches and assertions on values |
| whose type is, or is based on, that type parameter to match only the |
| types listed in the type parameter's contract. |
| It is still possible to match the value's actual type by first |
| converting it to the `interface{}` type and then doing the type |
| assertion or switch. |
| |
| #### 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._ |
| _Why introduce another way to write methods?_ |
| |
| Contracts, unlike interfaces, support multiple types, including |
| describing ways that the types refer to each other. |
| |
| 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. |
| |
| More seriously, a contract is a relationship between the definition of |
| a generic function and the callers of that function. |
| To put it another way, it is a relationship between a set of type |
| parameters and a set of type arguments. |
| The contract defines how values of the type parameters may be used, |
| and defines the requirements on the type arguments. |
| That is why it is called a contract: because it defines the behavior |
| on both sides. |
| |
| An interface is a type, not a relationship between function |
| definitions and callers. |
| A program can have a value of an interface type, but it makes no sense |
| to speak of a value of a contract type. |
| A value of interface type has both a static type (the interface type) |
| and a dynamic type (some non-interface type), but there is no similar |
| concept for contracts. |
| |
| In other words, contracts are not extensions of interface types. |
| There are things you can do with a contract that you cannot do with an |
| interface type, and there are things you can do with an interace type |
| that you cannot do with a contract. |
| |
| It is true that a contract that has a single type parameter and that |
| lists only methods, not builtin types, for that type parameter, looks |
| similar to an interface type. |
| But all the similarity amounts to is that both provide a list of |
| methods. |
| We could consider permitting using an interface type as a contract |
| with a single type parameter that lists only methods. |
| But that should not mislead us into thinking that contracts are |
| interfaces. |
| |
| ##### Why not permit contracts to describe a type? |
| |
| _In order to use operators contracts have to explicitly and tediously_ |
| _list types._ |
| _Why not permit them to describe a type?_ |
| |
| There are many different ways that a Go type can be used. |
| While it is possible to invent notation to describe the various |
| operations in a contract, it leads to a proliferation of additional |
| syntactic constructs, making contracts complicated and hard to read. |
| The approach used in this design is simpler and relies on only a few |
| new syntactic constructs and names. |
| |
| ##### 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 the syntax `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 the syntax `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. |
| However, this would be addressed by requiring `type A [type T] int` |
| for a parameterized type. |
| |
| Parsing declarations like `func f(A[T]int)` (a single parameter of |
| type `[T]int`) and `func f(A[T], int)` (two parameters, one of type |
| `A[T]` and one of type `int`) show that some additional parsing |
| lookahead is required. |
| This is solvable but adds parsing complexity. |
| |
| The language generally permits a trailing comma in a comma-separated |
| list, so `A[T,]` should be permitted if `A` is a parameterized type, |
| but normally would not be permitted for an index expression. |
| However, the parser can't know whether `A` is a parameterized type or |
| a value of slice, array, or map type, so this parse error can not be |
| reported until after type checking is complete. |
| Again, solvable but complicated. |
| |
| More generally, we felt that the square brackets were too intrusive on |
| the page and that parentheses were more Go like. |
| We will reevaluate this decision as we gain more experience. |
| |
| ##### 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 introduce as few new |
| names as possible. |
| In this design we introduce one new keyword and one new predefined |
| name. |
| |
| 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 |
| type orderedSlice(type Elem Ordered) []Elem |
| |
| func (s orderedSlice(Elem)) Len() int { return len(s) } |
| func (s orderedSlice(Elem)) Less(i, j int) bool { return s[i] < s[j] } |
| func (s orderedSlice(Elem)) 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 Elem Ordered)(s []Elem) { |
| sort.Sort(orderedSlice(Elem)(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 Elem) struct { |
| s []Elem |
| f func(Elem, Elem) bool |
| } |
| |
| func (s sliceFn(Elem)) Len() int { return len(s.s) } |
| func (s sliceFn(Elem)) Less(i, j int) bool { return s.f(s.s[i], s.s[j]) } |
| func (s sliceFn(Elem)) 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 Elem)(s []Elem, f func(Elem, Elem) bool) { |
| Sort(sliceFn(Elem){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 |
| |
| // Keys returns the keys of the map m. |
| // Note that map keys (here called type K) must be comparable. |
| func Keys(type K, V comparable(K))(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 |
| |
| type Set(type Elem comparable) map[Elem]struct{} |
| |
| func Make(type Elem comparable)() Set(Elem) { |
| return make(Set(Elem)) |
| } |
| |
| func (s Set(Elem)) Add(v Elem) { |
| s[v] = struct{}{} |
| } |
| |
| func (s Set(Elem)) Delete(v Elem) { |
| delete(s, v) |
| } |
| |
| func (s Set(Elem)) Contains(v Elem) bool { |
| _, ok := s[v] |
| return ok |
| } |
| |
| func (s Set(Elem)) Len() int { |
| return len(s) |
| } |
| |
| func (s Set(Elem)) Iterate(f func(Elem)) { |
| 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, show 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, r.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 returns 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" |
| |
| type Metric1(type T comparable) 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 has 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 returns 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. |
| |
| #### Dot product |
| |
| A generic dot product implementation that works for slices of any |
| numeric type. |
| |
| ```Go |
| // Numeric is a contract that matches any numeric type. |
| // It would likely be in a contracts package in the standard library. |
| contract Numeric(T) { |
| T int, int8, int16, int32, int64, |
| uint, uint8, uint16, uint32, uint64, uintptr, |
| float32, float64, |
| complex64, complex128 |
| } |
| |
| func DotProduct(type T Numeric)(s1, s2 []T) T { |
| if len(s1) != len(s2) { |
| panic("DotProduct: slices of unequal length") |
| } |
| var r T |
| for i := range s1 { |
| r += s1[i] * s2[i] |
| } |
| return r |
| } |
| ``` |
| |
| #### Absolute difference |
| |
| Compute the absolute difference between two numeric values, by using |
| an `Abs` method. |
| This uses the same `Numeric` contract defined in the last example. |
| |
| This example uses more machinery than is appropriate for the simple |
| case of computing the absolute difference. |
| It is intended to show how the common part of algorithms can be |
| factored into code that uses methods, where the exact definition of |
| the methods can very based on the kind of type being used. |
| |
| ```Go |
| // NumericAbs matches numeric types with an Abs method. |
| contract NumericAbs(T) { |
| Numeric(T) |
| T Abs() T |
| } |
| |
| // AbsDifference computes the absolute value of the difference of |
| // a and b, where the absolute value is determined by the Abs method. |
| func AbsDifference(type T NumericAbs)(a, b T) T { |
| d := a - b |
| return d.Abs() |
| } |
| ``` |
| |
| We can define an `Abs` method appropriate for different numeric types. |
| |
| ```Go |
| // OrderedNumeric matches numeric types that support the < operator. |
| contract OrderedNumeric(T) { |
| T int, int8, int16, int32, int64, |
| uint, uint8, uint16, uint32, uint64, uintptr, |
| float32, float64 |
| } |
| |
| // Complex matches the two complex types, which do not have a < operator. |
| contract Complex(T) { |
| T complex64, complex128 |
| } |
| |
| // OrderedAbs is a helper type that defines an Abs method for |
| // ordered numeric types. |
| type OrderedAbs(type T OrderedNumeric) T |
| |
| func (a OrderedAbs(T)) Abs() OrderedAbs(T) { |
| if a < 0 { |
| return -a |
| } |
| return a |
| } |
| |
| // ComplexAbs is a helper type that defines an Abs method for |
| // complex types. |
| type ComplexAbs(type T Complex) T |
| |
| func (a ComplexAbs(T)) Abs() T { |
| r := float64(real(a)) |
| i := float64(imag(a)) |
| d := math.Sqrt(r * r + i * i) |
| return T(complex(d, 0)) |
| } |
| ``` |
| |
| We can then define functions that do the work for the caller by |
| converting to and from the types we just defined. |
| |
| ```Go |
| func OrderedAbsDifference(type T OrderedNumeric)(a, b T) T { |
| return T(AbsDifference(OrderedAbs(T)(a), OrderedAbs(T)(b))) |
| } |
| |
| func ComplexAbsDifference(type T Complex)(a, b T) T { |
| return T(AbsDifference(ComplexAbs(T)(a), ComplexAbs(T)(b))) |
| } |
| ``` |
| |
| It's worth noting that this design is not powerful enough to write |
| code like the following: |
| |
| ```Go |
| // This function is INVALID. |
| func GeneralAbsDifference(type T Numeric)(a, b T) T { |
| switch a.(type) { |
| case int, int8, int16, int32, int64, |
| uint, uint8, uint16, uint32, uint64, uintptr, |
| float32, float64: |
| return OrderedAbsDifference(a, b) // INVALID |
| case complex64, complex128: |
| return ComplexAbsDifference(a, b) // INVALID |
| } |
| } |
| ``` |
| |
| The calls to `OrderedAbsDifference` and `ComplexAbsDifference` are |
| invalid, because not all the types that satisfy the `Numeric` contract |
| can satisfy the `OrderedNumeric` or `Complex` contracts. |
| Although the type switch means that this code would conceptually work |
| at run time, there is no support for writing this code at compile |
| time. |
| This another of way of expressing one of the omissions listed above: |
| this design does not provide for specialization. |