Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Problems with Go's design (medium.com/tucnak)
77 points by lladnar on Oct 28, 2015 | hide | past | favorite | 86 comments


While the title is linkbait-ish, the article makes interesting points with code examples, and the author is not 'religious' -- he concludes by saying he will continue to use Go.

Like most other languages, Go is neither great nor bad; it's just another tool designed by people to solve a specific set of problems -- in Go's case, people who in the past have had to develop very, very large, complicated systems with lots of concurrency. For projects like that, Go would be a great choice; for other projects, it might not be.

In some circumstances even Ruby can be a better choice than Go for high concurrency. I recently read a post on HN in which the author (someone with an Erlang background) lays the reasons why he chose Ruby for a highly concurrent application. When I first saw the link, my immediate thought was, Ruby!!?? But then I read the post and the reasons were all very sensible and practical-minded, so in that case Ruby was arguably a much better choice than Go, Erlang, Scala, Rust, and the like.

Here's the post: https://news.ycombinator.com/item?id=10394450 (direct link: https://github.com/rustyio/super-imap#why-ruby) -- I really liked the rational, practical, 'non-religious' tone.

Suggestion: don't call Go or any other popular language "poorly designed" with a short blog post. Instead, say "it doesn't fit my current needs."


> don't call Go or any other popular language "poorly designed" with a short blog post. Instead, say "it doesn't fit my current needs."

that's not what the author wanted to say though. very, very often someone says "this is bad" when they actually mean "this doesn't fit me". in this scenario, I think the author made a very good case for "it's poorly designed".


RodericDay:

Language design decisions by superstars like Robert Griesemer, Rob Pike, and Ken Thompson (the creators of Go) that may seem inconvenient or annoying when working on small to mid-size projects could very well make life much easier when working on giant projects which take years and do not fit on any single person's head.

I'm reminded of this insight by Lawrence Kesteloot of Dreamworks: "What’s particularly hard is having technical discussions with someone who hasn’t broken through as many walls as you have. Breaking through these walls means making different trade-offs, and specifically it means making a decision that seems to make less sense in the short term but will help later. This is a hard argument to make—the short term advantages are immediately demonstrable, but I can’t convince anyone that a year from now someone may make an innocent change that breaks this code."[1]

Unless you personally have led Internet-scale mission-critical software projects at a place like Google, as the authors of Go have, I don't think you're in a position to judge their decisions too harshly. Neither am I, BTW.

--

[1] http://www.teamten.com/lawrence/writings/norris-numbers.html


I keep seeing this argument all the time. Lets put a counter-argument-by-authority: Fran Allen [1] thinks that the C was a huge step backwards in language design [2], and there is no reason to think that Go didn't repeat the same pattern.

Interestingly, the article you quoted mentions functional programming and immutable data as the step to go from 200K to 2M lines. Go is fundamentally incapable of functional programming* and its builtins allow pervasive mutable state.

[1]: https://en.wikipedia.org/wiki/Frances_E._Allen

[2]: http://www.amazon.com/Coders-Work-Reflections-Craft-Programm...

* Its impossible to support FP without generics. Even the most basic higher order functions require type variables.


The guarantees offered by functional programming and immutable data are great, but they mostly evaporate when you move from a single process application to a multiple processes system, distributed on many machines and communicating through IPC.

Are you aware of a 2M lines code base that fully relies on FP and immutable data?


I beg to differ: the guarantees functional programming and data immutability offer are exactly the sorts of guarantees needed for scalable parallelisation.

LOC is not a useful metric for the utility of a program, in fact the reverse is true in terms of maintainability and places for bugs to hide.

This chap claims to write very robust programs, entirely functionally that are worth billions in their operation.

http://logicaltypes.blogspot.co.uk/2015/08/pure-functional-p...


I agree that "the guarantees functional programming and data immutability offer" are very useful for "scalable parallelisation".

But this is not what I was answering to.

I was answering to the idea that FP and immutability are useful when you scale from 200K to 2M lines of code:

> the article you quoted mentions functional programming and immutable data as the step to go from 200K to 2M lines

In my opinion, this argument is irrelevant.


The argument being that non functional programming has fundamental limits to its scalability, seems like a very important question as the complexity of code begins to grow exponentially.

