
Go generics draft design: building a hashtable - mdlayher
https://mdlayher.com/blog/go-generics-draft-design-building-a-hashtable/
======
malcolmgreaves
Since this post is illustrating the use of generics, why not go all the way
with the get implementation to return an Option[V] type? It's a natural thing
to do here. The return type is already a kind of sum type: it's either the
value you want (non-zero value, true) or it's not (zero value, false). If the
implementation uses the optional type, it'll become impossible to write code
that uses the zeroed-out returned value incorrectly. Calling code must always
explicitly check the returned Optional[V] value to access the value of V and
continue or to perform some code to handle the not present case. As it stands,
it's very possible to ignore the 2nd returned boolean value and write code
that'll easily break.

Now, I can see why the author would _not_ want to do this, since this
"explosion" of sum-typed things is present in all go code (e.g. the err :=
...; if err == nil { ... pattern). So, it might be easier for Go programmers
to see how they could use generics in their own code by re-using this pattern.
However, I think this is a disservice to why generics are an incredibly useful
construct in programming languages. They can be used to align code more
closely with the semantics that the programmer wants to convey.

~~~
ccktlmazeltov
I really wish Go would pursue sum types instead of generics

~~~
qtplatypus
Sum types and Generics solve different but related problems. So they are not
really comparable. I would like to see both get implemented.

~~~
ccktlmazeltov
I'd say it's really hard to abuse sum types, I wouldn't say the same of
generics.

~~~
qtplatypus
That is true. However sum types don't allow us to abstract over common code.
For example a sum type wouldn't allow us to create a type safe channel "fan
in" function that works for all channel types.

------
iand
The author asks about using the same hash function as the builtin Go map. This
was recently exposed in the standard lib at
[https://golang.org/pkg/hash/maphash/](https://golang.org/pkg/hash/maphash/)

Specifically
[https://golang.org/src/hash/maphash/maphash.go?s=1316:1346#L...](https://golang.org/src/hash/maphash/maphash.go?s=1316:1346#L203)
links to the internal hash function.

~~~
mdlayher
Right, but you can only write strings or bytes to the hash, not integers,
booleans, structs, etc. So the problem remains.

------
arghblarg
For what it's worth from the Peanut Gallery™, I find the proposed syntax quite
readable and am happy there are no angle brackets <> stabbing my eyes.

It seems powerful enough to cover most cases of generics. Architecture
astronauts will never be happy, but tough.

~~~
IshKebab
I'd definitely prefer angle brackets. With everything being parantheses I just
get lost keeping track of what is what - they're now used for type parameters,
parameters and return values, one after the other with no separation, and two
of them are optional. Quite confusing if you ask me.

~~~
Cthulhu_
And yet, if you think about parsing it, they're all ways to group things
together. Avoiding adding reserved characters (see also the discussion about
adding ternaries) is a worthy goal to pursue. Keeps the parser(s) simpler too.

An IDE / coloring scheme can help make things look more distinct if need be.

------
int_19h
For a long time, Go designers have been saying that it doesn't have generics,
because they don't think that the ways they're done in other languages is
"good enough".

Looking at this, I don't see any fundamental differences from, say, C#.
They're even using interfaces for generic constraints. Did they decide that
they are "good enough", after all?

~~~
sudhirj
Don’t think the type systems itself were up for debate - the Go team isn’t
necessarily going to invent new types of types or advance research in type
theory - That’s already been done enough by Haskell, Scala and others.
Whatever system comes into Go will have already been done somewhere.

The good enough is more drawing the tradeoff line in a spot that’s useful and
clean to maintain, and the place where the core team and the community draws
that line is finally converging.

~~~
int_19h
But that's the thing - they ended up drawing that trade-off line in more or
less the same spot as most other mainstream PLs with generics. I don't
understand why it took so long to basically acknowledge that prevailing wisdom
is correct.

~~~
sudhirj
A refusal to acknowledge that prevailing wisdom was correct is what got us Go
in the first place. I think of the value of the project as being a re
examination of every aspect of programming from first principles. Some
decisions will change, some will be considered to already be optimal.

------
nemetroid
> For my design, I decided to enforce that both key and value types are
> comparable, so I could build a simple demo using two hashtables as an index
> and reverse index with flipped key/value types.

Surely the demo works equally well without this extra constraint? If the demo
had a generic function for creating a reversible mapping it would have been
necessary, but as it stands, this extra constraint comes across as avoiding
having to write the less aesthetically pleasing

    
    
      type Table(type K comparable, V interface{}) struct {
        ...

~~~
mdlayher
Yep that is true. I wanted both values to be comparable for an easy demo but
would write it as you've suggested if I intended to make this a general
purpose package.

~~~
nemetroid
What I mean is that as written, the demo doesn't need V to be comparable.

~~~
mdlayher
Oh wow, for some reason I totally zoned out and finally understand what you
mean. Yes, you're right! I should fix that.

------
nicoburns
The limitation around implementing methods on built-in types seems
unfortunate. If I understand how Go interfaces work correctly, that would mean
that you also can't implement your own interfaces on built-in types. Which
would seem like quite a severe restriction in being generic over them. Perhaps
I'm missing something?

~~~
benhoyt
What would be a real-world example of what you're referring to ("implement
your own interfaces on built-in types")?

~~~
renewiltord
You can do cool things like Ruby's `2.days.ago` in languages that allow you to
implement traits on any type.

~~~
dnr
`2.days.ago` is not "cool," it's an example of the some of the worst things
about Ruby. Suppose I came across a cutesy expression like that in a program I
was reading. How in the world am I supposed to figure out what code is invoked
by that expression and where that code lives?

It's emphasizing writeability over understandability, which is totally
backwards from the point of view of engineering robust systems.

Except it's barely even writeability, it's some bizarre notion that code
should read like English where possible. And of course, since it's not
actually English, it's only possible in limited ways and trying to generalize
past those limits will break. So you have to learn exactly where the limits
are anyway, which is as much effort as learning to use a proper library,
except harder because a proper library will have the decency to stay in its
own namespace.

------
lsllc
Interesting article. I think that using <> for the type specifiers would
possibly be better! For example one could quickly end up with something like:

    
    
      func (obj *SomeType(Q, Z)) Foo(type K, V comparable)(key K, val V) (*OtherType(Q, V), error) {
          ...
      }
    

... Lots of Infuriating & Silly Parentheses?

~~~
BurningFrog
Now that unicode is everywhere I think it's time to use more brackets than [],
(), {} and <>.

Python is the worst, where dicts and sets both use {} and tuples and
"grouping" both use (), each causing real problems.

Some of many possibilities:

｟｠«» ⟦⟧ ⟨⟩ ⟪⟫ ⟮⟯ ⟬⟭ ⌈⌉ ⌊⌋ ⦇⦈ ⦉⦊ ||

Yes, you need a way to type it on non specialized keyboards.

An easy way is is to type it as (( or [[ etc, and let the IDE convert it.

~~~
hombre_fatal
To me this is the ultimate "developer chasing shiny" at the expense of
everything else.

Especially in the context of Go that hasn't even managed to use anything other
than parentheses in its func definition syntax. No language has saturated the
bracket options that come with the standard US keyboard so much that it's
worth bringing in characters that you need additional tooling to type.

If (), <>, {}, and [] aren't enough for a language, something has gone very
wrong.

~~~
qtplatypus
While you normally don't regard it as a bracketing we also use "", '' and ``
to bracket things.

In Perl / / was used to bracket regrexs in C and many C languages /* */ is
used to bracket comments.

------
_ph_
Looks pretty nice and readable. Looks to me like a strong indication that the
updated generics concept is close to what should go into Go.

------
giovannibajo1
Small note: the runtime hash function is available in standard library
(hash/maphash) since Go 1.14. You don't need that introspection magic to
access it.

------
AbuAssar
Offtopic: the blog post title alone is taking up my entire phone screen
(iPhone 7)

------
ardit33
I am not a GoLang programmer, but that just didn't look neither pretty or
readable...

Perhaps, perhaps, hard-core generics are not a great idea after all, and they
should only be in the annotation level of the language (especially for
container types, which is the only place where generics are truly valuable)...

~~~
galkk
The problem is that with current proposal Go severely lacks type inference,
required for generics to look nice and clean.

A lot of the information is already in the definitions, there's no reason to
repeat it, but Go chose to do that.

    
    
        // Reduce reduces a []T1 to a single value using a reduction function.
        func Reduce(type T1, T2)(s []T1, initializer T2, f func(T2, T1) T2) T2 {
    
        s := []int{1, 2, 3}
        sum := slices.Reduce(s, 0, func(i, j int) int { return i + j })
    

and the example from referenced article is even more outrageous

    
    
        t1 := hashtable.New(string, int)(8, func(key string, m int) int {
    

We have function types literally at the same exression, but Go requires to
repeat it.

~~~
mseepgood
> We have function types literally at the same exression, but Go requires to
> repeat it.

It does not require it, the author chose to do it.

    
    
        t1 := hashtable.New(8, func(key string, m int) int {
    

is perfectly valid.

[https://go2goplay.golang.org/p/SbEXpyVl-V3](https://go2goplay.golang.org/p/SbEXpyVl-V3)

~~~
mdlayher
In this particular case, the compiler can't infer the type of V if you omit
the type parameters at the call sites:

    
    
      type checking failed for main
      prog.go2:17:3: cannot infer V (prog.go2:46:27)
      prog.go2:22:3: cannot infer V (prog.go2:46:27)
    

But generally you are right, yes. It is able to infer K at least.

