

Things I hate about Go - DanWaterworth
https://github.com/DanielWaterworth/Musings/blob/master/things_i_hate_about_go.md

======
chalst
> There are builtin polytypic functions, but you can't define your own.

This sounds like a first-release issue: getting type systems right in a way
that allows you to grow your language is hard.

> Gorountines are just a hack for storing a continuation in a closure in
> functional languages.

No, I don't think this is fair. Firstly, controlling access to continuations
makes sense, since they can be a world of woe. Second, continuations alone
don't give you any parallelism: that CPS helps you understand the goroutines
does not mean that the semantics of goroutines can be understood purely in
terms of continuations.

See Shivers, 1997, Continuations and threads: Expressing machine concurrency
directly in advanced languages (postscript),
<http://www.ccis.neu.edu/home/shivers/papers/cps-threads.ps>

~~~
DanWaterworth
My problem is not that the runtime controls access to continuations, actually
that's quite a nice thing for it to do. My problem is that it's implemented
using segmented stacks, which is using more memory that just storing a
closure.

------
jesstaa
> The language is privileged

Most languages are privileged. In python you can't define your own calling
convention or add your own flow control. If the language wasn't privileged,
then it would't be a language.

>you can't pass a slice of concrete types conforming to an >interface into a
function expecting a slice of interfaces.

Interface and concrete types are different sizes, so doing this requires an
allocate. The language chooses not to hide this as it would be different from
how all other kinds of slices work.

> Makes it difficult to find out why your program crashed > because the
> information you need has been cut off by the > terminal!

This is a problem with your terminal. Getting a terminal that has a bigger
buffer is a good idea, logging to a file is also a good idea. It would be
weird to have the thing that happened first be at the end of the stack trace.

>If you want to change whether an entity is public/private, >you have to
replace all occurrences.

This is a rare thing to do and you get the advantage of knowing the visibility
of a method or variable as you read it. Code is read more often than it's
written, so they optimise for readability.

>You can't use custom types for the key of a map. To get >around this, you
have to use maps of maps.

The built in map covers most people's needs. If your needs differ then people
have written external packages that implement a hash map. Every additional
feature is a burden on anyone that doesn't use it.

> You can't select over multiple io.Readers without starting > a goroutine for
> each one.

