1. 41
    Why Go Is Not Good go yager.io
  1.  

  2. 15

    This is a really lucid explanation of the differences between the type systems in Go, Rust and Haskell even if the title is unnecessarily controversial.

    1. 10

      The final point is false: Go has the unsafe package available, for doing exactly the sort of unsafe manipulation he claims is N/A in Go.

      1. 13

        Where “good” is defined by the complexity and feature-set of the type system. As covered in the HN thread, the author of this post is looking for a more featured and complex type system, which Go does not offer. Yes, there are things you can do in Rust and Haskell which are difficult in Go. If you want to do those things, use Rust or Haskell.

        1. 8

          Actually, I think the language he is looking for is D. I get the impression what he is looking for is a Systems Programming Language.

          I think Go, by Pike’s own admission, is a particularly weak contender in that space.

          It would be much more interesting to see that level of analysis between Rust and D, and on the basis of a casual reading of that blog post my thoughts on each Rust vs Go point made were, “Eh, D wins over both…”

          Here is a panel discussion between Stroustrup for C++, Pike for Go, Alexandrescu for D and Matsakis speaking for Rust….

          http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Panel-Systems-Programming-Languages-in-2014-and-Beyond

        2. 9

          “Go is not good because it’s different from my favorite programming languages.”

          I’m not convinced.

          1. 6

            It' more like “Why Go has different design principles” rather than why it’s not good. Go’s designed to be simple, practical and enjoyable to program in. Haskell’s designed for theoretical purity and Rust’s designed to do everything you could think of plus a lot more. Each of these approaches has their niche. I use all three for different things.

            1. 18

              Go’s designed to be simple, practical and enjoyable to program in.

              Unfortunately, the authors of Go have been done an excellent job convincing people that there is a contradiction between a more powerful type system and being simple and practical. Adding basic concepts such as ADTs and generics does not significantly complicate the language or implementation.

              Go is really cheating a whole generation of developers out of progress. We already know not having generics sucks. Dropping down to a single interface type and casting back out sucks. Java went through this already and everyone is both happy to be out of it and sad that it has a lasting impact on the language.

              1. 10

                Adding basic concepts such as ADTs and generics does not significantly complicate the language or implementation

                Can you point to a language with the tiny spec and compositional simplicity of Go that also has an advanced (Milner-Hindley or otherwise) type system and generics?

                We already know not having generics sucks. Dropping down to a single interface type and casting back out sucks.

                Like the overwhelming majority of people who criticize Go’s lack of generics, you seem to operate from the position that generic programming is programming, programming qua programming, and you move forward from there and assume that Go programs are full of unsafe interface{} type assertions, and that Go consequently sucks.

                But—and I can’t say I understand why this is so difficult to digest—but that’s not how Go works. Idiomatic, performant Go doesn’t leverage generic programming techniques in the way that C++ programmers (for example) might expect it to. Go’s “version” of generic programming is behavior-based functional composition: programming with real interfaces. I think you can successfully argue it’s less powerful than generic programming, for certain definitions of powerful, but I think you can’t argue it’s a regression in expressivity, productivity, or safety.

                Go likely requires you to unlearn your default techniques, and saturate yourself in a different set of idioms and paradigms. It’s equally a challenge for folks used to strict OO, to FP folks, to folks used to expressing their domain invariants with a type system. Go doesn’t walk any of these lines deliberately; it has made a deliberate choice to provide fewer features in service of a coherent composite whole, viz. less is exponentially more.

                1. 5

                  Can you point to a language with the tiny spec and compositional simplicity of Go that also has an advanced (Milner-Hindley or otherwise) type system and generics?

                  I spend most of my time in Ocaml and find its spec to be quite readable. Of course, my statement hinges on what the reader believes “significantly” means.

                  you seem to operate from the position that generic programming is programming, programming qua programming, and you move forward from there and assume that Go programs are full of unsafe interface{} type assertions

                  Actually, no, I don’t believe this. In the future I would appreciate if you would ask me for clarification on my position rather than making assumptions. And please drop the condescending attitude.

                  My statement was simply this: proponents of Go, and specifically its lack of generics, often sight that implementing them well in the type system and/or runtime is somehow an unsolved mystery and Go is doing us a favor explicitly deciding to not be innovative. I contend this is false. There are multiple runtimes that implement generics efficiently. One does not need a H-M type system to get them either. I think the claims that yourself, and Rob Pike, make in the link you posted are either ignorant or disingenuous.

                  I especially think Rob Pike’s post is misleading. The argument effectively comes down to C++ vs Go. I would choose Go over C++ any day of the week. But he leaves out the middle ground languages which are rather simple but still contain powerful and expressive type systems (SML, F#, and Ocaml come to mind). He presents a false dichotomy and of course his solution wins, the odds are in his favor.

                  but I think you can’t argue it’s a regression in expressivity, productivity, or safety.

                  I have no opinion on the impact on productivity but it is a fact that the lack of a type system capable of expressing parametric polymorphism is less expressive and less safe. Having a single interface{} cast is, by definition, less safe. In practice it may be irrelevant but that is a different argument. Perhaps you’re right, and it doesn’t matter. I think that’s a shame because having parametric polymorphism in your pocket is very powerful tool, I use it almost every day. And in my opinion it simplifies code considerably. I think powerful collection libraries takes a few years for communities to decide are important so perhaps we’ll see that in Go in the future, or maybe I’m completely wrong and nobody will care. If I were a betting man I would put money on that Go will eventually get generics. Languages simply move in that direction. Even C has a weak form of generic macros now.

                  1. 1

                    proponents of Go, and specifically its lack of generics, often sight that implementing them well in the type system and/or runtime is somehow an unsolved mystery and Go is doing us a favor explicitly deciding to not be innovative.

                    Nobody cites this at all. The Go team, and everyone active in the language and ecosystem, go out of their way to make clear at every opportunity that generics would be welcomed, as soon as a suitable implementation is proposed: one which doesn’t complect other dimensions of the language, which composes with existing idioms, which brings overall value to the language, beyond of the value of generics in and of themselves. (That’s the standard that all features in Go must meet, mind.)

                    If I were a betting man I would put money on that Go will eventually get generics.

                    Consequently re: the above, I agree.

                    Having a single interface{} cast is, by definition, less safe.

                    I agree. If that were how Go programs were structured, your point would carry weight. Since it isn't—since Go programs don’t idiomatically rely on interface{} type assertions to accomplish goals—the point is pragmatically moot.

                    1. 4

                      Nobody cites this at all. …

                      IMO, what you said is just a longer rephrasing of what I said. You said that the Go team does not want them until the complexity vs value trade off is better. My point is: it’s already really good and this claim that it isn’t is ridiculous. We know how to build decent type systems with generics that would fit into Go’s model. Please provide explicit technical details as to what is lacking.

                      Since it isn't—since Go programs don’t idiomatically rely on interface{} type assertions to accomplish goals—the point is pragmatically moot

                      I downloaded the top 8 repositories from the Trending Repositories[1] on Github. They were: cayley, docker, gin, gwitter, peco, pongo2, ui.

                      I then issued the following query: grep -R 'interface{}' $repo | grep -v test | wc -l

                      A crude query, for sure.

                      Of the results, 7 of the 8 repos have interface{} in it (peco). cayley and docker have the most, they are also the largest projects. In total I had 268 lines with interface{} in them across all 7 repos that had hits. The largest projects also have more lines with interface{}. I suspect that is as a project grows the need for code reuse increases.

                      Can please explain this? What am I missing?

                      1. 1

                        We know how to build decent type systems with generics that would fit into Go’s model. Please provide explicit technical details as to what is lacking.

                        On the contrary: nobody, to the best of my knowledge, has proposed a generics implementation that would fit nicely into Go’s model. Please provide an explicit reference to such a proposal.

                        (An implementation is not a high-level description of a generics system, abstract from the host language spec. It’s a specific, detailed description of a generics system, including all of its interaction points with the host language.)

                        I downloaded the top 8 repositories from the Trending Repositories[1] on Github.

                        All of those were recent HN clickbait. None of them are particularly good Go. And the presence of interface{} as a string in the source is totally independent of its use in the API of a module or package.

                        I suspect that is as a project grows the need for code reuse increases.

                        Sure. But interface{} as a generic type specifier is not an idiomatic mechanism for code reuse in Go. I don’t know how many times I need to say this.

                        1. 5

                          All of those were recent HN clickbait. None of them are particularly good Go.

                          This amounts to a No True Scotsman. And even if it is true, your response lacks evidence.

                          Your precise statement was:

                          Since it isn't—since Go programs don’t idiomatically rely on interface{} type assertions to accomplish goals—the point is pragmatically moot.

                          You said nothing about APIs. And it’s unclear to me if being an API function or not is actually a useful distinction to make in this conversation. A bug is a bug, my claim is parametric polymorphism reduces a class of bugs with little expense to the programming, language, or implementation, my evidence being implementations for languages such as SML, Ocaml, Java, C# and Ada. Note: I am not saying Go needs to look like these languages, I am saying these languages demonstrate efficient runtimes with generics.

                    2. 1

                      proponents of Go, and specifically its lack of generics, often sight that implementing them well in the type system and/or runtime is somehow an unsolved mystery and Go is doing us a favor explicitly deciding to not be innovative.

                      Nobody has said implementing generics is mysterious. People have said that implementing generics with the right trade offs is difficult.

                      Do you acknowledge that implementing generics of some sort is not free?

                      If so, could you please articulate what “not free” means? What are the costs?

                      Given those costs, can you imagine any reasonable person unwilling to pay them for some variety of use cases?

                      If I were a betting man I would put money on that Go will eventually get generics. Languages simply move in that direction.

                      One key difference that Go detractors often forget about is that there is some amount of blessed parametric polymorphism in the language. (The only time this is brought up by detractors is to poo-poo it because they feel cheated.) But it’s just another trade off. One can get a lot of mileage out of generic slice and map types (and their associated parametric functions built into the language). It’s not a pretty solution, but it very well could be the difference maker.

                      Consider, for example, that pre-generics Java was painful enough to write that the designers decided to add generics to the language. What does “painful enough” mean? There’s a line somewhere and methinks Go is in uncharted territory.

                      But he leaves out the middle ground languages which are rather simple but still contain powerful and expressive type systems (SML, F#, and Ocaml come to mind).

                      Standard ML is effectively dead. F# is effectively Microsoft only. Ocaml is an interesting choice considering its compiler is so slow (as is mlton for SML). The entire reason Go was even invented was because the creators were getting sick of compile times.

                      Are those languages options? I guess in some technical sense they probably are. But they’re also easily dismissed given the context. (And that’s not to say that I don’t like those languages. I <3 SML. It’s such a simple language.)

                      1. 3

                        Standard ML is effectively dead.

                        Don’t let that stop you: SML# Unless you mean dead as it has a very small community

                        1. 2

                          Nobody has said implementing generics is mysterious. People have said that implementing generics with the right trade offs is difficult.

                          I claim this is false. My evidence is the slew of languages with generics.

                          Do you acknowledge that implementing generics of some sort is not free?

                          I contend that not having generics is more expensive. And just to be clear, I am not talking about C++ style generics, I apologize if I should have stated this earlier, but I am really talking about parametric polymorphism.

                          peterbourgon argues here that if you need a linked list, you’re better off reimplementing the algorithm yourself. If you agree with this perspective then we are unfortunately going to just be talking past each other forever.

                          Standard ML is effectively dead. F# is effectively Microsoft only. Ocaml is an interesting choice considering its compiler is so slow (as is mlton for SML).

                          I was unclear in my phrasing: I was not arguing that Go should not exist and we should use one of those languages instead. I was arguing that those languages represent, what I consider to be, a more appropriate line between simplicity and expressiveness. Giving significantly more bang for the buck. You even state SML is a simple language.

                          Also, the ocaml compiler is actually quite fast, I’m not sure why you think it is otherwise. It is slower than Go’s but if that is the only acceptable speed then there isn’t much to talk about.

                          1. 2

                            I claim this is false. My evidence is the slew of languages with generics.

                            According to you, what are the languages that have an efficient implementation of generics and that could be used as an inspiration for implementing generics in Go?

                            By “efficient implementation”, I mean:

                            • Avoid code bloat caused by an exaggerated code specialization (the compiler generates one version of each function for each possible parametric type) which is bad for CPU cache (this is often an issue with C++ templates)
                            • Avoid slow execution by having only one version of each function with everything passed as a pointer, even when the value fits in a 64 bit register (this is often an issue in Java)
                            • Avoid slow compilation (is it possible to compile a package containing generic function without already knowing the types that will be passed to this function?)
                            • Avoid making the language specification too much complex (type constraints, rules for type inference which is almost necessary to make generics usable, covariance and contravariance – https://blogs.janestreet.com/a-and-a/)
                            1. 2

                              I claim this is false. My evidence is the slew of languages with generics.

                              That what is false? Existence doesn’t mean anything. The Go people want an implementation that meets their criteria, which includes keeping their compilers fast and their language simple.

                              I contend that not having generics is more expensive.

                              Is it? I don’t know. I’ve written a lot of Go and a lot of Haskell and SML and Rust, and when I need an abstract data type in Go, yes it is annoying. Nobody is saying otherwise. How much annoyance are you willing to put up with?

                              I contend that it is not that much in the Go world, probably because blessed generic types cover a fairly large number of use cases in practice.

                              I don’t care about C++.

                              peterbourgon argues here that if you need a linked list, you’re better off reimplementing the algorithm yourself. If you agree with this perspective then we are unfortunately going to just be talking past each other forever.

                              It depends on what your trade offs are. I don’t think there is one right solution for all cases in Go programs. It is true that writing a generic linked list in Go without sacrificing type safety and performance is basically impossible.

                              And what is this supposed to demonstrate? That writing Go programs is annoying? It certainly would be if you needed a performant generic linked list everywhere and were unwilling to sacrifice type safety. If that’s the case, then Go simply does not meet your requirements.

                              I was arguing that those languages represent, what I consider to be, a more appropriate line between simplicity and expressiveness.

                              Then just say that: “For my particular tastes, Go’s type system is not expressive enough.” That’s cool and fine by me. Not everyone’s tastes are the same! (That’s precisely my point.)

                              You even state SML is a simple language.

                              So what? Is it some kind of ultimate paradox to think that both Go and SML are simple languages?

                              Also, the ocaml compiler is actually quite fast, I’m not sure why you think it is otherwise. It is slower than Go’s but if that is the only acceptable speed then there isn’t much to talk about.

                              It’s not just slower than Go’s. It’s much slower than Go’s. All compilers for sophisticated type systems that I’ve used are much slower than Go’s (ghc, rustc, g++, mlton to name a few).

                              The entire reason Go exists was because of slow compile times. So I have no idea why you think Rob Pike should be proposing languages with very slow compilers (by comparison).

                            2. 1

                              Ocaml is an interesting choice considering its compiler is so slow (as is mlton for SML).

                              Source? ocamlc compiles really quickly, and I don’t notice ocamlopt being much slower than other compilers I use (granted I use mostly javac and gcc).

                              1. 1

                                We’re talking about Go here. Ocaml’s compiler is much much slower compared to Go’s.

                                And yes, it’s slower for very good reasons. And that’s exactly my point.

                          2. 2

                            Go’s “version” of generic programming is behavior-based functional composition: programming with real interfaces.

                            I’m curious about this statement. In your opinion, is there a more better way in Go to write the LinkedList example from the OP? (genuinely curious here, not trying to attack Go, which I don’t really know anything about)

                            1. 3

                              is there a more better way in Go to write the LinkedList example from the OP?

                              Almost certainly, you’d never see the code the OP produced in a typical/idiomatic Go program. (The presence of interface{} in an API is almost always an immediate bad smell and red flag.)

                              Rather, Go would respond by challenging the assumptions the OP baked into his example. One (typically) doesn’t ever write a linked list for the sake of writing a linked list. You use a linked list, in the service of some other goal. So let’s talk about that goal. What’s the actual value type? What API do you actually need, for a collection of those value types? Implement that: a specific expression of your concrete problem, rather than a generic expression of a class of problems that resembles the one you have.

                              1. 3

                                What if I just want an IntMap (a map optimized for integer keys), am I supposed to reimplement it every time I want to store a different type of value in it? What’s the golang way to implement something that’s actually generic like that?

                                1. 1

                                  I’m not sure to understand your question. If you want to implement a map optimized for integer keys, then it’s not generic by definition.

                                  1. 1

                                    It’s generic with respect to what you put in it.

                                  2. 0

                                    What if I just want an IntMap (a map optimized for integer keys), am I supposed to reimplement it every time I want to store a different type of value in it?

                                    Yes.

                                  3. 3

                                    Your suggestion here is literally to keep rewriting a linked list implementation for every concrete type that you want in a linked list. This is the exact problem that generics (or parametric polymorphism) solves without the code duplication.

                                    1. 2

                                      Your suggestion here is literally to keep rewriting a linked list implementation for every concrete type that you want in a linked list.

                                      My suggestion is that “linked list” is a means, not an end, and that you should think about what semantics you actually need out of your container API, what performance characteristics for each operation, and so on, and implement exactly that, for your concrete data types. If you need exactly the same linked list API for N concrete data types, yes, it’s N concrete implementations (in the base case). But, in my experience, that’s so rare a situation in the development of an actual service or application as to not really be worth considering.

                                      Go definitely takes the opinionated stance that code duplication is not a fundamental evil, and that DRY and its consequents (e.g. [premature] abstraction) are overused and overvalued. Idiomatic Go probably wouldn’t generalize a block of code until it’s used in several places, where several > 2, and until that generalization yields some other benefits beyond simply eliminating SLOC.

                                      1. 6

                                        My suggestion is that “linked list” is a means, not an end, and that you should think about what semantics you actually need out of your container API, what performance characteristics for each operation, and so on, and implement exactly that, for your concrete data types.

                                        The last 60 years of research in containers has demonstrated that:

                                        • Containers are generally more complicated than the values they contain.
                                        • Performance characteristics generally depend very little on what they contain, but rather the semantics of how one wants to use it.

                                        Given that, your suggestion to reimplement container types every time sounds absolutely ridiculous to me. Even the authors of Go disagree with this to some degree, providing a few generic containers implemented in the language.

                                        If your argument is that having to reimplement containers all the time sucks but Go simply does not need interesting containers, then that is completely different and I have no opinion on that.

                                    2. 1

                                      Interesting, thanks!

                              2. 4

                                and Rust’s designed to do everything you could think of plus a lot more

                                Can you explain why you think this?

                                1. 2

                                  Rust is a very feature-rich language. I use it when I need those kinds of features.

                              3. 3

                                I think this is an excellent example of why new languages that do not build on the past and languages that omit features are detrimental to the minds of their users. From the context of the article, it makes sense that Go is attracting programmers from Python. Most Python programmers don’t know better, for programming tools one could engage in on the platform will get one labeled unpythonic. Nothing in Go will push the boundaries of a Python programmer. Learning a new language should force new thought patterns.

                                1. 7

                                  Go exposes CSP to programmers which may not have had experience with it before. In addition, the Go toolchain and std library are quite nice. While not especially novel, not every language has those either.

                                  Learning a new language should force new thought patterns

                                  I think it is a nice property when this happens, but I don’t think it is a requirement. There are other reasons to learn programming languages – such as: job requirement, better tooling, better safety (eg. pointers), better performance, portability, platform specific, domain specific, business reasons, etc.

                                  1. 0

                                    CSP is arguably a library feature.

                                    Quality is reason to use an implementation not a language. All of those things you mention are great reasons to immerse oneself in Go, but go itself doesn’t cause a Python programmer to think differently about computation.

                                    1. 3

                                      It’s not a library feature because you can’t build “select” or epoll-awareness without runtime help. No one (or, no one should) uses libraries for CSP for those reasons.

                                      1. 1

                                        you can’t build “select” or epoll-awareness without runtime help

                                        Not sure exactly what that means. What qualities in a language are necessary and sufficient to support programming in CSP? The JVM wasn’t designed for CSP, yet it clearly supports many languages that use it effectively.

                                        http://twistedsquare.com/CHP.pdf

                                        1. 11

                                          CSP means things look like they block even when they don’t in order to give you greater efficiency while maintaining clarity. When a channel or socket is being read from, you don’t want to spend a whole thread blocking on that because the whole point of CSP is that you have lots of little threads doing that kind of thing all the time.

                                          Well, first up, any CSP library built on the JVM without patching into its guts won’t know about the blocking behavior of other code and so you’ll accidentally block everywhere making it useless. Without knowledge of a thing making a socket read, mutex lock, or a JNI call to who knows what, that CSP library will just cause a bunch of blocked native threads in your app and massively reduced latency, throughput and efficiency.

                                          I could totally stop there and just say “and that’s why no JVM CSP library has ever taken off”. But I’ll push this a little more.

                                          On a modern OS, you need green threads to do this CSP fanciness because the native threads have too much overhead (in memory cost, creation time, etc. etc.) to be created so willy-nilly. When the CSP program comes up to a spot where its going to do a channel or socket read or write, your scheduler goes “oh, cool, we’ll just put this green thread aside until the channel becomes ready (or epoll fires in the socket case) and use the native thread it was scheduled on to do work for other green threads”. Then when the channel’s ready (or the epoll fires), the scheduler brings the paused goroutine back on to a native thread and starts up the code at the spot it was left at.

                                          It takes a lot of work to make CSP work fast. m:n scheduling is hard enough, but you’re trying to get the efficiency to the point that it’s only a small amount more expensive than “just putting the stack pointer in the right place”. All this takes some deep systems access (and expertise) to do!

                                          Look at select works in Go. Notice that you can’t do that efficiently without owning how green threads (goroutines, in go’s case) are scheduled. Also, notice that you can have multiple channel operations your goroutine can be waiting on and you’ve got to make that scheduling constant time.

                                          Alongside, cgo (the Go C interface) informs the runtime when you’re entering C code and that it needs to assume the native thread is going to be blocked while in C-land. Only way to do that fast on the JVM is to patch up JNI, and JNA.

                                          Which is why all these CSP libraries written on top of the JVM are pretty silly. A CSP JVM library doesn’t have access to underlying socket, file, mutex and other resource access that the JVM or the Go runtime does to prevent blocking. A CSP JVM library doesn’t even have enough speedy systems access to write a really fast scheduler. All of them are either very broken or very inefficient and usually both.

                                          1. 1

                                            My question wasn’t why is the JVM not suited for CSP. My question was what is necessary to support CSP. Your citing the need to call into C code form the JVM to implement the version of lightweight coop threads that Go uses is a red herring. My question was about CSP, not Go, not the JVM.

                                            http://www.dabeaz.com/coroutines/

                                            1. 1

                                              All of these critiques apply to every CSP library written in a language that do not have CSP as a core foundation. Map the JVM lingo into Python lingo (since you linked to some stuff in that). Coroutines don’t solve the API problems!

                                            2. 0

                                              Nothing about CSP requires threads. Continuations and waiting on a file descriptor are sufficient. Silly.

                                  2. -5

                                    It’s interesting to see with vulgar articles like this one, how many proponents of a system are interested in simply derailing the possibility that the system they’ve invested themselves into could somehow be imperfect, and how many are interested in actually creating conversation to either better educating the community if the system is right and perception is wrong or improving the system if there is an issue with it. Discussion rarely gets past micro-aggressions, condescension, and unsupported opinions presented as facts; this is where the “community” will draw out where they stand. And reading through this thread (as well as some of the discussion about said subjects on the golang-nuts mailing list, although not so much the golang-dev list), it’s worth a reminder that if a complaint is being commonly leveraged against the system you’re so valiantly defending, maybe more of the community needs to take a better look at these issues and how they deal with them.