Large imperative codebases are inherently fragile, hence the need for test driven development - good batteries of tests offer demonstrable reliability.

Functional programming offers provable reliability.

Provability is not just for scaling complexity but also for much simpler software that absolutely must not fail.

Imperative code becomes hard to reason about before 200K LOC is reached.


The counterargument is that very large codebases tend to be composed of multiple services. Each service lives in its own process and maybe in its own machine, and communicate with other services through IPC. At that point, you've lost the benefit of functional purity and immutability, because each service can maintain some "hidden" state.

You're right, above some threshold, it becomes hard to reason about imperative code with mutations. Your preferred solution is to adopt functional programming and immutability. Another solution is to decompose your system in multiple communicating services. Google's codebase is a very well known example of the latter approach.


This is false. Well, it may be true in Erlang, but its false in Haskell, where there is always clear separation between equations and effects/state no matter the source (IO to other services or local state). Not to mention that there are tools such as STM that let you deal with state in a much safer and less error-prone way.

Separating the state between multiple communicating services is basically the same strategy as OOP (objects that communicate via messages and encapsulate state) but with stronger encapsulation requirements (access only via API, you can't just "reuse" methods willy-nilly all over the place but you have to come up with a sensible library or a third service, etc etc).

It works, yes but it works about just as much as OO does. Which is... not very much. And it also comes with its own tradeoffs (good luck getting atomic changes over multiple microservices)


Erlang's very existence is a pretty solid refutation of the claims you're putting forth. :P


I don't think so. An Erlang process has no mutable state. But it can communicate with another Erlang process to set/get some state. This is how a lot of mutable structures are implemented in Erlang.


Do they, now? Or do they only evaporate in Erlang?

Since most FP codebases do the same with a few orders of magnitude less code, no, I'm not aware. Though I imagine that if they had to copy their code for every single instance of their generic typeclasses, they would quickly amass many millions of lines.


I actually had a subtle bug caused by variable shadowing. The current behaviour of := is error prone and IMO should be revisited. The situations where it cleans things up are offset by situations where it doesn't work (including in compound statements where it often ends up being I'd like to declare and check err but I also want to be able to use the other variable if err==nil).

Another thing not mentioned is type switches, that feels like something that was duct taped onto the language. I think it would have been better to be able to use .(type) anywhere (e.g. in an if statement) and not have some sort of special switch statement. So make types more of a first class thing. It's obviously more work though but there is probably some middle-ground there that would make it seem less like a hack.

I think the behaviour of slices is reasonable given that they are not as high level as they are in some other languages. They're basically a slightly nicer facade over pointer arithmetic and they are designed for efficiency and performance. I think overall they're fine. The "..." is a little awkward (appends could be a little nicer).

Just use goimports to and you won't see unused import errors any more. Works for me and makes things cleaner.

All in all I think there are some rough corners that could use a little polish but that can still happen, it's a new language after all. It's good enough to be really useful.


Yes, the semantics of ":=" for the multiple-variable case are just weird. You can create one variable and assign another, and it's invisible what's happening. Not too good.

Variable shadowing is probably a misfeature anyway. If shadowing were an error, sometimes you'd have to change a local variable name. No big deal. An amusing alternative would be to require that any name imported into a block must be at least 3 or 4 characters long. You don't want short global variable names anyway; if "x" is a global variable, something is wrong. Then short local variable names could never clash.


The behaviour you are talking about isn't anything to do with :=. It's just how scoping works in Go. := is just syntactic sugar.

http://play.golang.org/p/bh6pm_8s9S


Probably the root of the issue is that it's slightly more complicated than syntactic sugar:

"Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original."

I.e. the mix of declaration with re-declaration. I agree it doesn't seem like much but it ends up causing needless confusion. If there was a more explict way of doing this, e.g.:

x, var y = 1, 2 // y explicitly declared

maybe that would be better. not sure.


This is what enables the idiom "foo, err := DoSomething()" where err is declared previously.

Without the mixed declaration/redeclaration you'd have to either declare a different error variable every time in the block, or explicitly declare your result variable:

  foo1, err1 := DoSomething()
  foo2, err2 := DoSomething2()
  // or
  var Foo foo
  var err error
  foo, err = DoSomething()
  foo, err = DoSomething2()


