
Scrapping Contracts: a critique of the Go generics proposal - Merovius
https://blog.merovius.de/2018/09/05/scrapping_contracts.html
======
EdSchouten
I think this proposal looks pretty good, but it does expose something odd
about the core language. Given this example from the article:

    
    
        func Max(type T ordered) (a, b T) T {
          if a < b {
            return b
          }
          return a
        }
    

It would make me assume that if I were to write a function that looks like
this, it would at least accept the same type of arguments:

    
    
        func AnotherFunction(a, b ordered) {
            ...
        }
    

As in, substituting 'T' with 'ordered'. Unfortunately, it would not. In this
case, the arguments are interface objects, which are pointers under the hood.
Calls to the function have to be adjusted to pass in pointers. I think Go
would have been more consistent if instances of interface objects always had
to be prefixed by * . Concretely, this would throw a compiler error:

    
    
        func ReadStuff(r io.Reader) {
            ...
        }
    

Instead, it should be written as follows:

    
    
        func ReadStuff(r *io.Reader) {
            ...
        }
    

As in, r is a pointer to an object that implements the io.Reader interface.

~~~
majewsky
> It would make me assume that if I were to write a function that looks like
> this, it would at least accept the same type of arguments:
    
    
        func AnotherFunction(a, b ordered) {
            ...
        }
    

Not really. If we consider `ordered` an interface, then `func(a, b ordered)`
does not enforce typeof(a) == typeof(b), only that both types implement
`ordered`.

~~~
EdSchouten
Exactly, which is why I said: "it would at least accept"

