This proposal is largely based on my previous proposal, which can be found here. It had a few problems though, which are fixed by this proposal.
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.
Do these problems really justify creating an entirely new construct that has a very similar purpose to something we already have?
This proposal is about contract embedding, which describes the idea that contracts should be embedded into interfaces, rather than being a separate construct.
A type would satisfy the interface under the old definition, along with also satisfying any contracts embedded into the interface.
Some common interfaces people may be using:
// Describes bool, complex, pointers, channels, interfaces,
// some structs, some arrays, strings, floats, and ints.
type Comparable(type T) interface {
contract(t T) {
t == t
}
}
// describes strings, floats, ints, and complex
type Addable(type T) interface {
contract(t T) {
t + t
}
}
// describes strings, floats, and ints
type Orderable(type T) interface {
Comparable(T)
contract(t T) {
t < t
}
}
// describes floats, ints, and complex
type Numeric(type T) interface {
Comparable(T)
contract(t T) {
t + t
t - t
t * t
t / t
}
}
// describes floats and ints
type RealNumeric(type T) interface {
Orderable(T)
contract(t T) {
t + t
t - t
t * t
t / t
}
}
// describes ints
type BinaryNumeric(type T) interface {
RealNumeric(T)
contract(t T) {
t >> t
t << t
t & t
t | t
t &^ t
}
}
For a Graph
interface (doesn't require contracts at all):
type Edge interface {
Nodes() (Node, Node)
}
type Node interface {
Edges() []Edge
}
type Graph(type N Node, E Edge) struct {
// ...
}
func NewGraph(type N Node, E Edge)(init []N) *Graph(N, E) {
// ....
}
For a Sum
method:
type Addable(type T) interface {
contract(t T) {
t + t
}
}
func Sum(type T Addable(T))(t ...T) {
var sum T
for _, elem := range t {
sum += elem // since + is defined for T, we may do this
}
return sum
}
func main() {
Sum(5, 6, 9) // returns 20
Sum("woah", "strings") // returns "woahstrings"
}
Doing this we could also redefine a lot of the things in builtin.go
. Doing this
could clarify a lot of the functionality in Go. For instance, we don't need to pull
out the spec to see that complex
types aren't orderable, all we'd need to do is
look at the numeric
interface.
type bool comparable(bool)
type complex64 numeric(complex64)
type complex128 numeric(complex128)
type string interface { orderable(string); addable(string) }
type float32 realnumeric(float32)
type float64 realnumeric(float64)
type int binarynumeric(int)
type int8 binarynumeric(int8)
type int16 binarynumeric(int16)
type int32 binarynumeric(int32)
type int64 binarynumeric(int64)
type uint binarynumeric(uint)
type uint8 binarynumeric(uint8)
type uint16 binarynumeric(uint16)
type uint32 binarynumeric(uint32)
type uint64 binarynumeric(uint64)
type uintptr binarynumeric(uintptr)
type rune = int32
type byte = uint8
This solves both of the main problems with the last proposal
that I had made. Because we have contracts, we can define
len
ers, cap
ers, etc. We are also able to use contracts to
only identify complex numbers with contract(t T) { imag(t) }
.
I think this would be a good way to unify the ideas of contracts and interfaces into a singular, simple, and powerful idea.
I actually like this.
👍