There are two main parts to the Contracts Draft Design:
- Type Parameters
- Contracts
Type Parameters are mostly independent of Contracts; Contracts builds on Type Parameters. The two concepts are well thought-out and many corner cases have been looked at.
These are some specific points that I think are done well by the current generics draft proposal, not in any particular order:
- Type parameters (generics!) in functions, structs, etc.
- Ability to express relationships between types through contracts
- Ability to constrain contract types by methods, and/or by a list of existing types, including aggregate types
- Pointer methods in contracts
- Embedded contracts
- Self-referential types in contracts
- Export rules (capitalization) for contracts are the same as other constructs
- Explicit type parameters at call sites, inferred where possible
- "Unboxed" generics, for those who want to avoid any runtime penalty
- Ability to defer the decision of compile-time/runtime implementation
- Instantiation of type-parameterized functions, structs, etc.
- No changes to the Reflect package
- No operator overloading (barring
Ordered
andComparable
, etc. as proposed) - Type conversions
- Focus on the burden of complexity being on the writer of generic code, rather than the consumer
- Design that does not encourage widespread proliferation
- Good set of omissions.
- Absolutely acceptable zero value solution.
- Probably sufficient orthogonality with interfaces.
- Examples of how to implement many of the most common use cases for generics (map, reduce, set, linked list, etc.)
The primary issue with the Contracts proposal is the syntax; it does not feel like Go code. It is difficult to read and confusing to a Go developer.
This document contains several proposals to change the syntax of Type Parameters and Contracts, to improve their readability. Each proposal is incorporated into the next, so the examples will show an evolution of syntax. (Note that, in spite of this, each proposal can stand on its own, and can be broken out if desired.) At the end there will be a thorough comparisons with the existing draft's examples.
I propose an alternate syntax for specifying methods in contracts:
// Existing syntax
contract stringer(T) {
T String() string
}
// Method-call syntax
contract stringer(T) {
T.String() string
}
This syntax is more clear regarding the relationship of String()
to T
and the nature of what T
is, a type that is the receiver of the String()
method.
A similar alternative exists for specifying a list of types:
// Existing syntax
contract Float(T) {
T float32, float64
}
// Type-assertion syntax
contract Float(T) {
T.(float32, float64)
}
This syntax is familiar to anyone who has done an ordinary type assertion in Go; T
must be of one of the given types.
An example with both methods and types:
// Existing syntax
contract StringableSignedInteger(T) {
T int, int8, int16, int32, int64
T String() string
}
// Proposed syntax
contract StringableSignedInteger(T) {
T.(int, int8, int16, int32, int64)
T.String() string
}
An example with multiple types in the contract:
// Existing syntax
contract G(Node, Edge) {
Node Edges() []Edge
Edge Nodes() (from Node, to Node)
}
// Proposed syntax
contract G(Node, Edge) {
Node.Edges() []Edge
Edge.Nodes() (from Node, to Node)
}
By adding the .
to method & type specifications in contracts, the relationship is clarified and there is better visual separation of the different elements.
I propose the concept of an empty contract which is written as:
contract{}
The empty contract is satisfied by any type. The amount of types involved in an empty contract is unbounded.
The empty contract is used for a type-parameterized declaration that otherwise does not use a contract. Here are some examples:
// Existing syntax
func Print(type T)(s []T) { ... }
func Print2(type T1, T2)(s1 []T1, s2 []T2) { ... }
type Lockable(type T) struct {
T
mu sync.Mutex
}
type Vector(type Element) []Element
// Proposed syntax
func Print(type T contract{})(s []T) { ... }
func Print2(type T1, T2 contract{})(s1 []T1, s2 []T2) { ... }
type Lockable(type T contract{}) struct {
T
mu sync.Mutex
}
type Vector(type Element contract{}) []Element
This mirrors the concept of the empty interface which is familiar to Go developers. More importantly, by always using a contract with type parameters, the concepts involved are simplified and the syntax is more consistent.
The existing type parameter syntax is as follows:
type t1, t2, ...tN con?
In other words, a comma-separated list of type names with an optional contract specified at the end.
However, there are scenarios where some type parameters belong to a contract, and some belong to the empty contract:
func MapAndPrint(type E, M stringer(M))(s []E, f(E) M) []string {
r := make([]string, len(s))
for i, v := range s {
r[i] = f(v).String()
}
return r
}
The updated syntax then is:
type t1, t2, ...tN con?(t1, t2, ...tn)?
To improve this, and in alignment with the empty contract, I propose an alternative syntax when defining type parameters in this scenario:
func MapAndPrint(type E contract{}, M stringer)(s []E, f(E) M) []string {
r := make([]string, len(s))
for i, v := range s {
r[i] = f(v).String()
}
return r
}
The exact syntax is:
type t1, t2, t3, ...tN contract, t1, t2, t3, ....tN contract
Type parameters must list at most one regular contract, optionally the empty contract, and at least one of either. The layout and syntax is similar to the shorthand for multiple function arguments or struct fields and should seem natural to Go programmers.
When a contract is used to validate type parameters, the amount and sequence must match, but the names are irrelevant. I propose an additional, alternative method of declaring type parameters, whereby contract-matching uses the type names, and ignores the amount and sequence of values.
// Normal syntax
type t1, t2, t3, ...tN contract, t1, t2, t3, ....tN contract
// Alternative syntax
type name1? contract.t1, name2? contract.t2, ...nameN? contract.tN
In other words, name/type pairs.
- All types must be drawn from the same contract, or from the empty contract.
- The types must match how they are defined in their contract.
- There is no requirement to list every type specified by a contract.
- The type can stand by itself as an identifier, similar to anonymous struct embedding.
- If the signature contains an empty contract, no types may be "anonymous".
An example of this would be:
contract equal(T) {
T.Equal(T) bool
}
// Explicitly named contract type
func Index(type X equal.T)(s []X, e X) int { ... }
// Embedded contract type
func Index(type equal.T)(s []T, e T) int { ... }
contract stringer(T) {
T.String() string
}
// ILLEGAL!
func MapAndPrint(type E contract{}, stringer.T)(s []E, f(E) M) []string { ... }
// This is fine
func MapAndPrint(type E contract{}, M stringer.T)(s []E, f(E) M) []string { ... }
Although it could make individual type parameters more verbose, this style allows you to select specific arguments of the contract to use for validation. A good example of this use case is in Roger Peppe's generic mgo exploration. The syntax of the contract is outdated, but I think the point can't be missed:
contract SessionContract(
s Session,
db Database,
c Collection,
q Query,
it Iter,
) {
Session(Collection)(s)
Collection(Query)(db)
Query(Iter)(query)
Iter(it)
}
// Normal syntax
func PrintBobDetails(
type Session, _, _, _, _ SessionContract,
)(session Session) {
iter := session.DB("mydb").C("people").Find(bson.M{
"name": "bob",
})
... etc
}
// Name-based matching
func PrintBobDetails(
type SessionContract.Session,
)(session Session) {
iter := session.DB("mydb").C("people").Find(bson.M{
"name": "bob",
})
... etc
}
In addition to a contract with a large number of type parameters, this style can help with general code maintenance, because changing the number or sequence of type parameters will not break code that uses that contract. As an analogy, the existing way of declaring type parameters is like using unnamed fields in a struct literal; this is the named variation.
The delimiters to use for type parameters are perhaps the most hotly contested aspect of the contracts draft.
The contracts draft has specific callouts for the team's thoughts on F<T>
and F[T]
and F«T»
. To be fair, they also touch upon Lots of irritating silly parentheses, and the 4 kinds of parsing ambiguity introduced by using parentheses for type argument clauses.
It's true, there are downsides for any delimiter you can think of. However, using parentheses will have the greatest downsides, because it will harm the users of Go code by making it difficult to read. The F<T>
form, although potentially difficult to implement, would be superior simply because the complexity is kept in the parser and not thrust upon the developer. Go users may be distantly proud of the tooling's simplicity, including the parser, but they don't study the parser for their day job—they read and write Go code. It needs to be extremely readable, and while feedback is mixed, the general consensus seems to be that the additional parentheses make the code harder to read.
Key points:
- Using
F<T>
is most familiar to developers as it is common in other languages. If this can be solved, it would be for the best. - Using
F«T»
is not terrible. The characters can be written on a Mac withAlt + \
andAlt + Shift + \
. Perhaps the compiler could equally acceptF<<T>>
, and for consistencygofmt
could transformF<<T>>
intoF«T»
in cases where there is clearly no bit-shifting occurring. Also, generally speaking, a lot of generic code (I would say well-written generic code) can have the type parameters inferred, and won't need these delimiters, so the burden of typing unusual characters generally falls on the writer of generic code, not the consumer (as mentioned in the draft). - Failing the above,
F|T|
is a fallback, simply because they're not parentheses. - Also,
F<|T|>
is a little ugly, but it's notF<T>
and it's easier to see the start & end points thanF|T|
, so it's a candidate.
I won't insist on a particular delimiter, but I will argue, plead, and cajole that we not proceed until we have something better than parentheses worked out.
Here is how the Graph example looks with F«T»
:
contract G«Node, Edge» {
Node.Edges() []Edge
Edge.Nodes() (from Node, to Node)
}
type Graph«type Node, Edge G» struct { ... }
func New«type Node, Edge G»(nodes []Node) *Graph«Node, Edge» { ... }
func (g *Graph«Node, Edge») ShortestPath(from, to Node) []Edge { ... }
Something I would also support is angle brackets before types, suggested by Peter McKenzie. In that case I think the type
keyword would be omitted from the type parameter definition clause:
type <T contract{}>List []T
func <K, V contract{}>Keys(m map[K]V) []K
var ints <int>List
// keys := <int, string>Keys(map[int]string{1: "one", 2: "two"})
keys := Keys(map[int]string{1: "one", 2: "two"}) // type-inferred
type <T Equal>Set []T
func (s <T>Set) Find(x T) int {
...
}
The above is radical in its own way. <T>F
is less readable than F<T>
at first glance but more readable than F(T)
because the parts are clearly delineated.
For the sake of making a selection, the rest of this document will be in the style of F«T»
.
The following is a proposal that I talked myself out of.
There are some contracts that have a single type, which is specified in the name of the contract itself. The nested type then seems extraneous. Contracts could have a "solo" syntax:
// Existing syntax
contract StringableSignedInteger«T» {
T.(int, int8, int16, int32, int64)
T.String() string
}
// Solo syntax
contract StringableSignedInteger {
int, int8, int16, int32, int64
String() string
}
In combination with explicit, anonymous type parameters, you could achieve this effect:
contract setter {
Set(string) error
}
func Strings«type setter»(s []string) ([]setter, error) {
ret := make([]T, len(s))
for i, v := range s {
if err := ret[i].Set(v); err != nil {
return nil, err
}
}
return ret, nil
}
One could argue that this is more readable; it's similar to using an interface in that the contract and the type are unified. There's nothing extraneous.
However, I do not propose this for two reasons:
- Since it's innately a different syntax for a contract, it may throw off expectations of what a contract should look like, and it might then be confused with an interface.
- It creates inconsitent syntax when specifying a list of types as a constraint on a contract. I considered and discarded
_.(int, int8...)
and similar things. - It creates an ambiguity in the type parameter listing: which form is being used, named or unnamed? Without Solo Contract syntax, even anonymous type params can be identified by their dot; I have this feeling it must makes things too loose.
The normal syntax is not particularly burdensome:
contract setter«T» {
T.Set(string) error
}
func Strings«type set setter»(s []string) ([]set, error) {
ret := make([]T, len(s))
for i, v := range s {
if err := ret[i].Set(v); err != nil {
return nil, err
}
}
return ret, nil
}
What follows is a conversion of the code samples from the contracts proposal, to use the syntax given above, to get a moderately wide sampling of how it will look. Note that another set of delimiters could be used instead of F«T»
, such as F<T>
.
The main point of this entire batch of changes is to improve the readability of generic code in the Go language, and I believe the below examples do show that's been done. It no longer looks like odd and surprising function calls strewn throughout, it looks like something different and unique, and yet familiar enough to easily grasp the intent.
// Print prints the elements of a slice.
// It should be possible to call this with any slice value.
func Print(s []T) { // Just an example, not the suggested syntax.
for _, v := range s {
fmt.Println(v)
}
}
func Print«type T contract{}»(s []T) {
// same as above
}
Print«int»([]int{1, 2, 3})
Print([]int{1, 2, 3}) // inferred
// This function is INVALID.
func Stringify«type T contract{}»(s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String()) // INVALID
}
return ret
}
contract stringer«T» {
T.String() string
}
func Stringify«type Str stringer»(s []Str) (ret []string) {
for _, v := range s {
ret = append(ret, v.String()) // now valid
}
return ret
}
func Print2«type T1, T2 contract{}»(s1 []T1, s2 []T2) { ... }
func Print2Same«type T1 contract{}»(s1 []T1, s2 []T1) { ... }
contract viaStrings«To, From» {
To.Set(string)
From.String() string
}
func SetViaStrings«type To, From viaStrings»(s []From) []To {
r := make([]To, len(s))
for i, v := range s {
r[i].Set(v.String())
}
return r
}
type Vector«type Element contract{}» []Element
var v Vector«int»
func (v *Vector«Element») Push(x Element) { *v = append(*v, x) }
// This is OK.
type List«type Element contract{}» struct {
next *List«Element»
val Element
}
// This type is INVALID.
type P«type Element1, Element2 contract{}» struct {
F *P«Element2, Element1» // INVALID; must be «Element1, Element2»
}
type StringableVector«type Str stringer» []T
func (s StringableVector«Str») String() string {
var sb strings.Builder
sb.WriteString("[")
for i, v := range s {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(v.String())
}
sb.WriteString("]")
return sb.String()
}
type Lockable«type T contract{}» struct {
T
mu sync.Mutex
}
func (l *Lockable«T») Get() T {
l.mu.Lock()
defer l.mu.Unlock()
return l.T
}
type VectorInt = Vector«int»
contract PrintStringer«X» {
stringer«X»
X.Print()
}
This is equivalent to
contract PrintStringer«X» {
X.String() string
X.Print()
}
package compare
// The equal contract describes types that have an Equal method with
// an argument of the same type as the receiver type.
contract equal«T» {
T.Equal«T» bool
}
// Index returns the index of e in s, or -1.
func Index«type T equal»(s []T, e T) int {
for i, v := range s {
// Both e and v are type T, so it's OK to call e.Equal(v).
if e.Equal(v) {
return i
}
}
return -1
}
import "compare"
type EqualInt int
// The Equal method lets EqualInt satisfy the compare.equal contract.
func (a EqualInt) Equal(b EqualInt) bool { return a == b }
func Index(s []EqualInt, e EqualInt) int {
// return compare.Index«EqualInt»(s, e)
return compare.Index(s, e) // inferred
}
package graph
contract G«Node, Edge» {
Node.Edges() []Edge
Edge.Nodes() (from Node, to Node)
}
type Graph«type G.Node, G.Edge» struct { ... }
func New«type G.Node, G.Edge»(nodes []Node) *Graph«Node, Edge» { ... }
func (g *Graph«Node, Edge») ShortestPath(from, to Node) []Edge { ... }
type Vertex struct { ... }
func (v *Vertex) Edges() []*FromTo { ... }
type FromTo struct { ... }
func (ft *FromTo) Nodes() (*Vertex, *Vertex) { ... }
var g = graph.New«*Vertex, *FromTo»([]*Vertex{ ... })
type NodeInterface interface { Edges() []EdgeInterface }
type EdgeInterface interface { Nodes() (NodeInterface, NodeInterface) }
func MapAndPrint«type E contract{}, M stringer»(s []E, f(E) M) []string {
r := make([]string, len(s))
for i, v := range s {
r[i] = f(v).String()
}
return r
}
package from
contract setter«T» {
T.Set(string) error
}
func Strings«type T setter»(s []string) ([]T, error) {
ret := make([]T, len(s))
for i, v := range s {
if err := ret[i].Set(v); err != nil {
return nil, err
}
}
return ret, nil
}
type Settable int
func (p *Settable) Set(s string) (err error) {
*p, err = strconv.Atoi(s)
return err
}
func F() {
// The type of nums is []Settable.
nums, err := from.Strings«Settable»([]string{"1", "2"})
if err != nil { ... }
// Settable can be converted directly to int.
// This will set first to 1.
first := int(nums[0])
...
}
package pair
type Pair«type carT, cdrT contract{}» struct {
f1 carT
f2 cdrT
}
Print«int»([]int{1, 2, 3})
s1 := []int{1, 2, 3}
Print(s1)
package transform
func Slice«type From, To contract{}»(s []From, f func(From) To) []To {
r := make([]To, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
strs := transform.Slice([]int{1, 2, 3}, strconv.Itoa)
package pair
func New«type T contract{}»(f1, f2 T) *Pair(T) { ... }
type Pair«type T contract{}» struct { f1, f2 T }
var V = Pair{1, 2} // inferred as Pair«int»{1, 2}
// PrintInts will be type func([]int).
var PrintInts = Print«int»
contract reader«T» {
T.Read([]byte) (int, error)
}
func ReadByte«type T reader»(r T) (byte, error) {
if br, ok := r.(io.ByteReader); ok {
return br.ReadByte()
}
var b [1]byte
_, err := r.Read(b[:])
return b[0], err
}
No parsing ambiguity:
x1 := []T(v1) // type conversion
x2 := []T(v2){} // ([]T)(v2){} - compiler error?
x3 := []T«v3» // type instantiation
x4 := []T«v4»{} // typed-instantiated slice literal
No parsing ambiguity:
var f func(x(T)) // equivalent to func(x T)
var g func(x«T»)
No parsing ambiguity:
type S1«type T contract{}» struct {
f T
}
// Instantiated type `S1«int»` embedded
type S2 struct {
S1«int»
}
// Single field named `S1` of type `(int)`
type S3 struct {
S1(int)
}
No parsing ambiguity:
type I1«type T contract{}» interface {
M«T»
}
// Embed instantiated type `I1«int»` into `I2`
type I2 interface {
I1«int»
}
// `I3` has single method named `I1` that takes an argument of type `int`
type I3 interface {
I1(int)
}
contract setter«T» {
T.Set(string)
}
func Init«type T setter»(s string) T {
var r T
r.Set(s)
return r
}
type MyInt int
func (p *MyInt) Set(s string) {
v, err := strconv.Atoi(s)
if err != nil {
log.Fatal("Init failed", err)
}
*p = MyInt(v)
}
// INVALID
// MyInt does not have a Set method, only *MyInt has one.
var Init1 = Init«MyInt»("1")
// DOES NOT WORK
// r in Init is type *MyInt with value nil,
// so the Set method does a nil pointer deference.
var Init2 = Init«*MyInt»("2")
contract setter«T» {
*T.Set(string)
}
func LookupAsString«type T stringer»(m map[int]T, k int) string {
return m[k].String() // Note: calls method on value of type T
}
type MyInt int
func (p *MyInt) String() { return strconv.Itoa(int(*p)) }
func F(m map[int]MyInt) string {
return LookupAsString«MyInt»(m, 0)
}
// This function is INVALID.
func Smallest«type T contract{}»(s []T) T {
r := s[0] // panics if slice is empty
for _, v := range s[1:] {
if v < r { // INVALID
r = v
}
}
return r
}
contract SignedInteger«T» {
T.(int, int8, int16, int32, int64)
}
contract Ordered«T» {
T.(int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64,
string)
}
func Smallest«type T Ordered»(s []T) T {
r := s[0] // panics if slice is empty
for _, v := range s[1:] {
if v < r {
r = v
}
}
return r
}
// PrintStringer1 and PrintStringer2 are equivalent.
contract PrintStringer1«T» {
T.String() string
T.Print()
}
contract PrintStringer2«T» {
T.String() string; T.Print()
}
contract Float«T» {
T.(float32, float64)
}
contract IOCloser«S» {
S.Read([]byte) (int, error), // note trailing comma
S.Write([]byte) (int, error)
S.Close() error
}
contract unsatisfiable«T» {
T.(int)
T.(uint)
}
contract StringableSignedInteger«T» {
T.(int, int8, int16, int32, int64)
T.String() string
}
type MyInt int
func (mi MyInt) String() string {
return fmt.Sprintf("MyInt(%d)", mi)
}
contract byteseq«T» {
T.(string, []byte)
}
type MyByte byte
type MyByteAlias = byte
func Join«type T byteseq»(a []T, sep T) (ret T) {
if len(a) == 0 {
// Use the result parameter as a zero value;
// see discussion of zero value below.
return ret
}
if len(a) == 1 {
return T(append([]byte(nil), a[0]...))
}
n := len(sep) * (len(a) - 1)
for i := 0; i < len(a); i++ {
n += len(a[i]) // len works for both string and []byte
}
b := make([]byte, n)
bp := copy(b, a[0])
for _, s := range a[1:] {
bp += copy(b[bp:], sep)
bp += copy(b[bp:], s)
}
return T(b)
}
contract Slice«S, Element» {
S.([]Element)
}
func Map«type S, Element Slice»(s S, f func(Element) Element) S {
r := make(S, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
type MySlice []int
func DoubleMySlice(s MySlice) MySlice {
v := Map«MySlice, int»(s, func(e int) int { return 2 * e })
// Here v has type MySlice, not type []int.
return v
}
type M«type T contract{}» []T
contract C«T» {
T.M(T) // T must implement the method M with an argument of type T
T.(M«T») // T must be the type M(T)
}
func Index«type T comparable»(s []T, x T) int {
for i, v := range s {
if v == x {
return i
}
}
return -1
}
contract integer«T» {
T.(int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr)
}
contract integer2«T1, T2» {
T1.(integer)
T2.(integer)
}
func Convert«type To, From integer2»(from From) To {
to := To(from)
if From(to) != from {
panic("conversion out of range")
}
return to
}
contract integer«T» {
T.(int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr)
}
func Add10«type T integer»(s []T) {
for i, v := range s {
s[i] = v + 10 // OK: 10 can convert to any integer type
}
}
// This function is INVALID.
func Add1024«type T integer»(s []T) {
for i, v := range s {
s[i] = v + 1024 // INVALID: 1024 not permitted by int8/uint8
}
}
type Optional«type T contract{}» struct {
p *T
}
func (o Optional«T») Val() T {
if o.p != nil {
return *o.p
}
var zero T
return zero
}
F«int, float64»(x, y)(s)
func Map«type Element contract{}»(s []Element, f func(Element) Element) []Element {
r := make([]Element, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
type MySlice []int
func DoubleMySlice(s MySlice) MySlice {
s2 := Map(s, func(e int) int { return 2 * e })
// Here s2 is type []int, not type MySlice.
return MySlice(s2)
}
contract Float«F» {
F.(float32, float64)
}
func NewtonSqrt«type F Float»(v F) F {
var iterations int
switch v.(type) {
case float32:
iterations = 4
case float64:
iterations = 5
default:
panic(fmt.Sprintf("unexpected type %T", v))
}
// Code omitted.
}
type MyFloat float32
var G = NewtonSqrt(MyFloat(64))
type orderedSlice«type Elem Ordered» []Elem
func (s orderedSlice«Elem») Len() int { return len(s) }
func (s orderedSlice«Elem») Less(i, j int) bool { return s[i] < s[j] }
func (s orderedSlice«Elem») Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// OrderedSlice sorts the slice s in ascending order.
// The elements of s must be ordered using the < operator.
func OrderedSlice«type Elem Ordered»(s []Elem) {
sort.Sort(orderedSlice«Elem»(s))
}
sort.OrderedSlice«int32»([]int32{3, 5, 2})
sort.OrderedSlice([]string{"a", "c", "b"}) // type argument inferred
type sliceFn«type Elem» struct {
s []Elem
f func(Elem, Elem) bool
}
func (s sliceFn«Elem») Len() int { return len(s.s) }
func (s sliceFn«Elem») Less(i, j int) bool { return s.f(s.s[i], s.s[j]) }
func (s sliceFn«Elem») Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] }
// SliceFn sorts the slice s according to the function f.
func SliceFn«type Elem»(s []Elem, f func(Elem, Elem) bool) {
Sort(sliceFn«Elem»{s, f})
}
var s []*Person
// ...
sort.SliceFn(s, func(p1, p2 *Person) bool { return p1.Name < p2.Name })
package maps
// Keys returns the keys of the map m.
// Note that map keys (here called type K) must be comparable.
func Keys«type K comparable, V contract{}»(m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
k := maps.Keys(map[int]int{1:2, 2:4}) // sets k to []int{1, 2} (or {2, 1})
// Package slices implements various slice algorithms.
package slices
// Map turns a []from to a []to using a mapping function.
func Map«type from, to contract{}»(sl []from, f func(from) to) []to {
r := make([]to, len(sl))
for i, v := range sl {
r[i] = f(v)
}
return r
}
// Reduce reduces a []T1 to a single value using a reduction function.
func Reduce«type val, rdc contract{}»(s []val, initializer rdc, f func(rdc, val) rdc) rdc {
r := initializer
for _, v := range s {
r = f(r, v)
}
return r
}
// Filter filters values from a slice using a filter function.
func Filter«type val contract{}»(s []val, f func(val) bool) []val {
var r []val
for _, v := range s {
if f(v) {
r = append(r, v)
}
}
return r
}
s := []int{1, 2, 3}
floats := slices.Map(s, func(i int) float64 { return float64(i) })
sum := slices.Reduce(s, 0, func(i, j int) int { return i + j })
evens := slices.Filter(s, func(i int) bool { return i%2 == 0 })
// Package set implements sets of any type.
package set
type Set«type Elem comparable» map[Elem]struct{}
func Make«type Elem comparable»() Set(Elem) {
return make(Set«Elem»)
}
func (s Set«Elem») Add(v Elem) {
s[v] = struct{}{}
}
func (s Set«Elem») Delete(v Elem) {
delete(s, v)
}
func (s Set«Elem») Contains(v Elem) bool {
_, ok := s[v]
return ok
}
func (s Set«Elem») Len() int {
return len(s)
}
func (s Set«Elem») Iterate(f func(Elem)) {
for v := range s {
f(v)
}
}
s := set.Make«int»()
s.Add(1)
if s.Contains(2) { panic("unexpected 2") }
package chans
import "runtime"
// Ranger returns a Sender and a Receiver. The Receiver provides a
// Next method to retrieve values. The Sender provides a Send method
// to send values and a Close method to stop sending values. The Next
// method indicates when the Sender has been closed, and the Send
// method indicates when the Receiver has been freed.
//
// This is a convenient way to exit a goroutine sending values when
// the receiver stops reading them.
func Ranger«type T contract{}»() (*Sender«T», *Receiver«T») {
c := make(chan T)
d := make(chan bool)
s := &Sender«T»{values: c, done: d}
r := &Receiver«T»{values: c, done: d}
runtime.SetFinalizer(r, r.finalize)
return s, r
}
// A sender is used to send values to a Receiver.
type Sender«type T contract{}» struct {
values chan<- T
done <-chan bool
}
// Send sends a value to the receiver. It returns whether any more
// values may be sent; if it returns false the value was not sent.
func (s *Sender«T») Send(v T) bool {
select {
case s.values <- v:
return true
case <-s.done:
return false
}
}
// Close tells the receiver that no more values will arrive.
// After Close is called, the Sender may no longer be used.
func (s *Sender«T») Close() {
close(s.values)
}
// A Receiver receives values from a Sender.
type Receiver«type T contract{}» struct {
values <-chan T
done chan<- bool
}
// Next returns the next value from the channel. The bool result
// indicates whether the value is valid, or whether the Sender has
// been closed and no more values will be received.
func (r *Receiver«T») Next() (T, bool) {
v, ok := <-r.values
return v, ok
}
// finalize is a finalizer for the receiver.
func (r *Receiver«T») finalize() {
close(r.done)
}
// Package orderedmap provides an ordered map, implemented as a binary tree.
package orderedmap
import "chans"
// Map is an ordered map.
type Map«type K, V contract{}» struct {
root *node«K, V»
compare func(K, K) int
}
// node is the type of a node in the binary tree.
type node«type K, V contract{}» struct {
key K
val V
left, right *node«K, V»
}
// New returns a new map.
func New«type K, V contract{}»(compare func(K, K) int) *Map«K, V» {
return &Map«K, V»{compare: compare}
}
// find looks up key in the map, and returns either a pointer
// to the node holding key, or a pointer to the location where
// such a node would go.
func (m *Map«K, V») find(key K) **node«K, V» {
pn := &m.root
for *pn != nil {
switch cmp := m.compare(key, (*pn).key); {
case cmp < 0:
pn = &(*pn).left
case cmp > 0:
pn = &(*pn).right
default:
return pn
}
}
return pn
}
// Insert inserts a new key/value into the map.
// If the key is already present, the value is replaced.
// Returns true if this is a new key, false if already present.
func (m *Map«K, V») Insert(key K, val V) bool {
pn := m.find(key)
if *pn != nil {
(*pn).val = val
return false
}
*pn = &node«K, V»{key: key, val: val}
return true
}
// Find returns the value associated with a key, or zero if not present.
// The found result reports whether the key was found.
func (m *Map«K, V») Find(key K) (V, bool) {
pn := m.find(key)
if *pn == nil {
var zero V // see the discussion of zero values, above
return zero, false
}
return (*pn).val, true
}
// keyValue is a pair of key and value used when iterating.
type keyValue«type K, V contract{}» struct {
key K
val V
}
// InOrder returns an iterator that does an in-order traversal of the map.
func (m *Map«K, V») InOrder() *Iterator«K, V» {
sender, receiver := chans.Ranger(keyValue«K, V»)()
var f func(*node«K, V») bool
f = func(n *node«K, V») bool {
if n == nil {
return true
}
// Stop sending values if sender.Send returns false,
// meaning that nothing is listening at the receiver end.
return f(n.left) &&
sender.Send(keyValue«K, V»{n.key, n.val}) &&
f(n.right)
}
go func() {
f(m.root)
sender.Close()
}()
return &Iterator{receiver}
}
// Iterator is used to iterate over the map.
type Iterator«type K, V contract{}» struct {
r *chans.Receiver(keyValue«K, V»)
}
// Next returns the next key and value pair, and a boolean indicating
// whether they are valid or whether we have reached the end.
func (it *Iterator«K, V») Next() (K, V, bool) {
keyval, ok := it.r.Next()
if !ok {
var zerok K
var zerov V
return zerok, zerov, false
}
return keyval.key, keyval.val, true
}
This is what it looks like to use this package:
import "container/orderedmap"
var m = orderedmap.New«string, string»(strings.Compare)
func Add(a, b string) {
m.Insert(a, b)
}
func Add(s, t []byte) []byte
package slices
// Append adds values to the end of a slice, returning a new slice.
func Append«type T contract{}»(s []T, t ...T) []T {
lens := len(s)
tot := lens + len(t)
if tot <= cap(s) {
s = s[:tot]
} else {
news := make([]T, tot, tot + tot/2)
copy(news, s)
s = news
}
copy(s[lens:tot], t)
return s
}
// Copy copies values from t to s, stopping when either slice is
// full, returning the number of values copied.
func Copy«type T contract{}»(s, t []T) int {
i := 0
for ; i < len(s) && i < len(t); i++ {
s[i] = t[i]
}
return i
}
s := slices.Append([]int{1, 2, 3}, 4, 5, 6)
slices.Copy(s[3:], []int{7, 8, 9})
package metrics
import "sync"
type Metric1«type T comparable» struct {
mu sync.Mutex
m map[T]int
}
func (m *Metric1«T») Add(v T) {
m.mu.Lock()
defer m.mu.Unlock()
if m.m == nil {
m.m = make(map[T]int)
}
m[v]++
}
contract cmp2«T1, T2» {
T1.(comparable)
T2.(comparable)
}
type key2«type T1, T2 cmp2» struct {
f1 T1
f2 T2
}
type Metric2«type T1, T2 cmp2» struct {
mu sync.Mutex
m map[key2«T1, T2»]int
}
func (m *Metric2«T1, T2») Add(v1 T1, v2 T2) {
m.mu.Lock()
defer m.mu.Unlock()
if m.m == nil {
m.m = make(map[key2«T1, T2»]int)
}
m[key«T1, T2»{v1, v2}]++
}
contract cmp3«T1, T2, T3» {
T1.(comparable)
T2.(comparable)
T3.(comparable)
}
type key3«type T1, T2, T3 cmp3» struct {
f1 T1
f2 T2
f3 T3
}
type Metric3«type T1, T2, T3 cmp3» struct {
mu sync.Mutex
m map[key3«T1, T2, T3»]int
}
func (m *Metric3«T1, T2, T3») Add(v1 T1, v2 T2, v3 T3) {
m.mu.Lock()
defer m.mu.Unlock()
if m.m == nil {
m.m = make(map[key3]int)
}
m[key«T1, T2, T3»{v1, v2, v3}]++
}
// Repeat for the maximum number of permitted arguments.
Using this package looks like this:
import "metrics"
var m = metrics.Metric2«string, int»{}
func F(s string, i int) {
m.Add(s, i) // this call is type checked at compile time
}
package list
// List is a linked list.
type List«type T contract{}» struct {
head, tail *element«T»
}
// An element is an entry in a linked list.
type element«type T contract{}» struct {
next *element«T»
val T
}
// Push pushes an element to the end of the list.
func (lst *List«T») Push(v T) {
if lst.tail == nil {
lst.head = &element«T»{val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element«T»{val: v }
lst.tail = lst.tail.next
}
}
// Iterator ranges over a list.
type Iterator«type T contract{}» struct {
next **element«T»
}
// Range returns an Iterator starting at the head of the list.
func (lst *List«T») Range() *Iterator«T» {
return Iterator«T»{next: &lst.head}
}
// Next advances the iterator.
// It returns whether there are more elements.
func (it *Iterator«T») Next() bool {
if *it.next == nil {
return false
}
it.next = &(*it.next).next
return true
}
// Val returns the value of the current element.
// The bool result reports whether the value is valid.
func (it *Iterator«T») Val() (T, bool) {
if *it.next == nil {
var zero T
return zero, false
}
return (*it.next).val, true
}
// Transform runs a transform function on a list returning a new list.
func Transform«type T1, T2 contract{}»(lst *List«T1», f func(T1) T2) *List«T2» {
ret := &List«T2»{}
it := lst.Range()
for {
if v, ok := it.Val(); ok {
ret.Push(f(v))
}
it.Next()
}
return ret
}
// Key is a key that can be used with Context.Value.
// Rather than calling Context.Value directly, use Key.Load.
//
// The zero value of Key is not ready for use; use NewKey.
type Key«type V contract{}» struct {
name string
}
// NewKey returns a key used to store values of type V in a Context.
// Every Key returned is unique, even if the name is reused.
func NewKey«type V contract{}»(name string) *Key {
return &Key«V»{name: name}
}
// WithValue returns a new context with v associated with k.
func (k *Key«V») WithValue(parent Context, v V) Context {
return WithValue(parent, k, v)
}
// Value loads the value associated with k from ctx and reports
//whether it was successful.
func (k *Key«V») Value(ctx Context) (V, bool) {
v, present := ctx.Value(k).(V)
return v.(V), present
}
// String returns the name and expected value type.
func (k *Key«V») String() string {
var v V
return fmt.Sprintf("%s(%T)", k.name, v)
}
var ServerContextKey = context.NewKey«*Server»("http_server")
// used as:
ctx := ServerContextKey.WithValue(ctx, srv)
s, present := ServerContextKey.Value(ctx)
// Numeric is a contract that matches any numeric type.
// It would likely be in a contracts package in the standard library.
contract Numeric«T» {
T.(int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64,
complex64, complex128)
}
func DotProduct«type T Numeric»(s1, s2 []T) T {
if len(s1) != len(s2) {
panic("DotProduct: slices of unequal length")
}
var r T
for i := range s1 {
r += s1[i] * s2[i]
}
return r
}
// NumericAbs matches numeric types with an Abs method.
contract NumericAbs«T» {
T.(Numeric)
T.Abs() T
}
// AbsDifference computes the absolute value of the difference of
// a and b, where the absolute value is determined by the Abs method.
func AbsDifference«type T NumericAbs»(a, b T) T {
d := a - b
return d.Abs()
}
// OrderedNumeric matches numeric types that support the < operator.
contract OrderedNumeric«T» {
T.(int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64)
}
// Complex matches the two complex types, which do not have a < operator.
contract Complex«T» {
T.(complex64, complex128)
}
// OrderedAbs is a helper type that defines an Abs method for
// ordered numeric types.
type OrderedAbs«type T OrderedNumeric» T
func (a OrderedAbs«T») Abs() T {
if a < 0 {
return -a
}
return a
}
// ComplexAbs is a helper type that defines an Abs method for
// complex types.
type ComplexAbs«type T Complex» T
func (a ComplexAbs«T») Abs() T {
r := float64(real(a))
i := float64(imag(a))
d := math.Sqrt(r * r + i * i)
return T(complex(d, 0))
}
func OrderedAbsDifference«type T OrderedNumeric»(a, b T) T {
return T(AbsDifference(OrderedAbs«T»(a), OrderedAbs«T»(b)))
}
func ComplexAbsDifference«type T Complex»(a, b T) T {
return T(AbsDifference(ComplexAbs«T»(a), ComplexAbs«T»(b)))
}
// This function is INVALID.
func GeneralAbsDifference«type T Numeric»(a, b T) T {
switch a.(type) {
case int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64:
return OrderedAbsDifference(a, b) // INVALID
case complex64, complex128:
return ComplexAbsDifference(a, b) // INVALID
}
}
oOo
Hi, on the non-parenthetical delimiters, has anyone ever suggested some paradigm shift approach in the way computer languages are "understood"?
Here my point is that the only things available for developers today are text characters... we have nice IDEs that use colors, fonts, etc. to differentiate language elements, which helps a lot code readability), but at the end the source code is a sequence of characters that a compiler translates into machine instructions. Can't just a standardization of colors and fonts solve the case? So no matter if there are parenthesis in there that might be difficult to read, if they are green and bold we can probably see the difference.
That's kind of reading a book, how can we make the reader that a person is walking and not running? That's kind of easy, we can use words that describe what we want readers to compile in their heads... but why after reading The Lord of the Rings and later watching the movie we feel so weird? What we compiled reading the book was mixed with our memory, creating a lot of other things not there in the words written into that book. Sometimes we get frustrated because internally we had a much better "version" of the things, sometimes we get excited because the movie "version" added a lot of other things we didn't imagined when reading the book (not to mention the book was turned into a script in the process).
I am not saying a language should be a screenplay with visual and sound effects, songs, colors, etc., but small enhancements in how we turn words and grammar elements into a printed collection of pages are interesting! Things beyond a black ink on a white paper... take children books as an example, when the main character is excited, it shouts, and that is visually represented with big letters in the page. When we get older our books are just as programming languages... 300 pages of black ink, same font, over and over and over. For our imagination it full plate, we don't want everything to be there... but sometimes watching a movie makes us much more emotional as we see people crying, suffering, dying, etc.
Anyway, I was just sitting here looking for the Go 2 status and ended up reading on this gist, and felt like proposing a more childish and colorful coding experience. At the end of the month we get our salaries by coding lines and lines of text, but at the end of each day we hope to be artists, craftsmen, inventors, isn't it?!