Hacker News new | past | comments | ask | show | jobs | submit login
Things I hate about Go (github.com/danielwaterworth)
11 points by DanWaterworth on June 15, 2011 | hide | past | favorite | 13 comments



> 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


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.


* 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).


> 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?


>> 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.


> 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.


> 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.


I think I'm right in saying that haskell does what you'd expect when you have a list of some typeclass. The difference is that Go has opted to use RTTI, whereas haskell does it a compile time. So, it's not impossible to do well.


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

C++, relatively speaking, is not "privileged" with regard to types, but that lack of privilege is bought with great pain, particularly in exception safety mixing with copy constructors. The lack of semantic guarantees when overriding operators - like assuring the unsuspecting library user that x += y does something similar to x = x + y - can also be a genuine problem. Java evaded the whole thing for quite justifiable reasons, IMO, and C# implements operator overloads in relatively specific ways to avoid some of these problems - and still doesn't let you have copy constructors, assignment operators, or destructors on value types.

By baking in certain assumptions about types into the language, the commonality across all code written in the language is increased, and the costs of documentation and communication at independently developed library boundaries is reduced. There are also other benefits; with baked-in knowledge, the language may support value literals for specific types in the lexer and parser. Doing this in a non-"privileged" way would require macros or compiler plugins, which are themselves undesirable for a whole set of other reasons.


> 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.


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.


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...)


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.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: