Skip to content

Instantly share code, notes, and snippets.

@deanveloper
Last active November 18, 2020 08:34
Show Gist options
  • Save deanveloper/9063720344d7a041795cba778d7de77c to your computer and use it in GitHub Desktop.
Save deanveloper/9063720344d7a041795cba778d7de77c to your computer and use it in GitHub Desktop.
Contract Embedding - A revised version of my previous proposal, https://gist.github.com/deanveloper/c495da6b9263b35f98b773e34bd41104

Unifying the ideas of Contracts and Interfaces - Contract Embedding

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?

Proposal

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 leners, capers, 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.

@deanveloper
Copy link
Author

After further thought, I believe a better syntax would be

type comparable(type T) interface {
	contract {
		var t T
		t == t
	}
}

I think it looks better for the contract to not have parameters, even if it would mean having to write var t T at the beginning of each contract

@deanveloper
Copy link
Author

I made a small edit in my parametric functions since I used incorrect syntax (func (type ...) Name(args) instead of func Name (type ...)(args))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment