This is my second proposal based on Lance Taylor and Robert Griesemer's proposal for Go 2 "generics" (Contracts — Draft Design). Although they are very similar, it is a simplification of the original but keeps its capabilities.
According the Contracts proposal, "generic code is code that is written using types that will be specified later". To achieve that, it defines generic functions using an unspecified type called type parameter. When the code is used, the type parameter is set to a type argument.
package main
import (
"fmt"
)
func Print(type T)(s []T) {
for _, v := range s {
fmt.Println(v)
}
}
func main() {
Print(int)([]int{1, 2, 3})
}
Above, the Print
function is defined with a type paramenter (type T
). That definition states that the Print
function could receive a slice of any type. In the main
function, Print
is called with a type argument (int
). So, the compiler "instantiates" a Print
function that accept a slice of integer ([]int
) as its argument.
Besides the generic functions, the Contracts proposal also defines generic types using type parameter and type argument.
type Vector(type Element) []Element
var v Vector(int)
Above, Vector
is a type that defines a slice of any type. Therefore, the variavle v
is an instance of the type Vector
and a slice of integer ([]int
).
Contracts are structures used to constrain generic functions to maintain strict type rules at compile-time. This is an example from the Contracts proposal ⇗, the Stringify
function converts a slice of some type into a slice of strings ([]string
) by calling a String
method on each element.
func Stringify(type T stringer)(s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return ret
}
The Stringify
function will work if type T
has a String
method. But if doesn't, it will fail. The contract stringer
(see the below code) allow the compiler to verify the contrainsts of type T
to work with Stringify
.
contract stringer(x T) {
var s string = x.String()
}
The code inside a contract will never run, it is only used by compiler to:
- Validate a set of type arguments. 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 are invalid, the compiler will report a type error: the call is using types that the function’s contract does not permit.
- Describe what the function with type parameters, is permitted to do with those type parameters.
So, contract intend to be used for type checking of generic functions.
Using the type argument in the generic functions causes a complex syntax with a "lots of irritating silly parentheses". Therefore, this propose support to use type argument ONLY with generic types. Thereby, the contracts would be used for type checking of variables with generic type, instead checking generic functions. Following are the earlier examples written with generic types.
package main
import (
"fmt"
)
type Vector(type T) []T
func Print(s Vector) {
for _, v := range s {
fmt.Println(v)
}
}
func main() {
Print(Vector(int){1, 2, 3})
}
contract stringer(x T) {
var s string = x.String()
}
type Vector(type T stringer) []T
func Stringify (s Vector) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return ret
}
Variables are instatiations of a generic type. Bellow, the Elem
is a generic type that can be incremented, then variable v
is a slice of Elem
instatiated as an integer.
contract incrementable(x T) {
var _ T = x + 1
}
type Elem(type T incrementable) T
func Increment(s []Elem(t)) (ret []Elem(t)) {
for _, v := range s {
ret = append(ret, v + 1)
}
return ret
}
...
v := []Elem(int){1, 2, 3}
v = Increment(v)
contract comparable(x T) {
x == x
}
type Elem(type T comparable) T
func Contains(s []Elem, e Elem) bool {
for _, v := range s {
if v == e {
return true
}
}
return false
}
contract convertible(_ To, f From) {
To(f)
}
type Value(type T convertible(uint64, T)) T
func FormatUnsigned(v Value) string {
return strconv.FormatUint(uint64(v), 10)
}
...
s := FormatUnsigned('a')
contract viaStrings(t To, f From) {
var x string = f.String()
t.Set(string(""))
}
type ElemTo(type To, From viaStrings) To
type ElemFrom(type To, From viaStrings) From
func SetViaStrings(s []ElemFrom(t,f)) []ElemTo(t,f) {
r := make([]ElemTo(t,f), len(s))
for i, v := range s {
r[i].Set(v.String())
}
return r
}
package comparable
contract equal(v T) {
var x bool = v.Equal(v)
}
type Elem(type T equal) T
func Index(s []Elem, e Elem) int {
for i, v := range s {
if e.Equal(v) {
return i
}
}
return -1
}
import "comparable"
type EqualInt comparable.Elem(int)
func (a EqualInt) Equal(b EqualInt) bool { return a == b }
func Index(s []EqualInt, e EqualInt) int {
return comparable.Index(s, e)
}
package check
contract convert(t To, f From) {
To(f)
From(t)
f == f
}
type ElemTo(type To, From convert) To
type ElemFrom(type To, From convert) From
func Convert(from ElemFrom(t,f)) ElemTo(t,f) {
to := ElemTo(t,f)(from)
if ElemFrom(t,f)(to) != from {
panic("conversion out of range")
}
return to
}
contract add1K(x T) {
x = 1000
x + x
}
type Elem(type T add1K) T
func Add1K(s []Elem) {
for i, v := range s {
s[i] = v + 1000
}
}
contract strseq(x T) {
[]byte(x)
T([]byte{})
len(x)
}
type Elem(type T strseq) T
func Join(a []Elem(t), sep Elem(t)) (ret Elem(t)) {
if len(a) == 0 {
return ret
}
if len(a) == 1 {
return Elem(t)(append([]byte(nil), []byte(a[0])...))
}
n := len(sep) * (len(a) - 1)
for i := 0; i < len(a); i++ {
n += len(a[i])
}
b := make([]byte, n)
bp := copy(b, []byte(a[0]))
for _, s := range a[1:] {
bp += copy(b[bp:], []byte(sep))
bp += copy(b[bp:], []byte(s))
}
return Elem(t)(b)
}
package move
contract counter(x T) {
var _ int = x.Count
}
type Cont(type T counter) T
func Corresponding(p1 *Cont, p2 *Cont) {
p1.Count = p2.Count
}
contract ordered(e Ele) { e < e }
type orderedSlice(type T ordered) []T
func (s orderedSlice) Len() int {
return len(s)
}
func (s orderedSlice) Less(i, j int) bool {
return s[i] < s[j]
}
func (s orderedSlice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func OrderedSlice(s orderedSlice) {
sort.Sort(orderedSlice(orderedSlice))
}
You are right, the syntax of my propose is incomplete, so I just updated it. Now, the
viaStrings
example is like:The type arguments of
ElemFrom
andElemTo
are now explicitly declared in functionviaStrings
. I am not sure if it is a good solution, but is the only one I can see.You are right again. The propose doesn't address functions which only the result is parameterized.
Totally agree if you, I really hope you succeed.