
Maybe adding generics to Go is about syntax after all - Supermighty
https://dave.cheney.net/2018/09/03/maybe-adding-generics-to-go-is-about-syntax-after-all
======
ilovecaching
It's been three years since I started writing Go professionally and I think
adding generics to Go is one of the worst ideas I've ever encountered. I love
Haskell, and I much prefer hindley-milner type systems, type classes, and real
sum types. I see the value of generics where appropriate. Golang, however,
made the tradeoff to sacrifice anything near that level of abstraction, and
it's success might largely be attributed to the approach-ability of the
language as a result of that tradeoff. Retrofitting generics onto the language
will be at it's best a death sentence for the principles and ergonomics of the
language. One thing that has become clear to me as I have spent time with
Erlang, Haskell, and Go is that it's much better to use the right tool for the
job rather than trying to manipulate a tool to fit every use case, which is
precisely what adding generics is all about.

~~~
oppositelock
I've also been writing a lot of Go professionally for a long time, and I've
used every part of the language extensively, and I disagree.

There are many times I remember where I've had to implement some sort of
application specific data structure that's not built into the language, and
generics would have made it a lot nicer. I made it generic by using
interfaces, but then you have the problem of throwing away type safety, so to
preserve it, you need wrappers which cast.

It's not a given that adding generics would ruin the ergonomics of the
language. I hope that whatever form generics do take in the end, that they
remain simple. If I see template meta-programming in Go of the sort that
occurs in C++, I think I'll scream, but that meta programming was to work
around limitations in C++'s typing system which Go doesn't share.

~~~
ilovecaching
What do you mean by nicer? I would imagine that something that is application
specific would not require generics.

Interfaces do not throw away type safety if they are used correctly. They are
also better suited for the consumer of the type rather than the definer. One
of your problems might be that you are prematurely abstracting your interfaces
for your callers.

~~~
singingboyo
interface {} definitely does throw away type safety, however, and is
reasonably necessary for some library use cases. Is it possible that you're
coming at this from an angle of building applications for specific purposes,
while oppositelock is building more libraries?

My experience (admittedly limited) is that application writing in Go is nice,
but as soon as I try to build a library for use in several similar (but not
quite identical) cases, maintaining type safety can quickly become a hassle.