Nailed it. I love Go, but all of the things listed in this article (and more) make me bash my head against the wall more often than I'd like to admit. That said, there are some fantastic things about Go that aren't listed in the article (I feel like gofmt changed my life).

Don't let this article discourage you from investigating Go as a tool to use!


Same here. Mark me down for loving the runtime/community and having issues with the language. Someone's going to target the Go runtime w/ a better language and zero-overhead and I'll be happy. I can't just switch to Nim or D because I still want the Go ecosystem (e.g. /x/crypto/ssh).


I've never understood this, every Go user swears on gofmt for some reason that I just dont get. Can you elaborate how gofmt changed your life? Its a really simple tool and lots of languages have those...


You're right. There are other tools out there. But what I really appreciate about gofmt are a few things:

* It's highly opinionated. It's not cluttered with customization options. This means your Go program and my Go program are formatted exactly the same. Period. I don't have to choose between spaces or tabs or get annoyed that you chose spaces when tabs are clearly superior. * By being highly opinionated, Gofmt just takes care of those decisions. You focus 100% on coding -- not on stylistic decisions. * It's practically built in and maintained by the core team. The "other tools" out there are usually open-source. You have to go hunting for them. Gofmt ships right with Go. Then you have to figure out how to run them. With go, it's just a simple `gofmt <file>`. Boom. Done. No NPM dependencies, no Ruby gems to install. Just run it and move on.

I don't write Go full-time, but I've started adopting similar tools for my primary languages. My editor at my current job runs a similar tool for Node. I don't have to think about style any more. It's just taken care of for me. And all of my team have reached the same conclusion. Peace of mind and focus on what really matters is invaluable.


The reason I don't get it, I think is that I've never cared about stylistic decisions, ever. Why would anyone care, I have no idea.

I can configure my editor for either spaces or tabs. I run npm install when starting with the project anyway, and it will also fetch any devDependencies for me (which means a script such as npm run stylecheck can be made available). The project lead decides the rules. Or there can be no style rules, it doesn't matter - most of those are very superficial and have little to no effect on anything.


You obviously don't work with a team, or you work with a team that also doesn't have opinions about style. That's probably not a great thing. The tab-vs-spaces debate is very real, as well as things like whether hanging commas or trailing spaces are allowed, or whether you put one-if-blocks on one line or if you have to split them up into multiple lines, or whether the opening bracket goes on the same line or the next line, or whether... I could go on. All of these are things people tend to have preferences for, so when you work with multiple people, having a consistent style is a huge benefit to reading other people's code.

Imagine a simple CSS example: Some people like to do one line per element, while others like one line per style:

``` a { font-weight: bold; color: blue; text-decoration: none; } ```

vs

``` a { font-weight: bold; color: blue; text-decoration: none; } ```

That's a pretty trivial example, and for many people, both are acceptable. But when you get into large projects with hundreds or thousands of styles, or you have to code review that one change that was made off-screen on a really long line of CSS, one of those styles is going to be more conducive to code reviews and readability. Stylistic concerns are very real, and gofmt largely makes those go away.

Like kyrra said, you may not be opinionated on the matter, but other people are, and gofmt, et al, completely remove that concern from the table.


Like I said, other code reformatting tools exist, the team lead picks the tool and tool settings, and that should be the end of it.


Yes, this is true, but you're overlooking a few of the things I mentioned earlier.

Gofmt is extremely opinionated -- you can't customize it. Most other tools involve customizing to fit your own style conventions. Gofmt doesn't allow that.

Gofmt also ships with Go, so it's a built-in language tool, not a third-party tool. That's a huge benefit right out the gate, meaning that everyone's code will look the same. You don't really appreciate how nice this is until it's actually true

Finally, gofmt is dead simple to use. `gofmt <file>`. Boom. Done. Compare with, say, PHPCS, which is like: `phpcs --standards=<standards-file-path> --autofix=true --exclude=<exclude path>` and so on. Gofmt is just stupid simple to use.

