blob: 9cb83ee44c641863a4570e0ac85452f491f9d35e [file] [log] [blame] [view]
# 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.