------
atombender
This is, in my opinion, the best critique of the Go generics proposal so far.
There's more discussion on Reddit:
[https://www.reddit.com/r/golang/comments/9d1zsz/scrapping_co...](https://www.reddit.com/r/golang/comments/9d1zsz/scrapping_contracts/).

~~~
everdev
The Go community is against the proposed implementation and has provided a
well thought out alternative. If the past is any indication, the community
proposal will be rejected and we'll have the original proposal that no one is
really happy with.

It's fun to dream though.

~~~
akavel
What? Several people expressed constructive critique. You extrapolate this
_way_ too much in this comment. I, for one, am part of "Go community" and am
certainly not against the proposal. More than that, I _love_ that people
smarter than me are currently publicly discussing and exploring alternatives
and disadvantages in a civil manner.

------
chmike
I also felt uneasiness with the definition of contracts but couldn't put my
finger on the reason. Merovious made now clear to me that the language to
define contracts is the problem.

Nevertheless, I do think we need to keep contracts because they are a superset
of interfaces. Interfaces specify only a collection of methods, and contracts
specify a collection of methods and operations (e.g. ==). Remember that base
types can also have methods. We may also want to restrict types to referenced,
or unreferenced, because it determines if an assignment make a copy or not.

Contracts are types of generic parameters. I would find more natural to define
these generic parameter types like this

    
    
        type foo generic { ... }
    

As suggested by Merovius, the content would be a list of method signatures,
operations, interface names or predefined operation set names.

~~~
heavenlyhash
> methods _and operations_

Isn't a major part of the idea in the article that we can replace the "and
operations" part with a system of pseudo-interfaces that encompass those basic
operations?

------
atombender
To me, the most interesting part of this whole Go2 discussion is the amount of
brainpower being expended on keeping the language on its current path of
pragmatic minimalism and not careen off into type system land akin to
contemporaries like Rust, C#, Scala and Swift, while at the same evolving and
modernizing it. It's not easy.

Not that complex and rich type systems are a bad thing, but there's something
to be said for Go's austerity. Having been a Delphi/ObjectPascal programmer
for many years back in the 1990s, Go feels very familiar to me, with good
approaches to many challenges. I do think Go's designers made some crucial
errors that should have been obvious at the time, and that we are paying for
them now. In attempting its special flavour of minimalism, they arguably
didn't bother to learn enough from past language experiments. Of particular
note, Modula-3 has a very pragmatic approach to generics [1], which later
inspired similar approaches in Delphi/ObjectPascal/FreePascal. (I'm not saying
Modula-3's design is right for Go, though.) If you want to look at extreme
engineering pragmatism (as opposed to type-theoretical elegance you find on
Idris, Agfa etc.), look at Ada, or even plain Pascal. Both have range types
and enums and other things which grant the language first-class mechanisms for
tightening a program's semantics, but they do so in the syntax and compiler,
not through advanced type system shenanigans. (Oberon, which influenced Go in
many ways, actually did away with enums in the name of simplification.
Minimalism can go too far, too.) Ada, arguably the most engineering-oriented
language ever conceived, also has tagged unions, although I don't think they
were ever used for error returns, since Ada has exceptions.

I'm not at all worried that Go adopting generics will lead to type system
madness, especially given how conservative the designs have been so far. I
think the amount of discussion and lively idea brainstorming in the community
right now is a very healthy sign. More than some other language communities,
there have been times when it's seemed -- right or wrong, it's hard not to get
this impression from the vantage point of an ordinary developer -- that the Go
team has historically been somewhat distant, preferring to unleash finished
implementations on the world instead of evolving their designs in
collaboration with the community ( _cough_ vgo vs. Dep), and I think this is
an opportunity to open up the process a bit. I don't know about anyone else,
but I'm certainly very much enjoying all these articles being put out.

[1]
[https://www.cs.purdue.edu/homes/hosking/m3/reference/generic...](https://www.cs.purdue.edu/homes/hosking/m3/reference/generics.html)

~~~
vanderZwan
> _More than some other language communities, there have been times when it 's
> seemed -- right or wrong, it's hard not to get this impression from the
> vantage point of an ordinary developer -- that the Go team has historically
> been somewhat distant, preferring to unleash finished implementations on the
> world instead of evolving their designs in collaboration with the community
> (cough vgo vs. Dep)_

I think one reason may be that "constrained minimalism" and "internet
community" are two things that don't really play nice together.

------
slavapestov
I don't mean this as a criticism, but it's amusing how the Go community is
slowly inventing Swift.

\- "Protocols" that can be used as both constraints on generic parameters, and
existential types

\- Operator requirements in protocols

\- Associated types

All of the above introduces a huge amount of complexity too, and I imagine
trying to retrofit it onto an existing language that does not have generics is
going to be an interesting challenge.

~~~
atombender
To be fair, Swift is on another level, and doesn't strive for Go's rather
relentless minimalism. Go, even with these additions, would be a lot smaller
than Swift, which was intentionally designed around rich, complex
abstractions, including generics and compile-time type constraints, from the
start.

Of course, Swift didn't exactly pioneer these concepts. Scala, C# and Haskell
also provide compile-time type constraints that are heavily based on generics,
and these languages also influenced Swift.

~~~
jjtheblunt
It's always been "curious" that Swift never credited Scala, I thought.

~~~
thayne
My impression when I first saw swift code was "wow, that looks a lot like
scala".

~~~
weego
Anecdotally as a mostly full time scala dev, swift has been the easiest "I'm
bored this weekend I'll pick up a new language for side projects" I can
remember.

------
lazyjones
All these proposals are far too explicit and cause too much boilerplate code,
someting that the Go team so far tried to avoid (or so it seems).

I‘d prefer a macro-like approach to generics where generic parameters are
unconstrained like interface{} until they are used inside the function with
constrained operators/function calls, which define the limitations for the
parameters. Just like a macro that expands to code that only works with
particular types, it will simply generate an error when the compiler detects
an incompatibility between the types used by the caller and the operations
inside the function. This would be powerful enough and simple to understand.

~~~
aw1621107
> I‘d prefer a macro-like approach to generics where generic parameters are
> unconstrained like interface{} until they are used inside the function with
> constrained operators/function calls, which define the limitations for the
> parameters.

That sounds awfully like how C++ templates work, and I think that it’s pretty
much universally agreed that that approach is not ideal due to the potential
for supremely terrible error messages (among other drawbacks, probably).
Contracts allow for type checking at the call site, which is significantly
more user-friendly.

~~~
lazyjones
Compiler errors and their quality are fundamentally an implementation issue.
I‘m talking about the design of the language and what the user has to deal
with all the time, not just in the error case. Designing primarily for
beautiful error messages is a flawed concept IMO.

~~~
aw1621107
> Compiler errors and their quality are fundamentally an implementation issue.

I don't really agree here; language design can place constraints on the
quality of errors emitted by implementations. There's a reason Concepts are
such a widely desired feature in C++, even though template errors now are
vastly better than they were in the past --- there's only so much you can do
with the language in its current state.

> I‘m talking about the design of the language and what the user has to deal
> with all the time, not just in the error case.

I'm inclined to say that fixing/avoiding type errors is likely to be far
closer to what programmers have to deal with all the time than having to write
contracts, especially for library types.

In addition, it's not just the error case that's involved here. If you're
writing generic code with constraints on the involved type(s), those
constraints are probably going to have to be documented somewhere anyways. Why
not write them in your source code where the compiler can help?

> Designing primarily for beautiful error messages is a flawed concept IMO.

I'm not saying that the design has to be primarily for beautiful error
messages; it's just that useful error messages are important and whatever
design emerges should try to enable helpful error messages.

------
w323898
I'll admit that I don't have much experience with generics, but can't we get
done what we're trying to get done just by letting builtin operators get
overloaded, then making an interface for those functions and using the
interface? From what I've seen, it appears the main point is to let various
types that can all get "+" applied to them get handled with one function. But
defining the interface is the contract and the interface is any type that
fulfills it, so...? What am I missing?

~~~
euyyn
I think the graph example of the article, instantiated using an adjacency
matrix, shows nicely a need for generics that has nothing to do with
operators.