You can't select{} on io.Readers because the io package isn't part of the
language. But you can certainly write a package that uses the OS's select()
syscalls to wait on any number of file descriptors(which would block the
goroutine and the thread it's running on as expected). Yep. But goroutines are
cheap. Also, how do you select over an arbitrary type?

~~~
DanWaterworth
>> The language is privileged Most languages are privileged.

> In python you can't define your own calling convention or add your own flow
> control. If the language wasn't privileged, then it would't be a language.

I'll clarify. I don't think languages should be privileged in regard to types.

>> you can't pass a slice of concrete types conforming to an interface into a
function expecting a slice of interfaces.

> Interface and concrete types are different sizes, so doing this requires an
> allocate. The language chooses not to hide this as it would be different
> from how all other kinds of slices work.

Whether it requires an allocation or not, it's an annoyance. This is just an
example of behavior that is counterintuitive w.r.t interfaces.

>> Makes it difficult to find out why your program crashed because the
information you need has been cut off by the terminal!

> This is a problem with your terminal. Getting a terminal that has a bigger
> buffer is a good idea, logging to a file is also a good idea. It would be
> weird to have the thing that happened first be at the end of the stack
> trace.

I've now set the terminal buffer to unlimited, but it's annoying to have to
scroll back though thousands of stack traces.

>> If you want to change whether an entity is public/private, you have to
replace all occurrences.

> This is a rare thing to do and you get the advantage of knowing the
> visibility of a method or variable as you read it. Code is read more often
> than it's written, so they optimise for readability.

I see your point here, but I'm not saying I agree.

>> You can't use custom types for the key of a map. To get around this, you
have to use maps of maps.

> The built in map covers most people's needs. If your needs differ then
> people have written external packages that implement a hash map. Every
> additional feature is a burden on anyone that doesn't use it.

I certainly don't agree with "Every additional feature is a burden on anyone
that doesn't use it", more like "Every additional feature is a missed
opportunity for a bad headache".

>> You can't select over multiple io.Readers without starting a goroutine for
each one.

> You can't select{} on io.Readers because the io package isn't part of the
> language. But you can certainly write a package that uses the OS's select()
> syscalls to wait on any number of file descriptors(which would block the
> goroutine and the thread it's running on as expected). Yep. But goroutines
> are cheap. Also, how do you select over an arbitrary type?

It's possible. You could have an interface that has a function that sets a
callback that is called when data is available and in this callback you could
then write to a channel. When you read from the channel, you'll get messages
from each of the io.Readers. Goroutines are cheap, but I'm not convinced they
are cheap enough.

~~~
jesstaa
> Whether it requires an allocation or not, it's an annoyance. This is just an
> example of behavior that is counterintuitive w.r.t interfaces.

If I had a 1GB []byte and passed it in to a function taking an []interface{}.
I think it would be really counterintuitive that the function call would
allocate 16GB of memory. While passing it to a function taking a []byte would
allocate no memory at all. You can't avoid this allocation.

It's been considered that it might be nice to be able to convert between
slices of different interfaces and this might happen, but it's not going to
happen for slices of types for the reason above.

The special case of auto-converting a type to an interface on assignment is a
bit weird and creates endless confusion for new people coming from different
language as it makes Go's interfaces look like they are the same as Java or
C++'s. But once you understand it, then it makes sense.

In Java an Interface and an Object are very similar, they both have a set of
virtual methods and interfaces just verify that the type you're passing in has
the expected set of virtual methods to implement the interface. Converting an
array of Java Objects to an array of Java interfaces doesn't require anything
at runtime.

In Go, a type and an interface are very different, a type has static methods
and an interface has virtual methods.

~~~
barrkel
> Converting an array of Java Objects to an array of Java interfaces doesn't
> require anything at runtime.

It potentially costs every time you put something into the array - Java arrays
are covariant. Either a runtime check is needed, or the JIT compiler will have
to create a proof that any particular array access is to a consistently typed
array.

------
tgrisfal
* It's not a mature language (yet).

* I can't seem to stop running into people promoting it.

* It doesn't solve any problems for me (yet).

------
SamReidHughes
> Gorountines are just a hack for storing a continuation in a closure in
> functional languages.

This is just not true. Passing continuations around is hellish enough in
node.js, and when you've got RAII features the way Go has, that increases
their advantage in Go. The memory overhead of coroutines is Worth It.

> You can't select over multiple io.Readers without starting > a goroutine for
> each one.

We found ourselves needing to throttle the number of simultaneous I/O requests
(to disk) well before we reached any kinds of memory problems with coroutines,
but this problem might be understandable on 32-bit systems. Oh well. If you
have ~512K network connections, adding an extra 4GB of RAM is probably
affordable.

~~~
DanWaterworth
But in functional languages, the compiler does the conversion to CPS, leaving
you with a synchronous interface. I don't advocate the node.js approach.

It may be affordable, but it shouldn't be necessary, when it can be done
better.

------
hsmyers
Hell you can't do any of those things in the first 15 languages I learned. In
fact none of the concepts even existed in those languages---so? Bother?
Irratated? Annoyed? I can understand all of those; but hate? I'm hoping that
is hyperbole brought on by momentary programmers frustration. Otherwise it is
a waste of time. Go changes fast enough that perhaps by this time next week
some of these things might have gone away. Some on the other hand might still
be there, but what might change is your understanding of the reasons why they
exist and what you should do to code around them (or through or whatever...)

~~~
DanWaterworth
Some of them are there because of fundamental decisions that the go team has
made; I don't see those being reconciled. Others may be fixed.

The use of the word hate was mostly an exaggeration to attract readers,
partially momentary frustration and partially substantive.

