Forgive me for making a new thread, I felt the older ones had run their course, and I had trouble deciding who to reply to.
I would like to propose a simpler syntax which looks a bit more Go-like (to me)
is more terse, has no ambiguity and does not require any new bracket types. Here it goes:
// generic type variables are always prefixed by an `@` symbol
type Heap(@T) struct {
data []@T
head, tail uint
}
// this is how it is instantiated:
var myHeapInt Heap@(int)
var myHeapSomeType Heap@(SomeType)
// No need to specify the type variable in functions, they should just match the declaration.
// Easy for the compiler and humans to follow. There's practically no new syntax other
// than the generic type specifier.
func (h *Heap) Push(v @T) bool {
...
}
// Complex example with multiple type specifiers.
// What if one wants explicit order of instantiation?
type S(@V, @U, @T) struct {
P1 @T
P2 @U
P3 @V
}
// instantiation is always the same syntax
var myS S@(int, string, float)
// what if attributes are of generic type themselves?
// This equates to: S2<S<T, U V>>
// which can be simplified to S2<T, U, V> where S<T, U, V> exists
type S2(@T, @U, @V) struct {
P1 S(@T, @U, @V)
}
// can be instantiated the usual way
var myS2 S2@(int, string, float)
// for a function, which constraints are set for the input types
func Max(v ...@T) @T
where
@T: int | uint | int8 | ... | float32 | float64, // T can be of
multiple types, which helps to avoid defining a new interface for
everything
// @V : ... multiple where clauses can be added here for different type variables
{
...
}
////////////////////////////////////////////////////////////////
//
// Examples lifted from the official proposal:
//
////////////////////////////////////////////////////////////////
// Smallest returns the smallest element in a slice.
// It panics if the slice is empty.
func Smallest(s []@T) @T
where @T: Ordered
{
r := s[0] // panics if slice is empty
for _, v := range s[1:] {
if v < r {
r = v
}
}
return r
}
// Map calls the function f on every element of the slice s,
// returning a new slice of the results.
func Map(s []@F, f func(@F) @T) []@T {
r := make([]@T, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
// NewPair returns a pair of values of the same type.
func NewPair(f1, f2 @F) *Pair(@F) { ... }
To
me this looks simpler and more clear to read than the official
proposal, and practically all other generics syntax that I know of.
The `@` symbol will immediately signal `generic type` to the reader, there isn't too much to type, retype or read.
You will also never see a generic type without its `@` prefix anywhere in the code, which significantly helps with readability.
It's vague neither to humans nor machines, and can be read as `Heap at int (-land!)` during instantiation which while not
idiomatic English, still communicates the purpose.
What do you think?