------
rplnt
Some discussion on why it might be not:
[https://www.reddit.com/r/golang/comments/9cjw98/maybe_adding...](https://www.reddit.com/r/golang/comments/9cjw98/maybe_adding_generics_to_go_is_about_syntax_after/e5b8akq/)

------
foolfoolz
i haven’t written a whole lot of go, but from what i’ve done and what i see
coming in go 2 i don’t really want to have to write a lot more. go felt like
it was modern-vintage to start with memory management but also
values/pointers. with a heavy emphasis on multi return error handling instead
of generics (Either), nil instead of Option, and mutability by default

and the future seems to be more weirdness. magic methods like handle. list
comprehensions still uncertain. contracts sound like a backwards compatible
break and duplicate of existing functionality.

i used go to use libraries and low memory foot print for my small projects.
but i bet there’s simpler routes out there

------
AnimalMuppet
Major props to Dave for realizing that his previous position (that adding
generics had nothing to do the syntax) was mistaken, and for admitting so
publicly.

------
gregwebs
This might be nice to use in the specific case. However, its important to note
that in the general case, you need a type variable because it is used in
multiple positions. In the function below (taken from the proposal), one is
stating that T is the same type in all usages.

    
    
        func Join(type T strseq)(a []T, sep T) (ret T)
    

This below version is different, each variable could be a different type (that
satisfies strseq).

    
    
        func Join(a []strseq, sep strseq) (ret strseq)

~~~
mopsy
As I understand, the type would be implicitly applied to every func/struc in
scope (which I assume would be the package)

It work quite well for package like a Map so any data structure/algorithm. I
suspect it would be less ideal for filter/map/reduce.

------
arendtio
I think the syntax is essential to the problem. A few days ago I wrote a quite
similar post about the difference between Generics and Interfaces:
[https://gist.github.com/arendtio/77dd4df5f4b19dc69da35064843...](https://gist.github.com/arendtio/77dd4df5f4b19dc69da350648434a88a)

------
wbl
Why not adopt standard ML's polymorphism semantics? Is there some type theory
issue I don't know about preventing that?

~~~
danharaj
I don't know much about Go. Does it have subtyping? Mixing polymorphism and
subtyping isn't obvious. Stephen Dolan's thesis on algebraic subtyping [0] is
the first really satisfactory answer, all previous ones having significant
drawbacks, but it's only 2 years old, untested in a production language.

Considering how conservative, bordering on reactionary, the philosophy of Go
seems, I think it's politically untenable.

[0] PDF:
[https://www.cl.cam.ac.uk/~sd601/thesis.pdf](https://www.cl.cam.ac.uk/~sd601/thesis.pdf)

~~~
atombender
Go does not have subtyping. Its type system is sufficiently rudimentary that
steering it towards ML would just be just too extreme at this point.

------
cyphar
On the topic of "syntax is important" (though not related to generics -- other
than by aside to the Result type) I'm still not a huge fan of the handle
syntax -- I reckon something more similar to Rust's .map_err() would be
handled better because lots of people now use pkg/errors[1] which has the
boilerplate of

    
    
        if err != nil {
            return errors.Wrap(err, "some message that is unique to this line")
        }
    

and the handle construct doesn't really handle (excuse the pun) this properly
(you have to re-define handle each time or have a local variable or something
similar). Which means that the new construct won't actually be massively
helpful unless the only thing you want to do is errors.WithStack or just
bubble the error back up.

But I do get why they couldn't get .map_err() -- because that's something you
can only really do if you have a Result type.

[1]: [https://github.com/pkg/errors](https://github.com/pkg/errors)

~~~
ainar-g
Yeah. I always try to include some context with my errors, so the only actual
uses for check/handle I have is

    
    
      handle err { log.Fatal(err) }
    

in quick "scripts". And even there I should probably provide more context. All
clean-up work is already done in defer.

Overall, check/handle feels like a poorly-thought slap-on to me, on par with
ON-units from PL/I[1]. And PL/I is not something you want as an inspiration.

[1] [https://en.wikipedia.org/wiki/PL/I#ON-
units_and_exception_ha...](https://en.wikipedia.org/wiki/PL/I#ON-
units_and_exception_handling)

~~~
cyphar
Right -- people _already_ use defer as a way of mapping errors and doing
cleanup in an already awful way:

    
    
        func something() (Err error) {
            defer func() {
                if Err != nil {
                    someCleanup()
                    Err = fmt.Errorf("wrapping: %v", Err)
                }
            }()
            return blah()
        }
    

_And_ unlike handle you can actually give an arbitrary mapping function, while
as far as I can tell you'd need to do something like

    
    
        handle err { return mapError(err) }
    

Rather than the more ergonomic

    
    
        handle mapError
    

Or

    
    
        handle func(err Error) { return mapError(err) }
    

But whatever -- all of this is pretty useless. The main benefit of check in my
mind is actually that you would be able to easily bump your test coverage --
because Go test coverage is based on lines and so the repeated use of

    
    
        if err != nil {
            return err
        }
    

artificially dilutes your test coverage (you don't need to test that every
error is propagated -- that's just doesn't make any sense and might not be
possible if you are returning directly from os.* functions that don't even
have fault-injection).

------
fithisux
Why not reuse Common Lisp macros?

~~~
gnulinux
Why not use lisp syntax, at all? Why do we have to reinvent syntax every
single generation? I'm not an old school engineer (I'm in my early 20s and in
my first job, freshly outta college) but I can't see why we don't use lisp,
prolog or ML syntax for _everything_. Seems vastly better than C-like synaxes
in all ways I can think of, easier to implement, easier to extend, easier to
read (imho) etc... When I program even in a low caliber lisp like elisp (which
I do routinely since I use emacs) it feels like syntax gets in my way less
frequently compared to python, C, Javascript etc... I just wanna think about
my program, and not the syntax. If lisp is not expressive enough, why not just
ML? Why do we have to reinvent it so many times when syntax is not an
interesting or relevant problem (e.g. Rust syntax)?

~~~
evmar
I tinkered a bit (as many others have too!) with a lisp syntax for C, just to
see what it'd feel like. That is, exact C semantics (so not inventing
anything) but sexpressions.

What I remember struggling with is that it was surprisingly kind of hard to
remember when parens are necessary where. For example you might define an if
statement:

(if (> x 3) (printf ...))

Looks nice, but what about if you want multiple things in the body? Your
options are

(if (> x 2) (progn (printf a) (return b)))

Where "progn" means "curly braces" effectively, or instead always requiring a
list-shaped argument

(if (> x 2) ((printf a) (return b)))

which means in even the single-expression case you have to remember to always
double-wrap

(if (> x 2) ((printf a)))

And now throw in handling of 'else' into the above. It gets surprisingly hard
to use surprisingly fast. This problem repeats everywhere (function
definitions, type declarations, for loops, and so on).

What all the above really clarified for me is that sexps work great in
languages like lisp/scheme that are designed around them, but they don't solve
syntax for free.

~~~
kazinator

      (when (> x )
        (printf a)
        (return b))
    
      (cond
        ((> x 3) (printf a)
                 (return b))
        ((< x 2) (printf b)
                 (return a))
        (t (return c)))
    

Nobody who works with Lisps thinks in terms of "do I need double parentheses".
You just know that _if_ takes two or three expressions, _when_ takes an
expression followed by zero or more forms, _cond_ takes a sequence of zero or
more clauses which consist of a test followed by a body of zero or more forms.

If you're designing an S-exp syntax for something else, it's probably best to
keep the familiar things the same; don't make some different _if_ and such.

The Lisp _if_ maps naturally to the ?: ternary operator; _cond_ , _when_ and
_unless_ can compile to cascaded _if /else if/else_.

~~~
evmar
Yes, but you are describing how a lisp programmer would expect things to work.
As my comment was saying, I was exploring whether a verbatim translation of C
syntax works as sexeps, and where it fails.

------
jonathankoren
If Go gets generics, maybe they’ll actually add an ‘implements’ keyword for
interfaces and no more of this:

    
    
      var _ SomeIface = SomeConcrete{}
      var _ AnotherIface = SomeConcrete{}
    

Just to see if you actually implemented all the signatures on the interface
correctly.

~~~
dagss
Go has a lot of pain points but the _one_ positive thing it has over other
languages is how it handles interfaces.

The whole point is that the caller can make new interfaces to fit existing
structs in other libraries.

~~~
pjmlp
Nothing new, ML derived languages also allow for it.

~~~
k__
TypeScript too.

------
artursapek
I was at Gophercon and saw Russ's video where he spent 30 seconds explaining
what generics were to the audience and showed a half-baked example of the
syntax they are considering. I've been using Go for almost 5 years now and
have no desire to see generics introduced. It would ruin what is currently a
very simple and easy-to-use language. Am I the only one who is hoping this
whole thing fizzles out?

~~~
ilovecaching
Yes, the people who want generics are either people who don't use Go, or don't
understand the purpose of Go and the implications of adding them to the
language.

~~~
stouset
Great way here to completely discount the criticisms of people who've used the
language and found it unacceptable how often you have to escape out of the
type system of a "safe" language with `interface{}`.

~~~
ilovecaching
I've written Go for three years, and I've successfully avoided using
interface{}. It's a matter of architecting your application to fit the
language and choosing a different language if your requirements grow beyond
what Go can provide. Golang has made a tradeoff between simplicity and
abstraction, which is a perfectly fine choice. Turning Go into Java is just
going to ruin yet another language with complexity thanks to mob mentality.

~~~
laumars
It's not always that simple. Sometimes you cannot avoid using interface{}.
There is a reason why some SQL libraries use interface{}, why (un)marshallers
use interface{}, and why I use it heavily in my scripting interpreter which is
written in Go.