Yes, you're right that other tools exist, but gofmt is the one that really makes it click for developers, and they think "Man, THIS is how code formatting should work." And then we take it over to the other languages, to C or Python or Javascript or PHP, and we introduce tools that are similar to gofmt. That's why gofmt in particular has "changed my life" (hyperbole, sure, but the point remains that I code completely differently now). Without gofmt, it's doubtful I'd ever adopt code formatting tools to the point that I have today.


I have already used one such tool for C++ in one of my teams (aastyle ? Something like that). It's only the firs step. Then you have to wonder/discuss: what actual style to use? There are many C/C++ styles out there (stroustrup, linux, gnu, OTBS, etc.), so you have to discuss about that. Now, when you have chosen one, you have to discuss about minor fixes: "ok, so OTBS is cool, but what about line lengths? Should we choose 80 cols? That's silly, why not 120?" etc. All in all, you end up taking a whole hour (even more) about this issue.

With go, that doesn't happen. You call gofmt and you're done. Another side effect: when you get external source code (open source, code written by another team, etc.), you know it will be formatted exactly the same way as yours.


I'm afraid that may be your fault. What style you pick doesn't matter much (consistency does though) so you should just pick one.

It really baffles me that anyone could spend so much time on something like that. I'm sure there are lots of more exciting (or more pressing) problems to solve on the projects you're working on...


erk, that totally screwed up my code example... sorry. :)


Almost all Go code you find on the web will be formatted the same. It removed some pain points when looking at someone else's code. It also complete removes the discussion about formatting during code reviews with team members. While you may not be opinionated on the matter, many people will learn a certain formatting style and stick with it. They then bring it to their new company and it can cause some minor conflict.

Gofmt helps remove that.


Any code reformatting tool helps remove that. Gofmt isn't the only code reformatting tool in existence.


It also opens the door to automatic code modification (like goimports or gorename), and because the code is always formatted the same, you avoid "noise" in your diffs related to trivial changes (spacing, etc.).


I'm surprised to see the author mention not being able to compile when there are unused imports. Editor plugins that call goimports have solved this problem for a long time now. For example, I use vim-go (https://github.com/fatih/vim-go), and if I remove the only line of code that references the "fmt" package, the "fmt" import line at the top of the file will automatically be removed.

Edit: Here is the reasoning on the FAQ behind code with unused imports not compiling: https://golang.org/doc/faq#unused_variables_and_imports


This is a typical instance of solving language issues with excessive tooling. I find it uneasy that we will be reproducing all the baggage Java required us in a different language. And even Java didn't have mandatory imports.


I personally wouldn't call it excessive. It's more like set it once and forget about it. And it's something that basically all professional Go developers do, along the same lines of how everyone runs gofmt.


Cool package, but it looks like it doesnt exist for TextMate


Meh.

1. How often do you insert into a slice? You know that's a O(n) operation, right?

2. nil is typed, I'll grant that it's not the most obvious part of Go's type system.

3. Meh.

4. This is type covariance. It's complicated and hard to get right. The Java Generics FAQ looks like this: http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.ht...

5. I like by-value loops. Everything else in Go is by-value.

6. Yeah, fuck everything we know about parsing, let's make whitespace significant, then we can declare slices like:

    x := []int{
      1
      -2
      +3
    }
    len(x) == 3
    y := []int{1 -2 +3}
    len(y) == 1
7. Meh


That's not type covariance. Variance only comes up when you have generics, which Go doesn't, and subtyping, which Go doesn't really have. It's the lack of a coercion/conversion between slice-of-T to slice-of-interface.

The correct language-level solution to #4 is to introduce generics and to allow generic type parameters to have interface bounds. That would solve the problem without having to introduce variance.


I don't see how it's not type covariance.

Go's slices are generic to the extent needed, and the subtyping of Go's interfaces fits the requirements for ordering of types well enough.


In Go, if T implements interface I, T is not a subtype of the interface type I. T can be converted to the interface type I, but it's not a subtype.


What's the difference though? Is this true of interfaces in Java for instance, or does it have to do with the specific implementation of go?


Isn't it possible to talk about variance as soon as you have a type constructor (function from types to types)? In this case [].

Edit: I don't know go, but if slices give write access, I think the automatic conversion the author wants would be unsound.


