
Go's updated generic proposal (Contracts) - simonz05
https://github.com/golang/proposal/blob/4a54a00950b56dd0096482d0edae46969d7432a6/design/go2draft-contracts.md
======
tapirl
It is not a proposal, it is a draft.

The new draft improves the contract part. But generic declaration part still
looks some verbose. There are many repetitiveness, such as the Map example:
[https://github.com/golang/proposal/blob/4a54a00950b56dd00964...](https://github.com/golang/proposal/blob/4a54a00950b56dd0096482d0edae46969d7432a6/design/go2draft-
contracts.md#containers)

    
    
        func New(type K, V)(compare func(K, K) int) *Map(K, V)
        func (m *Map(K, V)) Insert(key K, val V) bool
        func (m *Map(K, V)) find(key K) **node(K, V)
        func (m *Map(K, V)) Insert(key K, val V) bool
        func (m *Map(K, V)) Find(key K) (V, bool)
        func (m *Map(K, V)) InOrder() *Iterator(K, V)
    
    

And I didn't find how to write a contract which requires a type must have some
specified fields in this draft.

~~~
antpls
I would believe Generics are highly reusable code, you shouldn't have to write
and read them often. The verbosity you lost in those few lines are earned
every time another piece of code use them.

Also you cannot just criticize without also proposing a solution :)

~~~
namelosw
Generics are highly reusable is true.

But people don't read and write them often because of the language design.

Most Hindley Milner typed languages like ML and Haskell by default are generic
by default and it's not harder or more complex than non-generic code.

Thinking of a function 'swap'. The signature should just be swap : (a, b) ->
(b, a) instead of swap: <T, U>(x: (T, U)) => (U, T). The language design in
most of languages which discourage people using generics.

In fact, non-generic code usually introduce couplings to the argument type,
making things harder to change, and violates the least power principle.

~~~
Faark
This works nice for something as simple as a swap. But more complicated use-
cases have requirements. I want those explicitly stated rather than inferred
by a compiler. Abstracting the actually used features behind names like U:
String, T: Map is can keep complexity hidden. Or would you prefer the compiler
to auto-generate them for you from usage? (like U: must have
ChatAt(int)->Char, Length->int; T: must have put(U), get(U), Length->Int) I
certainly prefer names. And those have to be declared somewhere.

------
latchkey
I've been doing some work in Dart/Flutter recently and I'm finding it to be a
nice mix of Java/JavaScript/TypeScript/Golang. It has generics which work
pretty well, although I've already found a very small bug in it [1].

Initial reading of this document makes me feel it is really over complicated
compared with Dart.

[1] [https://github.com/dart-lang/sdk/issues/37626](https://github.com/dart-
lang/sdk/issues/37626)

~~~
proyb
Which part is overcomplicated compared with Dart?

~~~
latchkey
One thing I _really_ like about Dart is the lack of interface objects, they
are implicit. The `contract` proposal feels like having to write interfaces.
The inference in Dart is really nice to work with as the compiler gets it
right most of the time.

The other thing is the documentation is really easy to grok and come up to
speed on:

