Generics as builtin typeclasses
I love the debate around the generics proposal for Go 2.
In the spirit of Go’s ‘good enough’ type system, here’s a thought experiment for ‘good enough’ generics. I think we can get 80% of the power for 20% of the complexity.
(‘Good enough’ is a compliment – I come from a C# background and find the simplicity of Go’s type system refreshing.)
I propose that the first version of Go’s generics consist of a finite-but-thorough set of builtin type classes, instead of user-defined contracts. Usage might look like:
func Sum(type T numeric)(x []T) T {
var total T
for _, v := range x {
total += v
}
return total
}
or
type Set(type T comparable) []T
…where numeric
and comparable
are typeclasses, defined below. In fact, the go/types
package already defines many type classes, without using that terminology.
Instead of covering the full design space of what generics can do, we focus on an intuitive set of typeclasses that meets a broad set of needs.
Typeclasses
numeric = integer | float | complex
// supports +, -, *, / operators, enabling generic Sum, Average, etc
comparable = [requires logic]
// supports == operator, enabling generic Set
// Defined here: https://golang.org/ref/spec#Comparison_operators
orderable = integer | float | string
// supports < and > operators, enabling generic Min, Max, Sort
integer = int | int8 | int16 | int32 | int64| uint | uint8 | uint16 | uint32 | uint64 | uintptr | byte | rune
float = float32 | float64
complex = complex64 | complex128
Assignability
Typeclasses are not assignable types, they are (optional) constraints on generic type declarations.
// Wrong
type Foo struct {
Age numeric
}
// Right
type Foo(type T numeric) struct {
Age T
}
// Constructor example
func NewFoo(type T numeric)(age T) Foo(T) {
foo := Foo(T) {
Age : age,
}
return foo
}
What this reminds me of
Typeclasses look a lot like union types. int8
gets unioned into integer
which gets unioned into numeric
, etc.
Another way to think of type classes is as interfaces for operators. Imagine that the +
operator were an Add()
method instead. The numeric
type above is a lot like an Adder
interface.