diff --git a/design/go2draft-contracts.md b/design/go2draft-contracts.md index cdc04504..2922cdad 100644 --- a/design/go2draft-contracts.md +++ b/design/go2draft-contracts.md @@ -2,19 +2,27 @@ Ian Lance Taylor\ Robert Griesemer\ + August 27, 2018 +Modified by Scott Cotton +Sep 24, 2018 +[diff](https://github.com/golang/proposal/compare/master...wsc1:master) + ## Abstract We suggest extending the Go language to add optional type parameters to types and functions. Type parameters may be constrained by contracts: they may be used as ordinary types that only support the operations described by the -contracts. +contracts. +Contracts are thus extensions of interfaces which allow +specifying more than method names. + 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. +The design can be fully backward compatible with Go 1. + For more context, see the [generics problem overview](go2draft-generics-overview.md). @@ -54,7 +62,18 @@ similarities but are not always the same. ## Design -We will describe the complete design in stages based on examples. +We will describe the complete design by + +1. First, supposing that Go has no interface types. +1. Second we will build a complete design by means of +examples and use of a new construct called _contracts_. +This will involve defining a new keyword, `contract`. +1. We will then replace the keyword `contract` with `interface`, +and define Go's current interfaces as syntactic sugar on +top of contracts which use the 'interface' keyword. + +The final steps gives backward compatibility, and unifies +the notion of contracts and interfaces. ### Type parameters @@ -113,6 +132,7 @@ At the call site, the `type` keyword is not used. Print(int)([]int{1, 2, 3}) ``` + ### Type contracts Let’s make our example slightly more complicated. @@ -176,9 +196,10 @@ contract between the generic code and calling code. ### Contract syntax -In this design, a contract has the same general form as a function. -The function body is never executed. -Instead, it describes, by example, a set of types. +In this design, a contract is a type declaration whose body has the same general +form as a function, with one exception. +The body is never executed. +Instead, it describes, a set of types by means of example usage. For the `Stringify` example, we need to write a contract that says that the type has a `String` method that takes no arguments and @@ -186,19 +207,37 @@ returns a value of type `string`. Here is one way to write that: ```Go -contract stringer(x T) { - var s string = x.String() +type stringer(x T) contract { + var s string = x.String() +} +``` + +The above mechanism describes the type by example. +It can be used with arbitrariry +language constructs, such as giving examples of indexing with square brackets, +testing for equality, assignability, addressability, dereferencing pointers, etc. + +Contract bodies have one special syntax in addition to the description of the +types by means of examples. +In particular, a contract body may contain an type parameter, such as `T` in +above, following by a colon `:`, followed by a bracketed list of method +signatures as in Go's interfaces today. + +For example, one may write + +```Go +type stringer(x T) contract { + T: { + String() string + } } ``` -A contract is introduced with a new keyword `contract`. -The definition of a contract looks like the definition of a function, -except that the parameter types must be simple identifiers. ### Using a contract to verify type arguments A contract serves two purposes. -First, contracts are used to validate a set of type arguments. +First, contracts are used to validate 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 @@ -208,9 +247,7 @@ error: the call is using types that the function’s contract does not permit. To validate the type arguments, each of the contract’s parameter types -is replaced with the corresponding type argument (there must be -exactly as many type arguments as there are parameter types; contracts -may not be variadic). +is replaced with the corresponding type argument The body of the contract is then type checked as though it were an ordinary function. If the type checking succeeds, the type arguments are valid. @@ -224,6 +261,32 @@ only type assignable to `string` is, in fact, `string`.) If any of those statements about the type argument are not true, the contract body will fail when it is type checked. +#### Unary contracts as types +Contracts which have only one type argument are unary. They thus +describe a restriction on a type. +As such, they may be used just +as ordinary types. +When they are used as ordinary types, the +the type argument is implicitly the type of the value which is asserted +to implement the contract. For example + +```Go +type stringer(x T) contract { + T: { + String() string + } +} +var er stringer +``` + +describes the variable `er` with type stringer. er is not a concrete value +but rather a boxed value that must conform to `stringer`. Unary contracts +which are applied as types also validates the (implicit) type argument of +the associated value. + + + + ### The party of the second part A contract is not used only at the call site. @@ -284,6 +347,55 @@ has type parameters; when validating the contract, the type parameters are passed to the function in the order in which they appear in the function definition. +#### Using a contract as a type +The Stringify example above shows a critical difference between contracts +and interfaces. +Today one may define a variation of Stringify above without +contracts. +```Go +type Stringer interface { + String() string +} /* builtin */ +func Stringify(s []Stringer) (ret []string) { + for _, v := range s { + ret = append(re, v.String()) + } + return ret +} +``` + +But this is in fact different. +Notably, each element of s may have a different +type and thus must be boxed and the method call to String() must be looked up +for each element. We call this _variadic_ type instantiation. +In the contracts version, each element in s is of one +concrete type. + +In many situations one of the two behaviors is desireable, in some +certainly a mix of the two behaviors is desireable. + +To achieve _variadic_ type instantiation of a contract, we use can +use it directly as a type + +```Go +func Stringify(s []stringer) (ret []string) { + for _, v := range s { + ret = append(ret, v.String()) // now valid + } + return ret +} +``` + +This has the effect of instructing the compiler to box values +like `v` in the example above in the same way interfaces are currently +implemented. + +This only works when the contract has one type parameter. +When a contract +has more than one type parameter, it is no longer really a type but rather +a relation between types. + + ### Contract syntactic details Before we continue, let’s cover a few details of the contract syntax. @@ -298,7 +410,7 @@ For example, this simple contract says that a value of type `From` may be converted to the type `To`. ```Go -contract convertible(_ To, f From) { +type convertible(_ To, f From) contract { To(f) } ``` @@ -334,7 +446,7 @@ a bit more convenient when we get to type inference. #### Restrictions on contract bodies -Although a contract looks like a function body, contracts may not +Although a contract body looks like a function body, contracts may not themselves have type parameters. Contracts may also not have result parameters, and it follows that they may not use a `return` statement to return values. @@ -345,11 +457,11 @@ This rule is intended to make it harder to accidentally change the meaning of a contract. As a compromise, a contract is permitted to refer to names imported from other packages, permitting a contract to easily say things like -"this type must support the `io.Reader` interface:" +"this type must support the `io.Reader` contract:" by embedding. ```Go -contract Readable(r T) { - io.Reader(r) +type Readable(r T) contract { + io.Reader(T) } ``` @@ -368,36 +480,6 @@ Of course, it is completely pointless to use a `goto` statement, or a `break`, `continue`, or `fallthrough` statement, in a contract body, as these statements do not say anything about the type arguments. -#### The contract keyword - -Contracts may only appear at the top level of a package. - -While contracts could be defined to work within the body of a -function, it’s hard to think of realistic examples in which they would -be useful. -We see this as similar to the way that methods can not be defined -within the body of a function. -A minor point is that only permitting contracts at the top level -permits the design to be Go 1 compatible. - -There are a few ways to handle the syntax: - -* We could make `contract` be a keyword only at the start of a - top-level declaration, and otherwise be a normal identifier. -* We could declare that if you use `contract` at the start of a - top-level declaration, then it becomes a keyword for the duration of - that package. -* We could make `contract` always be a keyword, albeit one that can - only appear in one place, in which case this design is not Go 1 - compatible. - -#### Exported contracts - -Like other top level declarations, a contract is exported if its name -starts with an upper-case letter. -An exported contract may be used by functions, types, or contracts in other -packages. - ### Multiple type parameters Although the examples shown so far only use a single type parameter, @@ -417,11 +499,11 @@ 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. +Although functions may have multiple type parameters, these type parameter +may only have a single contract. ```Go -contract viaStrings(t To, f From) { +type viaStrings(t To, f From) contract { var x string = f.String() t.Set(string("")) // could also use t.Set(x) } @@ -435,10 +517,39 @@ func SetViaStrings(type To, From viaStrings)(s []From) []To { } ``` +Note however, since unary contracts are types, one may have contracts +as types in addition to as type parameters. An example follows. + +```Go +type stringer(x T) contract { + T: { + String() string + } +} +type viaStrings(t To, f From) contract { + var x string = f.String() + t.Set(string("")) // could also use t.Set(x) +} + +func SetViaStringsWith(type To, From viaStrings)(s []From, with stringer) []To { + r := make([]To, len(s)) + for i, v := range s { + r[i].Set(fmt.Sprintf("%s-%s", v.String(), with)) + } + return r +} +``` + +The restriction for a single contract thus only applies to the type parameters. + ### Parameterized types -We want more than just generic functions: we also want generic types. -We suggest that types be extended to take type parameters. +The contracts mechanism above provides one way of creating parameterized types +and generic functions. +But we want generic types for other type expressions as +well. +We suggest that other type expressions be extended to take type +parameters as well ```Go type Vector(type Element) []Element @@ -458,6 +569,16 @@ This is called _instantiation_. var v Vector(int) ``` + +The same works with contracts +```Go +type C(t T) contract { + t + t +} +var v C(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. @@ -478,6 +599,7 @@ type List(type Element) struct { val Element } + // This is INVALID. type P(type Element1, Element2) struct { F *P(Element2, Element1) // INVALID; must be (Element1, Element2) @@ -541,7 +663,7 @@ Where it would be useful to add type arguments to a method, people will have to write a top-level function. Making this decision avoids having to specify the details of exactly -when a method with type arguments implements an interface. +when a method with type arguments supports a contract. (This is a feature that can perhaps be added later if it proves necessary.) @@ -558,8 +680,8 @@ contract call. This contract embeds the contract `stringer` defined earlier. ```Go -contract PrintStringer(x X) { - stringer(X) +type PrintStringer(x X) contract { + stringer(x) x.Print() } ``` @@ -567,7 +689,7 @@ contract PrintStringer(x X) { This is roughly equivalent to ```Go -contract PrintStringer(x X) { +type PrintStringer(x X) contract { var s string = x.String() x.Print() } @@ -587,7 +709,7 @@ package comparable // The equal contract describes types that have an Equal method for // the same type. -contract equal(v T) { +type equal contract(v T) { // All that matters is type checking, so reusing v as the argument // means that the type argument must have a Equal method such that // the type argument itself is assignable to the Equal method’s @@ -648,7 +770,7 @@ like finding the shortest path. ```Go package graph -contract G(n Node, e Edge) { +type G(n Node, e Edge) contract { var _ []Edge = n.Edges() var from, to Node = e.Nodes() } @@ -659,12 +781,12 @@ func (*Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { ... } ``` At first glance it might be hard to see how this differs from similar -code using interface types. +code using boxed types/variadic type instantiation. The difference is that although `Node` and `Edge` have specific -methods, they are not interface types. +methods, they are not boxed. 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. +don’t have to actually use boxing/variadic instantiation to do so. For example, consider these type definitions in some other package: @@ -675,32 +797,19 @@ type FromTo struct { ... } type (ft *FromTo) Nodes() (*Vertex, *Vertex) { ... } ``` -There are no interface types here, but we can instantiate -`graph.Graph` using the type arguments `*Vertex` and `*FromTo`: +There is no boxing/variadic type instantiation here, but we can instantiate +`graph.Graph` statically using the type arguments `*Vertex` and `*FromTo`: ```Go var g = graph.New(*Vertex, *FromTo)([]*Vertex{ ... }) ``` -`*Vertex` and `*FromTo` are not interface types, but when used -together they define methods that implement the contract `graph.G`. -Because of the way that the contract is written, we could also use the -non-pointer types `Vertex` and `FromTo`; the contract implies that the -function body will always be able to take the address of the argument -if necessary, and so will always be able to call the pointer method. - -Although `Node` and `Edge` do not have to be instantiated with -interface types, it is also OK to use interface types if you like. - -```Go -type NodeInterface interface { Edges() []EdgeInterface } -type EdgeInterface interface { Nodes() (NodeInterface, NodeInterface) } -``` - -We could instantiate `graph.Graph` with the types `NodeInterface` and -`EdgeInterface`, since they implement the `graph.G` contract. -There isn’t much reason to instantiate a type this way, but it is -permitted. +When `*Vertex` and `*FromTo` are used together they define methods that +implement the contract `graph.G`. Because of the way that the contract is +written, we could also use the non-pointer types `Vertex` and `FromTo`; the +contract implies that the function body will always be able to take the address +of the argument if necessary, and so will always be able to call the pointer +method. This ability for type parameters to refer to other type parameters illustrates an important point: it should be a requirement for any @@ -711,13 +820,10 @@ ways that the compiler can check. ### Values of type parameters are not boxed In the current implementations of Go, interface values always hold -pointers. -Putting a non-pointer value in an interface variable causes the value -to be _boxed_. -That means that the actual value is stored somewhere else, on the heap -or stack, and the interface value holds a pointer to that location. +pointers. Recall that in this design, there are no interfaces to consider +as of yet, we have assumed temporarily that Go has no interfaces. -In contrast to interface values, values of instantiated polymorphic types are not boxed. +Values of instantiated polymorphic types are not boxed. For example, let’s consider a function that works for any type `T` with a `Set(string)` method that initializes the value based on a string, and uses it to convert a slice of `string` to a slice of `T`. @@ -725,7 +831,7 @@ string, and uses it to convert a slice of `string` to a slice of `T`. ```Go package from -contract setter(x T) { +type setter(x T) contract { var _ error = x.Set(string) } @@ -943,6 +1049,8 @@ 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 @@ -966,7 +1074,7 @@ Sometimes, though, it’s possible to use a more efficient implementation for some type arguments. The language already has mechanisms for code to find out what type it is working with: type assertions and type switches. -Those are normally only permitted with interface types. +Those are normally, in current Go1, only permitted with interface types. In this design, functions are also permitted to use them with values whose types are type parameters, or based on type parameters. @@ -976,13 +1084,14 @@ 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. +argument that is not a variadic type instantiation/boxed type (fancy talk for +Go1 interface). ```Go -contract byteReader(x T) { +type byteReader(x T) contract { // This expression says that x is convertible to io.Reader, or, // in other words, that x has a method Read([]byte) (int, error). - io.Reader(x) + io.Reader(T) } func ReadByte(type T byteReader)(r T) (byte, error) { @@ -1028,7 +1137,7 @@ This is only an issue when the type is instantiated in place. ### Reflection -We do not propose to change the reflect package in any way. +We do not propose to change the reflect package in any significant 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 @@ -1039,6 +1148,12 @@ 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. +However, all references to interfaces in package reflect would apply +to their new implementation in this design, which is essentially +unary contracts. As many ways exist to enforce existence of methods +by contracts, only those methods which are explicitly declared are +available via methods of reflect.Type. + ### Contracts details Let’s take a deeper look at contracts. @@ -1091,14 +1206,12 @@ simple assignment statement like the ones shown above. * There is no way to distinguish a method call from a call of a struct field with function type. -When a contract needs to describe one of these cases, it can use a -type conversion to an interface type. -The interface type permits the method to be precisely described. -If the conversion to the interface type passes the type checker, then -the type argument must have a method of that exact type. +When a contract needs to describe one of these cases, it can +specify them using the method list syntax, or via embedding +a contract which does this. -An explicit method call, or a conversion to an interface type, can not -be used to distinguish a pointer method from a value method. +When methods are listed, one cannot distinguish a pointer method from a value +method in example based code. When the function body calls a method on an addressable value, this doesn’t matter; since all value methods are part of the pointer type’s method set, an addressable value can call either pointer methods or @@ -1108,7 +1221,7 @@ However, it is possible to write a function body that can only call a value method, not a pointer method. For example: ```Go -contract adjustable(x T) { +type adjustable(x T) contract { var _ T = x.Adjust() x.Apply() } @@ -1129,7 +1242,7 @@ method. That can be done like this: ```Go -contract adjustable(x T) { +type adjustable(x T) contract { var _ T = x.Adjust() var f func() T f().Apply() @@ -1140,6 +1253,8 @@ The rule is that if the contract body contains a method call on a non-addressable value, then the function body may call the method on a non-addressable value. +NB(wsc): this is a wart. + #### Operators Method calls are not sufficient for everything we want to express. @@ -1169,7 +1284,7 @@ not use `==` to compare values of slice, map, or function type). This is easy to address using a contract. ```Go -contract comparable(x T) { +type comparable(x T) contract { x == x } @@ -1223,7 +1338,7 @@ numeric types: ```Go package check -contract convert(t To, f From) { +type convert(t To, f From) contract { To(f) From(t) f == f @@ -1241,7 +1356,7 @@ func Convert(type To, From convert)(from From) To { Note that the contract needs to explicitly permit both converting `To` to `From` and converting `From` to `To`. The ability to convert one way doesn’t necessarily imply being able to -convert the other way; consider `check.Convert(int, interface{})(0, 0)`. +convert the other way; consider (in current Go1) `check.Convert(int, interface{})(0, 0)`. #### Untyped constants @@ -1252,7 +1367,7 @@ This is most naturally written as an assignment from an untyped constant. ```Go -contract untyped(x T) { +type untyped(x T) contract { x = 0 } ``` @@ -1277,7 +1392,7 @@ If the contract did not say `x = 1000`, the expression `v + 1000` would be invalid. ```Go -contract add1K(x T) { +type add1K(x T) contract { x = 1000 x + x } @@ -1339,7 +1454,7 @@ unnecessary conversions to `[]byte` in order to call `append` and `copy`, but perhaps the compiler can eliminate those. ```Go -contract strseq(x T) { +type strseq(x T) contract { []byte(x) T([]byte{}) len(x) @@ -1378,11 +1493,11 @@ The contract body can use `var y = x.f` to describe the field’s type. ```Go package move -contract counter(x T) { +type counter(x T) contract { var _ int = x.Count } -contract counters(T1, T2) { // as with a func, parameter names may be omitted. +type counters(T1, T2) contract { // as with a func, parameter names may be omitted. // Use contract embedding to say that both types must have a // Count field of type int. counter(T1) @@ -1414,6 +1529,7 @@ exhaustive set of rules describing when a contract body cannot be satisfied. It may be appropriate to add a vet check for this, if possible. +NB(wsc): sounds like a SAT problem to me. ### Implementation @@ -1665,6 +1781,10 @@ the type arguments from the regular arguments. The current design seems to be the best, but perhaps something better is possible. +NB(wsc): suggest to use square brackets for function type params and parens for +type type params. + + ##### What does := mean in a contract body? If a contract body uses a short declaration, such as @@ -1681,6 +1801,7 @@ It’s less clear what it permits in the function using this contract. For example, does it permit the function to call the `String` method and assign the result to a variable of empty interface type? + ##### Pointer vs. value methods in contracts It seems that the natural ways to write a contract calling for certain @@ -1707,6 +1828,132 @@ will have only non-existent or trivial requirements on their type parameters. More experience will be needed to see whether this is a problem. +##### Unary contracts as types applied with example code + +If a unary contract such as +```Go +type addOp(x T) contract { + var z int = x + x +} +``` + +is used as a type, for example +```Go +func F(x addOp) { + z := x + x +} +``` + +Then there is no method implementing the addition operator, since +x is boxed. + +Numerous discussions have expressed the idea of using builtin methods +which correspond to usage examples and operators, such as in Python's +`__add__` method. + +For the moment, this design only supports implementations of actual +Go methods, as in the existing Go1 interfaces. This is not a problem +of compatibility, but it is an unnatural restriction. +If and when defining some builtin operations via methods becomes possible, +then they may be dispatched with Go2 contracts as in this design. + +This later has been proposed as part of the feedback +[here](https://gist.github.com/rogpeppe/0a87ef702189689201ef1d4a170939ac). +also +[here](https://gist.github.com/pat42smith/ed63aca983d4ba14fdfa320296211f40). +and +[here](https://gist.github.com/deanveloper/c495da6b9263b35f98b773e34bd41104) + +Note that in this design, the only problem is for finding an implementation +of the the boxed case/variadic instantiation, but that for operator +overloading the problem is more general and applies also to static type +parameter instantiation as well. + +One possible way to deal with this is to have a special package which +takes pragma commens, like "C" is a special package for cgo whose pragma +allow for #include's and the like, and then to use an approach similar to +that suggested +[here](https://gist.github.com/rogpeppe/0a87ef702189689201ef1d4a170939ac). + +For example: +```Go +// bind[+]: fix.Add +// bind[*]: fix.Mul +// bind[0]: Zero +// bind[1]: One +import _ "operator/bind" + +// Q 32.32 fixed point type +type fix uint64 +const ( + Zero fix = 0 + One = fix(1<<32) +) +func (f fix) Add(a, b fix) fix { + return a+b +} + +func (f fix) Mul(a, b fix) fix { + var res fix + // this one is more complicated, implementation omitted. + return res +} +``` + +This would give the compiler a compile time way of associating methods of +numeric types to operators. +It is also a little bit ugly, which might be +a good thing to help restrict its usage to appropriate places. +Note also that this example brings up the problem of the value for 1, which +might well reasonably vary for numeric implementations. +Such strange things that pop up with operator overloading motivate +removing it from the type system and placing it in special package. + +Supposing the above were in place, let us examine some examples. + +```Go +// a multiplication contract, it has a one. +type Multipliable(a, b, c T) contract { + var one T = 1 + c = a*b +} + +// static type instantiation. +func MulsStatic(type T Multipliable)(vals ...T) T { + var res T = 1 + for _, v := range vals { + res = res * v + } + return res +} + +// the variadic/dynamic/boxed case +func MulsBoxed(vals ...Multipliable) Multipliable { + var res Multipliable = 1 + for _, v := range vals { + res = res * v + } + return res +} +``` + +The fixed point case is an interesting test for operator overloading which +shows that normal types without constant values may not suffice. +It is perhaps too much to try to support, but it is also a more reasonable use case for operator +overloading than say using `+` for slice concatenation. + +Binding methods to operators by means of pragma can simplify the type system. + +The example above would provide a means of implementing some of the example based +contract bodies for variadic instantiation/boxing. + +At any rate, all interfaces today only have listed methods, and having contracts +require operator overloading may be just too much in one shot. The example above +mostly shows that binding methods to operators can fit into this contracts +draft proposal in a way that to a reasonable extent addresses the wart of +using example based unary contracts as types. + + #### Discarded ideas This design is not perfect, and it will be changed as we gain @@ -1717,24 +1964,6 @@ This section lists some of those ideas in the hopes that it will help to reduce repetitive discussion. The ideas are presented in the form of a FAQ. -##### Why not use interfaces instead of contracts? - -_The interface method syntax is familiar._ -_Writing contract bodies with `x + x` is ordinary Go syntax, but it_ -_is stylized, repetitive, and looks weird._ - -It is unclear how to represent operators using interface methods. -We considered syntaxes like `+(T, T) T`, but that is confusing and -repetitive. -Also, a minor point, but `==(T, T) bool` does not correspond to the -`==` operator, which returns an untyped boolean value, not `bool`. -We also considered writing simply `+` or `==`. -That seems to work but unfortunately the semicolon insertion rules -require writing a semicolon after each operator at the end of a line. -Using contracts that look like functions gives us a familiar syntax at -the cost of some repetition. -These are not fatal problems, but they are difficulties. - ##### Why not put type parameters on packages? We investigated this extensively. @@ -1763,6 +1992,9 @@ 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. +NB(wsc): it might be nice to use squre brackets for function type parameters +and parens for type type parameters. + ##### Why not use `F«T»`? We considered it but we couldn’t bring ourselves to require @@ -1827,7 +2059,7 @@ need for boilerplate definitions in order to use `sort.Sort`. With this design, we can add to the sort package as follows: ```Go -contract ordered(e Ele) { e < e } +type ordered(e Ele) contract { e < e } type orderedSlice(type Ele ordered) []Ele @@ -1967,7 +2199,7 @@ methods rather than operators like `[]`. // Package set implements sets of any type. package set -contract comparable(Ele) { Ele == Ele } +type comparable(Ele) contract { Ele == Ele } type Set(type Ele comparable) map[Ele]struct{} @@ -2101,7 +2333,7 @@ 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. + not using pointers and as not boxed values. ```Go // Package orderedmap provides an ordered map, implemented as a binary tree. @@ -2324,11 +2556,11 @@ package metrics import "sync" -contract comparable(v T) { +type comparable(v T) contract { v == v } -contract cmp1(T) { +type cmp1(T) contract { comparable(T) // contract embedding } @@ -2346,7 +2578,7 @@ func (m *Metric1(T)) Add(v T) { m[v]++ } -contract cmp2(T1, T2) { +type cmp2(T1, T2) contract { comparable(T1) comparable(T2) } @@ -2370,7 +2602,7 @@ func (m *Metric2(T1, T2)) Add(v1 T1, v2 T2) { m[key(T1, T2){v1, v2}]++ } -contract cmp3(T1, T2, T3) { +type cmp3(T1, T2, T3) contract { comparable(T1) comparable(T2) comparable(T3) @@ -2559,3 +2791,94 @@ var ServerContextKey = context.NewKey(*Server)("http_server") 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. + +## Back to interfaces +Now suppose we +1. First replace the keyword 'contract' in this design with 'interface' and +1. Then make the case of Go1's interfaces +```Go +type X interface { + // ... as per Go 1 +} +``` +syntactic sugar for +```Go +type X(x T) interface { + T: { + // ... as per Go 1 + } +} +``` + +Then we have anonymous interfaces to deal with as well +```Go +var x interface{} +``` +this could similarly be rewritten to +```Go +var x (type _(x T) interface {}) +// (This require recognizing _ as an unnamed type.) +``` + +After doing the above, we have unified contracts and interfaces and made it +backward compatible (I think). + +## Discussion +This design has shown that contracts can be seen as an extension of interfaces. +I would imagine that it would be very convenient to avoid having separate +contracts for many existing interfaces. + +Some drawbacks have become clear in drafting this modification to the original +proposal. Mostly, there are example usages which forbid or currently forbid +being boxed. + +Another example is +```Go +type T(x X) contract { + *x +} +``` + +Such a contract cannot be boxed because Go doesn't allow derefencing boxed values, +unless it one day allows defining an operator for dereferencing. + +This phenomenon enlarges the space of "impossible contracts" substantially when +allowing unary contracts to act on runtime types, which is unfortunate. + +The counterbalance to these drawbacks are +* Many interfaces may need doubles as contracts if they are separate. +* Two constructs (contracts/interfaces) for overlapping functionality. +* non-orthogonality of constructs (defining type constraints vs applying them statically or dynamically) +* The drawbacks may reduce over time as user defined operators are considerred, whereas it +seems the complexity of implementing user defined operators may increase if contracts/interfaces are separate. + +type a(x T) { + // contract goes here + +} /* only if type monodic */ interface { + // +} + + + +type a(x T) { +} struct { +} + +type Er interface { +} + + +type Er(x T) { +} interface { +} + + + + + + + + + +