[https://dart.dev/guides/language/language-
tour#generics](https://dart.dev/guides/language/language-tour#generics)

[https://dart.dev/guides/language/sound-
dart](https://dart.dev/guides/language/sound-dart)

Edit: Additionally, mocking (with Mockito-Dart) becomes trivial, which is
great for unit testing:

    
    
      class Thing {
        String boo;
      }
    
      class MockedThing extends Mock implements Thing {}
    
      when(thing.boo).thenReturn('ack');

------
steinuil
I don't understand what's the difference between single-parameter contracts
and interfaces. Aren't contracts only useful when there's two or more type
parameters involved?

Edit: didn't read through the whole thing, I guess contracts can do things
that interfaces don't. I think the overlap still makes things a bit awkward
though.

------
tomohawk
Excellent example of clear and concise writing, introducing a subject.

~~~
azernik
Whatever you think of Go as a language, its documentation is outstanding.

------
baby
I wish there was a way to have generics without:

* letting people being able to abuse them. C++ cough cough.

* making it clear to people reading the code what is happening.

I’ve noticed a few things:

* associated types in Rust are much clearer than generics

* one letter generics are “overly generic” and do not describe enough.

* the declaration of the type really adds verbosity

So what can a language do?

1\. Maybe change the syntax to this one:

    
    
      func eat(food []generic.Type)
    

Or

    
    
      func eat(food []g::type)
    

Or something that doesn’t involve adding more <> or ()

2\. Forbid one letter generic or force a description of the generic or even
better: force listing all the types that are currently using this generic NEXT
to the generic!

    
    
      g.type -> {egg, bacon}
      func eat(food []g.type)
    

But then you can’t use a generic from another package... but maybe it’s a good
limitation?

Or force a generic to have a description, which is what golang is doing with
interfaces. The problem is that we can’t combine difference interfaces like in
Rust/ocaml

~~~
mehrdadn
> without... letting people being able to abuse them. C++ cough cough.

Curious what you consider "abuse", given the standard library itself uses
generics in all sorts of ways?

~~~
baby
For example. Typical C++ code:

    
    
      Thing<A<B, C<D>, E>>()

~~~
mehrdadn
I know the syntax, but what about this is "abuse" of generics exactly?

~~~
baby
It makes the code unreadable.

~~~
mehrdadn
That's not people abusing the syntax, that's the syntax being intertwined with
the semantics. Mind you, a similar thing can pop up in a language like C#, it
just happens less often.

------
threeseed
It is just me or do contracts look a lot like type classes.

~~~
choeger
Sshhhht! Industrial players don't like to hear that type systems are a
thoroughly mapped space. They like to think that their design is different and
does not adhere to the laws of type theory. It is also more fun to watch them
struggle essentially reinventing research papers from 20+ years ago.

Edit: yes, I am exaggerating a little, but I find it really amazing how PL
design is in practice so removed from its theoretical foundations.

~~~
stefano
> reinventing research papers from 20+ years ago

The first(?) paper on type classes, "Parametric overloading in polymorphic
programming languages", is from 1988, so they're reinventing things from at
least 31 years ago. I guess in 2050 Go will catchup with today's state of the
art.

------
simonz05
There is some more information in this liveblog from Ian's talk, Generics in
Go [1], and the *.go2 files in Robert Griesemer's change list [2]

[1]: [https://about.sourcegraph.com/go/gophercon-2019-generics-
in-...](https://about.sourcegraph.com/go/gophercon-2019-generics-in-go) [2]:
[https://go-review.googlesource.com/c/go/+/187317/](https://go-
review.googlesource.com/c/go/+/187317/)

------
MrBuddyCasino
I don’t quite get what contracts are needed for? This

    
    
        func Print(type T)(s []T)
    

could be written as

    
    
        func Print(type T implements SomeInterface)(s []T)
    

What am I missing?

~~~
hundt
You're probably not still looking for an answer, but since I tried to work
through this question myself I will answer it, if only for my own benefit:

See the "Mutually referencing type parameters" example. [0] Ignoring the issue
of generic types vs functions, suppose you wanted to do shortest path as a
fuction using the Node and Edge types described. You could try to write
something like

    
    
      func ShortestPath(type N implements Node, E implements Edge)(from, to N) []E
    

but how would it be implemented? Type N only promises that it it implements
the Node interface which is just

    
    
      Edges() []Edge
    

but those Edges might not be concrete type E.

So instead you must resort to

    
    
      func ShortestPath(from, to Node) []Edge
    

and use type assertions, which is where we were before generics.

The contracts allow you to constraint the relationship between the concrete
types N and E, enabling callers to work with concrete types directly.

[0]
[https://github.com/golang/proposal/blob/4a54a00950b56dd00964...](https://github.com/golang/proposal/blob/4a54a00950b56dd0096482d0edae46969d7432a6/design/go2draft-
contracts.md#mutually-referencing-type-parameters)

~~~
MrBuddyCasino
> You're probably not still looking for an answer

On the contrary! I think I got it, thanks.

Not sure which version is simpler though, I've certainly never hit the
"Mutually referencing type parameters" issue before. I guess using concrete
types and avoiding vtable lookups is an advantage that matters to Go?

~~~
hundt
> I've certainly never hit the "Mutually referencing type parameters" issue
> before.

I don't think I have either, but according to that document supporting this
use case was considered a minimum requirement for generics. I don't know that
much about generics but maybe it comes up more often than we realize.

> I guess using concrete types and avoiding vtable lookups is an advantage
> that matters to Go?

It avoids the vtable lookups and also, in cases like the mutually referencing
type parameters, provides compile-type type-checking, which you don't get with
the interface-only solution.

I'm also not sure how your proposed solution extends to parameterized types.
Certainly a central motivation of generics is make something like
LinkedList<T> possible.

------
grenoire
Honestly it just reminds me of Rust's traits (granted I didn't read the entite
spec.), could this come with operator overloading too?

~~~
owaislone
It could in future but this document is staying away from overloading. I doubt
Go will ever support it really but this design does not do anything to prevent
it in future.

------
misrab
If what Generics fundamentally solve is having to type out an almost identical
function for many types, why not just have macros?

~~~
YawningAngel
I think most people would agree that templates are more complicated than
generics

------
johnisgood
For a second I thought it was related to {pre,post}-conditions.

------
antpls
I have knowledge in Python and never developed in Go, so I can't comment the
Design choices. On the form, in my humble opinion, the PEP style is easier to
read : presenting rational and constraints before the proposed solution.

It would mean moving (and a bit of rewording) "Discarded ideas", "Comparison
with Java", "Comparison with C++" before "Design" and after "Background".

As a reader, I prefer to be presented with the problem first (with the
constraints and previously explored ideas), then be presented the logical,
"obvious", proposed solution.