Yes, a coercion would be unsound (at least, unless the coercion copied the backing store of the slice, which would be very unintuitive). It shouldn't coerce. I'm describing generic type parameter bounds, which are not the same thing.

(For what it's worth, I'm not sure I would bother solving this problem if I were suddenly put in charge of Go's design, given Go's extreme aversion to type system features. I'm just describing what the solution to this problem typically is.)


I don't know if you need to make whitespace any more significant in Go than it already is to implement 6. As you may know, Go inserts semicolons at the end of a line in the lexing phase if a token of the right type is the last one on the line (https://golang.org/ref/spec#Semicolons):

> • an identifier

> • an integer, floating-point, imaginary, rune, or string literal

> • one of the keywords break, continue, fallthrough, or return

> • one of the operators and delimiters ++, --, ), ], or }

The things you can put as elements of a slice, values of a map, or values in a struct literal are Expressions nonterminal symbols (https://golang.org/ref/spec#Expression). As far as I can tell, the tokens that can end an Expression are a subset of those after which semicolons can be automatically inserted.


This sums up my opinion on this pretty well.

On 1 I'll just say it'd be nice if append (and other varargs functions) could take multiple of individually listed and slice-expanded args at the same time. As in: `append(a[:2], 3, a[2:]...)`. I already expect append to be O(n), and this would be some very nice sugar.


Yes, the syntax here just looks so unnecessarily ugly. For all the talk of Pythonistas moving to go, I can't see myself switching over to a language with so many ugly, hard-to-remember warts.


"I still wonder why we can omit commas on import/var/const blocks and just can’t on lists and maps. Anyway, Rob Pike knows better than me! Viva la Readability!"

import/var/const blocks rely on semicolon insertion rules, list and map literals have a list of Elements separated by ',' [0]. I guess on the surface level I see how this is sort of inconsistent. It would be interesting to think about applying semicolon insertion to comma insertion.

0 - https://golang.org/ref/spec#Composite_literals


I worked on a project with go, and initially i liked it. Because the new way of thinking, and the fast compiler. But both the error checking, missing of generics, and missing of real functional programming paradigmas, required me to write something very simple in a complex way.

I concluded that a simple webapp, is better written in python, and go was not the way togo for my project. In python you can quickly generate json from objects (yeah you can in go but only with ugly string comments). All in all, go has its place, but when you start a new startup, watch out with go initially.


I would add "non-pointer method receivers allow you to modify the receiver object, but silently drop those modifications when the method exits (because they happen on a copy)".

This is similar to the variable shadowing example in the article, in the behavior is the same in C and so kind of makes sense for that reason. However in both cases, Go's syntax makes it very difficult to spot the error (already-declared variables allowed to the left of :=, method calls implicitly taking a pointer to the receiver), where in C it would be more obvious.


I've been bitten by #5 quite bad. Ranged over N elements in my slice, took the addresses of the values, copied to new slice. Ended up with a slice with N identical values. !?!?!? Took me about 30 mins of staring, printfing and whatnot before I understood what was going on (the loop variable containing the copies is mutable so you end up with pointers to it throughout the whole new slice. And at the end the value of it is the last element in the range. So I saw N*the last element when printing the result).


Yes, Go sucks. But currently, at least for me, I think it sucks a lot less than alternatives. No language is perfect, neither is Go.

The most annoying thing is compiler instructions with special formatted comments. And Rob Pike calls that "elegant design".


I think author of this article is strangely biased towards dynamic languages. Go is not about shortest programs in the worlds, yes sometimes code is longer than in python. But in Go I can manage code execution and predict memory easier better than in Python or other dynamic languages. Many things in Go were done on purpose. Learn to use Go and it's awesome tools, like gofmt, goimports and others.


Many of the issues here--for example, the set of built-in slice primitives being inadequate--have ready solutions in other statically typed languages (in this case, generics). These are not problems fundamental to static typing.

(For the record, I disagree with some of the criticisms here, for example the slice-of-interfaces type coercion criticism.)


I love Python, up until I remember that I can't make any guarantees of program correctness until I have 100% coverage tests on every line of code because the language is so flexible that it can't tell if a random identifier refers to a variable name that might exist at runtime (but is not determinable to exist at compile time), and therefore most of my variable-name typos that would be compile-time errors in another language are runtime errors in Python.

So great for prototyping, not so great for large projects and maintenance (as I've just put the floor of my unit test cost around a factor-of-2 more code).


Indeed, with python i can program fastest, but whenever something gets big you had wished i programmed it in golang. However, its still a valid choice to choose python initially. It depends


I agree strongly with this, it seems like the author has some expectations from his known language pool. Reason #3 about lexical scoping is the big one to me, as you can (and might want to) arbitrarily block variable scopes in many languages.

The other components seem to be mostly that Go is still in flux as a language so things that are silly today may be fixed tomorrow, but that's kind of a built in expectation with the ability to transform source files to current. This feels like a lot of the early Java issues that were resolved some time later.


All these languages, Go, C++11/14, trying to be as concise as Python. Just look at those slice examples. At least C++ is playing catch-up. With Go there is no reason it had to be worse than Python right out the gate.

https://gist.github.com/ericfrederich/c8d8513b9790106d87b7


Go is inspired by the C++ subset that Google choose to use (Just look for the Google's C++ style guide to look for things that Google banned). So in many ways, it's chosen to work as C++ in lots of corner cases.

In fact, that enabled automatic tool to convert Go compiler written in Google's C++ to Go.


> In fact, that enabled automatic tool to convert Go compiler written in Google's C++ to Go.

Hmm, I thought go was written in C at first place and not C++. It seems to me that Go creators were never big fan of C++ so I don't think it takes any inspiration from C++ .

All I see in some kind of C + garbage collection + interfaces and some concurrency percs. I fail to see how it was influenced by C++ in any ways.


That's correct, the Go toolchain was originally written in C, and translated from C to Go:

https://golang.org/doc/go1.5#c

And of course Go's designers include some of the designers of C (well, its predecessor B) and Limbo, and Go seems influenced more by that tradition than by C++.


I think the author is a victim of the fallacy that Go looks simple, even though it's a very low-level language (think C without the quirks). Most of the — very valid — points they make are a consequence of Go not hiding complexity from the programer.


I don't understand 3. How else is it supposed to work?


I wish the author would have taken a little more time to polish this and be a little more respectful.


While Go could probably be better at not letting you shoot yourself in the foot, 1-4 seem to be code smells - you should fix your code and not the language. 5-6 seem to be personal preference, I have no problem with either as they are and they certainly don't trip me up. #7 as the author says himself:

> noone actually cares, it’s just fine


That's just part of the deal. Sometimes you just have to write few more lines of code. Your fingers won't die from it.


"Alright, the title is quite bold, I admit it. I’ll tell you more: I love bold titles, they are all about attention."

In other words, click-bait. Oh, you think I use the term lightly? For an article that boils down to "things about Go that aren't particularly wrong, but I don't like", the author concludes with "I hate the language: it’s absolute crap". Really? Because when you throw the kitchen sink into your imports "just in case", it won't compile? (I pick on this particular example because dragging along imports you're not even using just strikes me as sloppy, hence coloring my view of the author.)

It's one thing to state, "I would have done this differently for these reasons", but to call the whole bundle "absolute crap" just reeks of some n00b who learned a single dynamic language, switched to Go, and had a hard time figuring out why his crap code wouldn't compile.


Point 4 makes sense though , it would really help with polymorphism in Go ,without generics. This is the kind of subtle behavior that is upsetting at times and I really think this is where the language could improve, definitely.


For every "quirk" mentioned in this article there are very good reasons for it, which the author lacks the imagination or patience to try and reason through. Point 4 is perhaps the best example.

So given an interface Foo and a struct FooImpl which implements Foo, why can't we pass a slice of []FooImpls to []Foo? It's because the creators of Go are lazy and arrogant and incompetent and don't care right?

No. Consider the situation where a []FooImpl is passed to a function F(f []Foo). This function decides to change one of the elements of the slice to a different implementation, say FooImpl2, eg. f[0] = FooImpl2{}. Now the original slice of []FooImpl's has a FooImpl2 in it. Oops, we broke type safety. Now consider what it would take to make this work - you'd have to somehow guarantee that functions which take []Foo don't mutate the slice. Is that possible? Is it possible to do quickly, and still have a language that compiles very quickly? Or we could introduce "const" and all those things from C++ but that's a whole new level of complexity to the language. And what about the performance penalties? A struct is a different size than an interface, so what code is emitted during compilation, something that can handle iterating over []your_interface and []your_struct?

So next time you think "Gosh, this bit of Go really sucks" take a moment and think about why it was designed that way. Because the fact is, it was almost definitely designed that way, as opposed to just overlooked or neglected. You may not agree with the choice that was made, but with the people who work on Go, I can guarantee that there was a conscious choice that was agonized over and discussed endlessly, just not with you in the room.


I wouldn't put it quite so strongly, but yes; go makes design decisions that are addressing non-obvious problems that the designers encountered over several years of software development in other languages.

Consider the block against unused imports. Now consider an application made of hundreds of .go files, that relies on hundreds of libraries, each of which could itself be made of hundreds of .go files---all of which changes frequently enough that a total recompile may be necessary often (which might sound like a code-base you can imagine Mr. Pike would be familiar with). It's actually a non-trivial amount of work to churn through and ignore all the unneeded imports.

"But that's sacrificing developer prototyping velocity to solve a problem that most developers never see; at most, it should be a feature toggled by a compiler flag", one might argue. Well, sure... Now consider how many C / C++ libraries cannot be compiled with -Wall -Werror because the original developer had the option of building the library without those flags enabled and not enough people want to learn all the subtle details of the languages they use, they just want "the code to compile and be done with it." Now consider what that does to the problem of trying to build larger projects from these tiny pieces that were never actually sanitized for unneeded imports... Which will inevitably happen to code that works well enough, etc.

It's a line drawn in the sand, but it's a line drawn in the sand from hard-won experience with how little projects grow into big projects.

Besides, the language is so small that it's not very hard to write wrappers for IDEs that can quickly identify and kill unused imports automatically (probably also add them when needed, like the most common "oh GOD I need to put 'fmt' back in just because I'm pen-testing the outputs in this code I'm debugging").


> I wouldn't put it quite so strongly, but yes; go makes design decisions that are addressing non-obvious problems that the designers encountered over several years of software development in other languages.

s/years/decades/

(Which strengthens your point...)


Sorry to bring up this yet-another-generics-ranty-thing, but actually point 4 is a typical issue that can be nicely solved with generics, by defining a function that works on every type that implements Foo. In this way you can directly pass a slice of Foo to the function and achieve type safety at the same time. As stated by the author, there's a demand for manually constructing an interface-slice in Go, and this shows a good example why generics can be useful.


That's a different criticism. That's the "Go Needs Generics" issue.

I don't disagree with it, but I also agree that there hasn't been any good solutions proposed for generics that meet the requirements that are at the core of Go: namely, performance of the compiled program, performance of the compiler, and performance of the developer.


Just monomorphize the code. It really isn't a problem for compilation performance. (I predict 10%-20% overhead based on measurements in other languages, though note that I think the entire concept of measuring the compilation speed overhead of generics is pretty much hopelessly flawed to begin with.)


The reason why I think generics are relevant to point 4 is that generics can be thought as "first-class interfaces" which seem to be wanted by the author. Yes, the author suggests a method that is not acceptable to my taste (implicitly converting []struct to []interface), but if Go had generics he might have not complained about the lack of "first-class interfaces", because generics are in some ways "interfaces on steroids".

As for the core features of Go you mentioned, sorry again for my cynical wording, but I think they are a bit dishonest.

They stress on the performance of the program, yet being slower than C/C++ due to the usage of GC and dynamic dispatch all the time. It was even regressed in Go 1.5[1], because the new GC prefers latency to performance. So, they traded performance for something considered more important.

How about the performance of the compiler? Well, the speed of the Go 1.5 compiler is ~10% worse than the previous one, because they rewrote the compiler in Go (it was previously in C, for those who didn't know yet). Why did they do so? Because it has advantages: reduces the barrier to contribute to the compiler, improves the code quality, etc.. Yet they traded performance for something they consider to be important.

And the performance of the developer... While Go is not very terrible at developer experiences, it isn't very good either. Embracing simplicity led to lack of expressiveness, a typical example being generics. While this isn't entirely a bad thing, it is, again, a trade-off.

So they make trade-offs all the time. Then why are generics not considered to be good enough to make a trade-off? Because they think so. It is their opinion, sure, but claiming generics are impossible because performance isn't very convincing to me.

[1] http://yokohummer7.github.io/blog/2015/09/01/go-1.5-regressi...


Sure, there's lots of trade-offs - you can't build anything significant without trade-offs. Also, you can still say performance is a value and trade it away for other things, eg GC. It's a bit silly to say that if something isn't as fast as C++/C right away then it doesn't take performance seriously. I don't think anyone's being dishonest in making such trade-offs - if you have multiple competing values then you need them.

The big difference with stuff like GC and re-writing stuff in go slowing the compiler time is that they are not inherent to the language. That stuff can and will improve. With generics if done incorrectly, it's more or less permanent. So I appreciate the conservatism there, and don't think it's fair (or accurate) to say that it's dishonest.


> The big difference with stuff like GC and re-writing stuff in go slowing the compiler time is that they are not inherent to the language.

Actually, I think GC is inherent to the language, and that's why I listed it as one of the trade-offs that Go made (not the Go implementation made). Almost every language construct depends on GC, e.g. append(), make(), and more. Even the following innocent-looking code:

  func f() *int {
    a := 1
    return &a
  }
is GC-dependent, because it would result in a dangling pointer in non-GC languages. But the code is perfectly fine in Go because GC is inherent to the language. It is not possible to make GC optional in Go, so the performance penalty will stay there forever, only alleviated as the implementation matures (or sometimes regressed, as Go 1.5 shows), unless you use only unsafe.Pointer all the time.


You can already define functions that work on every type that implements Foo, no? I've never coded in go, but I thought that was the whole point of interfaces.

And I don't see how adding generics would solve the problem of being able to add a FooImpl2 to a slice of FooImpls. Java has generics and they don't allow covariant generic collections for this same reason.


We replaced the baity title with something more neutral, in keeping with the HN guidelines:

https://news.ycombinator.com/newsguidelines.html


Dang, I think you're too aggressive with article renames. The author claims that Go is poorly designed. And that's the title of the article. Yes, it's a provocative opinion (and the article text is loud), but the title itself was not outrageous hyperbole. I disagree with other commenters here: just because a controversial opinion will (naturally) gather more clicks, doesn't mean it's linkbait.

The articles here on HN also surface on reddit and other forums. I frequently click links on HN just to discover I'd read them already.

The title often contains a clue as to what I'll find in the article. The author presumably gave it some thought. When HN editorializes titles, I lose that context before I decide to click.


You may not think this was linkbait, but I guarantee you that if this article were on HN's front page with that title, the thread would become about the title. All you need to do is look at the comments that were posted before we changed it.

Your objection is really to HN's title policy. In my view that policy is a critical aspect of this site—indeed the longer I work on HN the more critical it seems. So I have to disappoint you here; it's not going to change. Huge numbers of HN readers would be up in arms if it did, even though no reader agrees with every particular edit moderators do.


Well, it looks like my only recourse is to mount an aggressive campaign to be the new HN moderator, take you down, restore the original titles, and eventually get run out due to various lurid scandals involving clickbots.

Thanks anyway for the reply :-)


On a related note, unfortunately the system encourages attention grabbing headlines to make it to the first page. I think it's great that you then replace them, but without a penalty I don't think this will stop.

Would it be hard to deduct say min(5, upvotes)


That's indeed a weakness of the system, but it's complicated by the articles sometimes being good despite the clickbait. This is especially so with the publications large enough to employ specialist headline writers who put bait on everything.


I guess I'm a "a pussy, 9-year old boy or afraid of little bit “strong” writing.", because as soon as I saw that sentence I lost all interest in finishing the post (though it was pretty linkbaitey in the bit I did read so I had marginal interest in the first place).

Shit like that is why we have gender problems in the industry.


FUD


> it’s not foreach from C++.

Ah the penny drops. We have a whiny C++ programmer on our hands.




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

Search: