Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Any critique of Go seems to be met with angry pitchforks in this place.

As you say, the Go developers seem to have developed a kind of bunker mentality where they interpret legitimate criticisms of the language design as personal attacks, and respond by wearing Go's shortcomings as a badge of honour.

It's not, I think, entirely healthy.



I think everyone who uses Go for anything is pretty clear about why they like it, and one of the major reasons is simplicity. So why are people then surprised when criticisms over lack of features fall on deaf ears? And to be honest, this whole argument about pitchforks seems like a straw man. If anything, it's currently fashionable to dump on Go at every opportunity. Hell, it's fashionable to dump on everything around here at every opportunity. It's a way to put yourself above something with the least amount of effort.


My (inexpert) opinion is that the kind of computer language researcher or practitioner interested in exploring how far you can go with type systems is dissatisfied with Go.

But there are many types of static analysis which accomplish similar (or more dramatic) goals than what can be down with type analysis, and the simplicity of a language makes those kinds of analyses more reachable. Examples: gofmt and gofix.

I think it's amazing what kinds of magic can be encapsulated with the type system, but my experience doing software is that complex types often end up being a hairball which is very change-resistant. Go's emphasis on lifecycle support for large programs may point us to new kinds of tools and methods. Whether those tools and methods end up being able to be encompassed by type theory is an open question, but it looks to me like Go is aimed in a great direction to raise the questions.


What part of types is change resistant?


To me, a lot of the more interesting areas of type research basically involve embedding logic into types. So types for sequencing of operations, types that constrain the data in them, etc. Those are awesome in that they enable static analysis to detect more sorts of logic errors in a digestible way, but my impression is that they end up encoding constraints in the type system that are then harder to change as the demands on the system evolve.


Agreed. I foresee a great future for Rust, and also a great future for HN posts in the form: "I know I'll get downvoted for this, but Rust is a terrible language because it lacks the following features..."


Not likely.

Rust, while lacking in various aspects as any v.1 release would, already has means of extension. A library can mostly replace a lacking feature while playing nice with the rest of the ecosystem by use of the type system and the macro system.

Roughly the same kind of extensibility is possible in Python (some later language features first appeared as approximations in third-party libraries) and even Java (where a kind of code post-processing is possible via annotations).

Unfortunately, it's unreasonably hard in Go. All you got so far is un-hygienic macros, slightly better than C #defines.

This is sad because a number of other ideas in Go are right and well-implemented.


While Rust in it's own little world is promising, it's issue is that it tries to solve problems C++ programmers were facing 10 years ago, but 'good practices' and C++ devs limiting themselves to a subset of features "solved" most of these problems for most C++ developers. And that's Rust's main target audience.

Go's target audience are 2 groups: people previously writing stuff in C/C++ because they didn't have much choice unless they wanted to bring a shitton of dependencies, but in reality didn't want the complexity this brings, and people coming from scripting languages like Python and Ruby. And for these things, go is pretty damn good. It has it's downsides - like any language, but it works. It's strongest point however is it's standard library with 'modern features' and transparency. For me it was the first language where diving into the source code of libraries - even the stdlib - was so effortless and has become a completely normal thing to do. In C/C++, the most you do is dive into the headers, and in the latter case, this is not always a good idea if you want to keep your sanity (hello Boost).

Rust could gain traction the moment it finds a market, and few high profile projects written in Rust that are widely used. But right now, I'm not aware of any.


> For me it was the first language where diving into the source code of libraries - even the stdlib - was so effortless and has become a completely normal thing to do.

A great point. It is amazing how many little things Go and its ecosystem provides, that other languages have missed for years: go fmt. linked documentation. play.golang.org, and more. The language may look like it comes from the 80s, but the tooling is avant-garde.


Servo is one, but I believe that Rust needs another high profile project targeted to the embedded or low level system world to gain mind share.

It has to demonstrate that

Rust > ( C/C++ + Static/dynamic analysis )

in terms of safety and productivity.


In the medium to long run, I suspect criticism of Rust will be more along the lines of criticism of C++ – too ambitious, too many features, too hard to do simple things, too easy to do unsafe low level things – rather than along the lines of "lacks these features".


No, I've talked to Go people and they really do respond that way. Maybe my opinion is just one data point, but this entire thread seems to agree...


> I think everyone who uses Go for anything is pretty clear about why they like it, and one of the major reasons is simplicity.

Nope. I like Go because:

* Interfaces

* A sane, fast, build system

* Concise syntax, mostly

* Garbage collection

* Excellent concurrency support

* A (for the most part) well-designed standard library

* Optional semicolons

* Compile-time type-checking

* Static binaries

I like Go because of the features it has, not because of the features it doesn't have.

On balance, although Go sucks, it sucks less than any other language for the sorts of problems I use it for.

> And to be honest, this whole argument about pitchforks seems like a straw man

If you don't notice that criticism of Go is immediately and vigorously argued against... Well, you can't be following the comments very closely.


> If you don't notice that criticism of Go is immediately and vigorously argued against... Well, you can't be following the comments very closely.

Huh? Have you ever argued against anything on the internet and not been countered immediately? Do you think if I'd publish criticism like this about, say, emacs, haskell, firefox, twitter or puppies, I wouldn't get comments immediately telling me that I am wrong and fundamentally misunderstanding what an editor, programming language, browser, social networking platform or adorable animal photo is all about?


I'm confused. Your tone suggests you're disagreeing with me, but the words mean the opposite.

Do you think I said that "only criticism of Go is argued against"? Because I didn't.


I think some of that stems from the fact that the arguments against Go are often over emphasised matters of personal preference, or just so frequently raised that it becomes tiresome to read.

I love Go. I know it's an imperfect language and because of that I do often hate specific Go idioms. So I definitely don't have a "bunker mentality" when it comes to Go; nor any other language. But in terms of "getting stuff done" Go has generally served me - personally - better than any other language. I just get a little sick of hearing about how Go is a "bad language" when what people actually mean is "it's not productive for them personally."

I can program in over a dozen languages, so I do have extensive experience outside of Go. And as someone who is language agnostic it never ceases to amuse and irritate me just how zealous people get when trying to prove personal preference as scientific fact.


In the end there is no one true best language out there. Go is great for a subset of problems and making it better at other things is a balancing act.

The most popular languages are generally accidents of history. Unix gave us C, browsers gave us JavaScript, and Databases gave us SQL, etc.


> In the end there is no one true best language out there. Go is great for a subset of problems and making it better at other things is a balancing act.

This is the problem though - I think many people expect there to be a "one language to rule them all". Personally I like having lots of different languages that excel at some problems even if that means they fall short at other problems. But I think some people either want to specialise in a specific language, or spend so much time looking for perfection that they miss subtle beauties amongst a forest of flaws.


I think the language that wanted "to rule them all" was C++, and we all know how it ended. C++ can be procedural, oop, functional, you have metaprogramming, generics, everything. Everything. C++ is everything. Every pattern, every design philosophy can be implemented in C++. How much time does it take to compile? How many developers do know every C++ feature and pattern?


It's OK to see a language that has certain strengths but also some weaknesses due to the nature of the strengths.

It's sad to see a language that has certain strengths but also some painful weaknesses for no good reason except that it's v.1. So far the best I've heard is something like "We understand there's a problem for certain users, and we don't rule out adding generic types some time later, but it's hard and our priorities are different". It is indeed not easy to do well (though Java has somehow managed to find a satisfactory solution).

Well, I'll wait a few more years.


I don't really see how Go is, or should be, that limited as a factor of design (in contrast with something like Erlang). It's just that everyone in the ecosystem is focused on on the same things and when people with other use cases, that could benefit from the properties of the language, try to make themselves known it's all "works for me".

Edit: Thanks for proving my point everyone.

Edit2: To be slightly less snarky, despite the article I don't see any reason why Go couldn't be a fit for e.g. an oscilloscope which today is running a complete, often multi-core, linux system with 100k+ lines of code (with help from an ADC and FPGA etc. of course). If it wasn't for the fact that few people would undertake such an effort when that would be akin to swimming upstream against the ecosystem.


By this measure, isn't C worse?


Even with C, you can do some type safeish data structures using macros.


I think that your view, while true, misses the point (or doesn't make it explicit enough): tools are what really matter, languages are simply less important and ultimately interchangeable.


I'm not understanding SQL as an accident of history, can you explain?


The ideas behind SQL started with Codd's relational algebra, which has some very large differences from SQL. See https://en.wikipedia.org/wiki/Relational_algebra for more. Query languages were developed based on that.

However as https://en.wikipedia.org/wiki/SQL documents, a team at IBM implemented something called System R (R for Relational) with a query language called SEQUEL that was renamed to SQL for trademark reasons. Then Relational Software (now Oracle) implemented a database that could run on non-IBM software, and made their query language mostly compatible with IBM's so people could port to their database. And everyone who came after has made their implementations compatible for the same market reason that Oracle originally did.

Even today there are people who want to return to some of the ideas that Codd had which SQL does not implement. But there is such momentum around SQL that it is unlikely to ever happen.


SQL was the query language of the first major commercially successful RDBMSs (from IBM, who developed the language, and Oracle) -- which weren't the first two RDBMSs, and were arguably successful for reasons largely unrelated to the query language chosen -- and became the de facto standard because of that. So, yeah, I can see the "historical accident" there.


The problem is that there is no empirical evidence to support any of the claims. Even looking at generics, the limited studies I've see show that generics make people a little more productive when using a generic library, but far less productive when trying to write a generic library.

In short, these are entirely anecdotal and subjective points of view, so after the 1000th person says, "you are stupid, generics are amazing because .... my anecdotes" well eventually you tune it out.

Last, there are a ton of languages out there that have generics, richer type systems, etc. Go is trying something different, and given the lack of real empirical data, lots of experimentation is the best thing. Let Go do it's thing, and let the other language do theirs, why demand that all languages need to make the same trade-off on these topics?


I agree with a lot of this, but where things get muddy with empirical evidence, is that it implies a certain "default". In this case that can either be something like "generics are useful" or something like "not having generics is useful". I don't think either hypothesis is supported by much of the sort of empirical evidence you're looking for. Basically, I share your sense that this is all anecdotal and subjective, but in reverse: after the 1000th person says, "generics are too complex and not having them is better because … my anecdotes", I eventually tune it out.

Experimentation is definitely the right way to go (har!), but that doesn't imply that people should be mum about the results of their personal experiments! Obviously of these are actual scientific experiments, but when people (like the author of the OP) say "I've used Go and here is what I think", they are in essence reporting the findings of an "experiment" with the language.

I don't think anybody is demanding that Go make the same trade-off as other languages, they're just documenting their thoughts on the affect of the various trade-offs.


At least in the Go community, it doesn't seem like people are ever saying generics as a concept are bad, but that there are very real tradeoffs involved in adding them to Go, and they're the kinds of tradeoffs the language designers and maintainers have decided they don't want to make.

I really like how simple Go is, but I also think generics are super useful, and that if Go could implement them in a Go-y way, I would be incredibly excited, and probably use it even more than I currently do. But I'd rather see the language focused on doing what it currently does well, and making sure it keeps doing things well, over seeing it try to throw in a poorly designed generics system like Java.


I'm not sure that I've heard the argument that generics as a concept are bad, per se, but I believe I've heard the argument that they aren't very useful. I (again, non-empirically) disagree with that, but I don't know if it is a common belief in the Go community, and I don't disagree with you at all that generics wouldn't fit very well into Go's philosophy and design and probably don't belong in the language. I think that's a shame, but I still like Go a lot.

On the other hand, I do think the author is spot on about nils and multiple return types for error handling. Returning a container type that can represent either a successful or error value is simple (arguably, simpler!) and less error prone. It is something I would love to see in the language. (Somewhat ironically, if it were possible to make generic container types, this wouldn't need to be done in the language itself, but could be a library.)


In practice I'm not sure I see where a PotentiallyErorredResponse object that wraps a response and error together is different from returning a response and an error.

The advantage of Optional over returning a value or null is partially that it makes the programmer more aware of the fact that they're dealing with something that might be null. The same thing would be true of an Errorable wrapper.

I'd argue that Go makes it more explicit by having compile-time errors when you don't deal with potential error responses.


The solutions are definitely similar, but the advantage is eliminating the potential for propagation of nil values.

> I'd argue that Go makes it more explicit by having compile-time errors when you don't deal with potential error responses.

Showing my ignorance: how does this work? Does the Go compiler check that you check the second return value from a method before doing something with the first return value? I thought you could just ignore the error return and pass along the returned value (which might be nil) as you please. If there's compiler support for avoiding nil propagation, that's great!

In the case of a container type like I'm suggesting, you get the compile-time checking from the type system – an error return value is not of the same type as the underlying type, so you have to explicitly get the underlying value out. Even if you accidentally propagate the container, you probably have more information than if you propagate a naked nil, because the error information is part of the container that was propagated, rather than a separate value that might be lost.


>If there's compiler support for avoiding nil propagation, that's great!

Yes, would be great, but there is no such compiler support. You can ignore errors completely. Maybe you could argue that compiler errors for unused variables are a very weak sort of compiler break on ignoring errors.


One thing I liked about the Option type in java is that it not only offered better clarity, it also came with a few cool methods, like ifPresent()


There is no rigor to this blog post. He didn't have two teams build the same project with and without generics or anything like that.

The title is "why go is not good" which is drawing a conclusion based on an anecdote. It's the equivalent of walking outside in December, stating that it's cold, then drawing the conclusion that the globe isn't warming.


As far as I can tell, conclusions are based on facts about the language as well as other languages.

Maps, slices and channels are generics. I'd like to see code in Go written without maps, slices or generics.

Go will never implement immutable data structures. Its impossible to write an immutable data structure library without generics.

Go will never implement Futures / Tasks / Observables. Its impossible to write Futures / Tasks without generics.

Here are some statistics: In Go its impossible to write 90% of the functions listed here: https://lodash.com/docs - because they take higher order functions, which use generics. The fact that JavaScript has loops that can be used instead, yet this is still the most popular JavaScript library cannot be reconciled otherwise than by acknowledging that generics are generally useful.

Its a damn shame that the language has such flaws: the standard library and tooling are superb. And just generics can single-handedly get rid of 80% of the problems of Go (errors can be modelled with a generic Result<T> which forces you to check for error and allows chaining, generics would enable immutable data structures which are much safer for concurrent programming...)


You absolutely can implement those functions in Go, if you're willing to sacrifice both type safety and performance. e.g., https://godoc.org/github.com/BurntSushi/ty/fun

Javascript is unityped, and you can certainly pretend Go is unityped too, by using `interface{}` everywhere.

If you linked to a similar set of functions defined in, say, C++, then I'd agree that Go has no real way to achieve something similar.

> (errors can be modelled with a generic Result<T> which forces you to check for error and allows chaining, generics would enable immutable data structures which are much safer for concurrent programming...)

Generics isn't sufficient for that though. You also need sum types, which Go doesn't have.

Lack of generics isn't objectively a flaw. It's a trade off. You may disagree with that trade off though!


You don't need sum types, Result / Optional etc can be implemented in a language without them.

I can implement those in TypeScript too. Its a fast compiler, has a type system, and it supports generics.


Sum types are how Result/Option/Maybe/Either are typically implemented. The type system guarantees that inhabitants of the type contain exactly one success value xor one error value, and that accessing the value requires handling both cases, which is checked at compile time.


Sure, but you don't necessarily need sum types for Either. Its more tedious, but totally doable without them, e.g. just with higher order functions:

  let left  = a => (l, r) => l(a)
  let right = b => (l, r) => r(b)

its now impossible to consume result without passing functions to handle both the left and the right value.

  let mapRight = f => val => val(identity, right => right(f(val)))


Indeed, but now you're just being academic. Such an approach is quite bothersome. I maintain my initial criticism of your suggestion.

It's easy to be an armchair designer of programming languages. It's quite a bit more difficult to be in the driver's seat, because you have to answer the hard questions; you can't just throw feature sets against an HN comment wall and see what sticks.


You're coming from the same bias as the author of the article: Haskell and Rust are what a language should look like; Go doesn't look like that, therefore Go is bad.

Take one of your points:

> Go will never implement immutable data structures. Its impossible to write an immutable data structure library without generics.

Fine; I won't argue whether your statement is correct. But so what? That only turns into something anyone should care about if you also assume that "immutable data structures are The Right Way".

> Here are some statistics: In Go its impossible to write 90% of the functions listed here: https://lodash.com/docs - because they take higher order functions, which use generics.

This proves that Go is not built to use higher order functions. It does not prove that Go is flawed (unless you also assume that FP is The Right Way).

TL;DR: Go isn't trying to be a functional programming language. Some people think FP is the only way to go, and therefore think Go is bad. Those people need to realize that FP is not the only way to program, and to stop trying to force Go into their FP world.


I agree with most of your "so what?" point, but I think the lodash / FP thing is interesting because javascript is also not trying to be a functional programming language, and nor are the many other languages that have popular higher-order-function libraries (eg. Java, C#, Ruby, Python). There's a long trend of pulling in the ideas from functional languages that have proven to be generally useful, like lodash / Java 8 Streams style collections functions, while ignoring the stuff that might be useful but is harder to implement or work with, like purity, immutability, laziness, or sophisticated type inference schemes. Go is definitely anachronistic in not following this particular trend. Of course, that's the prerogative of its designers! Note that Go doesn't need generics to have 100% of what lodash has, it would just have to be implemented in the compiler, similar to slice, range, map, etc.

I have no problem accepting that FP is not the only way to program, but it sure would be convenient for me personally to have nicer ways to work with collections in Go.


As was mentioned before, you can absolutely do the same thing in Go that you see in lodash by giving up type-safety which javascript doesn't have anyway!.


I actually saw that post right after I wrote the one you just responded to. While it's definitely true, what makes it more awkward in Go is the necessity to translate back and forth between the "traditionally typed" and "unityped" dialects of the language. I'm not (only) referring to performance here, but ergonomics. Because javascript is unityped everywhere, a unityped implementation of those sorts of functions is natural, but because Go is mostly type-based, it is less natural and more boilerplate-y to pass and return `interface{}` everywhere. Nonetheless, it's a good point.


Or you don't give up type safety. TypeScript compiles really fast and has generics.


I'm not familiar with TypeScript, but since it compiles to Javascript, I imagine it's using type erasure. Indeed, such an implementation of generics is easy to compile quickly.

If one adopted such a scheme in Go (which would also be fast to compile), you'd end up sacrificing a great deal of runtime performance, because everything generic would be forced behind a box. Such a thing is no big deal in a language like Javascript, but for a language like Go that aims to be fastish, it's probably a non-starter.

Finding a compiler that monomorphizes generics and is also as fast as Go is probably a much harder challenge. I've had some folks claim that one of D's compilers is pretty fast, but I haven't seen any good benchmarks to support that claim. Many of the other monomorphizing implementations I've tried (Ocaml, ML, Haskell, Rust) are all pretty slow. At least, much slower than Go's compiler in my own experience.


Once you get data races in Go, you realize that immutable data structures are indeed the right way. Too bad though. That non-threadsafe, racy map is your only generic data structure.

I think that critics are generally cutting way too much slack to Go. Its a horrible language - with a decent library and excellent tooling and documentation, but still quite horrible.


> Once you get data races in Go, you realize that immutable data structures are indeed the right way.

False. There are more things in multi-threaded programming than are dreamt of in your philosophy. For some of them, immutability is very much the wrong way. It means some threads will be playing with stale data for some time.

I'm not saying that I'd want to write that kind of a program in Go, mind you. But your over-generalization is blatantly false.

And, in fact, I suspect that most people writing in Go aren't writing the kind of program where you could get data races at all. Multithreaded programming in Go (if I understand correctly) is mostly a matter of handling multiple independent data streams, not threads that need to access the same data objects.

I suspect that your over-generalization in the second paragraph is equally false, but I have less experience to validate that opinion.


Even if we assumed that (but seriously though, I invite you to at least provide one valid, complete example) your argument is valid, what you're saying is that Go is good for that subset of cases ("some of them") where immutability is the wrong way.

Does not invalidate the fact that it has zilch to offer for the cases where its the right way.


First: I didn't say that Go was good for that subset of cases. In fact, I said that I wouldn't want to write that kind of program in Go. I said that Go wasn't the wrong answer for the reason you stated, namely immutable data.

You want an example? Here's a video router for a TV station, which has multiple sources of user input (human-pushable control panels on two different data buses, plus serial data coming from multiple automation systems). You need to keep those control panels and automation systems updated with what's connected to what, even if they weren't the source of the command that changed it. And you need to keep the actual hardware switch up to date, too. And commands to the switch can fail, which you need to report back to whoever made the command. (One way the command can fail is if someone else locked an output to display a particular input.)

Faced with that problem, we implemented a single "state of the switch" data object that mutated as commands came in. But you could think about trying to implement it with immutable data. That would mean creating a new copy of the state of the switch for each (successful) command that was processed. That would mean copying a fairly large chunk of data many times a second, which would have been a challenge for the processor we had. That would also almost certainly mean a garbage-collected language which, when you're trying to respond within one TV frame (1/60th of a second), is a really bad idea. More to the point for our discussion, it would also mean that threads, which in our design only had to respond to one source of control, would now also have to handle state-of-the-switch updates pushed to them from other threads (or from some master). That seems like significant additional complexity to me. (Yes, I know that data races have their own complexity, but for our design, it was very clear how to prevent that. And if you're going to say that we could have had a separate thread receive the updates to update the control panels, now we've got a race as to who owns the hardware control bus to the panels.)


Now thats a good example!

First, a little correction: you don't have to make a copy of the entire state. This video explains the trick on how to get immutable data structures that share most of their data with their previous version https://youtu.be/SiFwRtCnxv4?t=8m39s - list are straightforward, and vectors and maps are based on the same HAMT tree-like structure.

Of course, a system with real time constrains and hardware limitations will have different optimal solutions. And yes, reference counting is the bare minimum you'd probably like for these structures (GC is even better)

However, we're talking about Go here - a language designed for writing servers that has a GC.

The solution in Haskell is actually quite nice: MVars [1] plus immutable data structure. An MVar contains the current state, represented by one such structure. takeMVar "removes" the variable - a thing which can be done atomically by the updating thread when the data becomes stale. After that, subsequent attempts to readMVar from other threads would block until there is a new updated value, to ensure everything is in sync. Finally, the updating thread does a putMVar, and all readers get the new value and continue executing.

The best part is they don't have to worry that the updating thread might start another update in parallel while they read: the data structures are immutable so the value being read is guaranteed to remain immutable. Even if the updating thread continues "modifying" the new structure in the background, it doesn't have an effect on the other consumer's version.

But yeah, all this is pointless if you have realtime constraints and therefore need super-tight control over execution time. It might be doable in a fast reference counted language, but it will also be much harder to reason about the time it will take to release the memory for the segments that aren't in use anymore.

[1]: https://hackage.haskell.org/package/base-4.8.1.0/docs/Contro...


> The best part is they don't have to worry that the updating thread might start another update in parallel while they read: the data structures are immutable so the value being read is guaranteed to remain immutable. Even if the updating thread continues "modifying" the new structure in the background, it doesn't have an effect on the other consumer's version.

If I understand what you said here correctly, this doesn't work for my example. A thread cannot continue with a stale version (and function properly). It must operate on a current version all the time (or block until it can).


You're right. For your example thats actually an error, and it wont happen if you `takeMVar` before you start working on the new value.

I'm describing a slightly different example there, where its okay to get the old data while updates are being "prepared" (e.g. every item in the dictionary is being fetched from the DB, typical for a server app). In that case, Haskell will work correctly. In Go on the other hand, reusing the data structure may result in a program crash, as Go's built in maps (which might contain that data) are not thread-safe.

(You can't even make the simplest type-safe, thread-safe mutable map that uses a RWMutex automatically under the hood. Because there are no generics)


> you realize that immutable data structures are indeed the right way

Well, that's false. Rust has mutable data structures but also statically prevents data races.


Do you mean this?

https://doc.rust-lang.org/nomicon/races.html

If that then sure, that might work too if you really need to do it. I wouldn't go so far as to say its the right way - it seems very bothersome to me.

Its more of a "yes, I'm willing to go through all this incredible pain, because I get some gain for it (constrained hardware? idk). Rust, please help me do it right."

Of course you can't do that in Go. Not only you don't get static guarantees, you can't even write a generic map with atomic access.

edit: by pain I mean this: https://doc.rust-lang.org/book/concurrency.html - and yes, thats painful compared to using immutable data structures.


> If that then sure, that might work too if you really need to do it. I wouldn't go so far as to say its the right way - it seems very bothersome to me.

There are plenty of bothersome things about "immutable only" too.

I've employed both approaches in earnest. Each have their own set of trade offs.

> Of course you can't do that in Go. Not only you don't get static guarantees, you can't even write a generic map with atomic access. You have to remember to use RWMutex every single time. No generics.

I'm quite aware of Go's limitations, thanks.

I find it amusing that you've dismissed an entire category of practice to statically eliminating data races at barely a glance. Irony, it seems. The very thing that people lament about Gophers is precisely the behavior you've demonstrated here! (Quite literally in fact. How many gophers have you heard say something like "generics is bothersome"?)

You've been polite, but snobbery is vexing, no matter where it comes from.


Fair enough. I admit to not knowing when you would prefer statically checked mutable data structures to immutable ones except for a few cases (dynamic programming arrays, fast matrix libraries, memory constrained environments).

I did use the word "seems" there though. Its not really dismissal, I would indeed like to be enlightened. In projects where I can afford a GC, I'd always take the GCed option (in my case an overwhelming majority). Same for immutable data structures (use whenever they can be afforded). Are those bad heuristics? (Afforded here refers to performance/memory constraints only)

One thing that GCed languages don't solve very well is handling other more scarce resources (file handles, connections from a pool, etc). It seems that Rust managed to solve this nicely. If only it was possible to use GC for everything except those kinds of resources (perhaps it is?), that would be perfect.


> I admit to not knowing when you would prefer statically checked mutable data structures to immutable ones except for a few cases (dynamic programming arrays, fast matrix libraries, memory constrained environments).

Those sound like pretty compelling use cases to me, and also ones that seem to be well suited for Rust. You might also consider looking at Servo; I bet its engineers could list a myriad number of reasons why immutable-only data structures are insufficient.

I note that performance is not the only trade off worth examining (to be fair, I think you acknowledged this). Another aspect of the trade off is abstraction, albeit this is fuzzier. Mutation can be more natural to a lot of folks. My pet theory is that we've built up a defense mechanism against mutation because it's the source of so many bugs; but Rust's static guarantees are worth consideration here. They remove many of the problems normally ascribed to mutability. For example, Rust not only prevents data races, but it also prevents aliasing mutable pointers to data at compile time, which defeats another class of bugs not related to concurrency at all.

My main point of contention with your comments is that you think you've stumbled on to the "right" way of doing something. In my opinion, that's nonsense. What's the point, even, to declare such a thing? Instead, focus on what the trade offs are, then make a decision based on the constraints you've imposed in any given situation. (Valid constraints absolutely include "immutable data structures are easier for me to reason about intuitively.")

> In projects where I can afford a GC, I'd always take the GCed option (in my case an overwhelming majority). Same for immutable data structures (use whenever they can be afforded). Are those bad heuristics? (Afforded here refers to performance/memory constraints only)

They don't seem like bad heuristics to me. They don't really correspond to my own heuristics, depending on what problem I'm trying to solve. (I once chose a language for a project based purely on the fact that I wanted to target non-programmers.)


Its not just that shared mutable state is hard, I'm thinking of the whole reasoning apparatus you get at your disposal:

http://www.haskellforall.com/2013/12/equational-reasoning.ht...

That indeed seems very much like something that can be called the "right way". If all functions in a given subset of the code are pure I can even imagine a tool that combines hoogle with your function's type signature and existing types to suggest how to finish writing your function (its just a graph search with nodes being the types and functions as the links). edit: seems like I don't need to imagine it - https://github.com/lspitzner/exference

Rust's way seems to me like they encode all the tediousness of dealing with shared mutable state into the type system. This is good, I guess, if you need to keep doing what you've always been doing but in a much safer way.


> That indeed seems very much like something that can be called the "right way".

No. It's just another useful tool in the toolbox. It comes with costs. Sometimes you don't want to pay them. Stop trying to monopolize "the right way."


I am not trying to monopolize "the right way". We were originally talking about Go, a language with a garbage collector made for writing concurrent servers. This is an area where immutable data structures are a no-brainer "right way" to avoid data races in the majority of cases, and I was expressing my frustration at the inability to write them in Go.

I really have no idea how the conversation became one about writing browser engines, embedded systems or systems with realtime constraints in Rust :)


> This is an area where immutable data structures are a no-brainer "right way" to avoid data races in the majority of cases

I continue to find your phrasing extremely off-putting, condescending and snobbish. I suggested a few ways of wording your concerns better, but it seems you're intent on remaining a snob.

I disagree that anything about your suggestion is a "no-brainer."


> I continue to find your phrasing extremely off-putting, condescending and snobbish. I suggested a few ways of wording your concerns better, but it seems you're intent on remaining a snob.

Thats a bit over the top, but I'll concede that my wording needs work. I enjoy discussing concrete problems and projects - hopefully fixing this will help get more of that. You did make some very good points as to why we avoid mutation, and I will try and evaluate Rust in more depth.


I don't think this is a good analogy at all. The article is more akin to walking outside in December and stating "here's why December is too cold for my liking". The article is just a series of observations on the author's subjective opinion about a language, along with reasoning on how that opinion was formed. It should be read as "why Go is not good (in my opinion)".

This is a major problem that I struggle with: saying "I think" and "in my opinion" gets old really fast and makes everything you say sound waffle-y, but if you don't say things like that, some people will interpret your statements as if you are claiming to state objective fact.

My sense is that such a diminishingly small amount of the things people discuss is actual fact that I can usually prepend "I believe" to any sentence I read. I am pleasantly surprised when I find that this rule fails to work for something, but that doesn't usually happen on the internet.


I agree, but then people can't complain much when other people, like the Go team, have different opinions and make different decisions than they would. We should encourage experimentation rather than discourage it. No one is forcing anyone to use Go, and there are plenty of languages that have generics, are immutable by default, etc, etc.


> We should encourage experimentation rather than discourage it.

I made this point already, but to reiterate more succinctly: we should definitely do that, and we should definitely also write about our thoughts on how we think those experiments are going, which is exactly what the OP is doing.


Sure, but don't be surprised when people have different opinions and aren't convinced by anecdotes. Reading the comments here you can find tons of people that are shocked, SHOCKED, that not everyone agrees with them on generics, immutability, etc. I think it's important that we remember that these are all opinions with no empirical data to support them, on either side.


Calls for rigor are unproductive, I think. The unfortunate reality is that an experiment with the rigor you want would be prohibitively expensive. The big differences in productivity I suspect are going to be in larger projects over longer periods of time. You also can't figure anything out from small sample sizes, so you would need to take four large teams, split them randomly into two groups of two, and have each time solve the same large problem over a long period of time.

Unfortunately, since we can't afford the rigor, we have to make do with anecdotes and less powerful studies.


I agree, but then we shouldn't assert conclusions with the level of certainty expressed in this blog post and many of the comments here.


Developers are also far more productive when using a programming language than when developing a programming language. So let's not have programming languages.

The thing is, a million developers can use a generic library, but only one has to develop it.

Excuse me now, I'm going to tune out a million boring arguments of the form "higher level languages are amazing ..." and go back to debugging IBM 360 assembly language program.


The usefulness of generics is easily demonstrated by Go itself — it has generics. It's only available to a bunch of magical functions (new, make, len, append), types ("chan", "map", arrays and slices are all generic types) and keywords ("range"). But it's there, in plain view.

Nobody can objectively argue that these aren't useful, or that they could have been implemented in a non-generic way without destroying the language. Wouldn't the utility transfer to the developer's own code?

As for "let Go do its thing", I would argue that it already has been done: Plenty of pre-existing languages don't have generics. The lack is always felt, including in Go's direct precursors (such as Modula-2 and Oberon, languages that people later hacked generics onto because their real-world ergonomy as designed by Wirth wasn't great).


> Any critique of Go seems to be met with angry pitchforks in this place.

You can say that about any language. The people that like the language will always defend it. e.g. PHP, C, Ruby. They all have flaws and yet when one talks about their shortcomings, the people get defensive.


But those languages are universally accepted as bad (well aside from Ruby, I don't know much about it because I already know Python and never felt I needed a different syntax for pretty much the same thing (arguably less used))

If someone started developing a language today and came up with C or PHP they would be criticized for many of the pitfalls of the mentioned languages and there would be a lot of improvements that could make those languages objectively better. But they are what they are because their design decisions were made under totally different environment than today and the advantage of using them is that you get to leverage everything built since then (well this argument is much stronger for C than PHP because PHP has alternatives that could be viewed as strictly better).

But Go is a new language. It's trying to sell itself as a better solution to existing problems so the level of criticism is going to be (justifiably) much higher - it doesn't just need to meet minimum usability bar - it needs to be better than existing defaults, and significantly so to justify the cost of switching - both in terms of learning and porting.


It's trying to sell itself as a better solution to Google's existing problems[1]. That's the keyword (no pun intended).

Just so happens that some developers at-large perceive an overlap, correct or incorrect, between their problems and Google's problems. So they use the language, and they're happy with it.

Go is not trying to sell itself as a better general solution. It was built within the context of Google's problems.

[1] https://talks.golang.org/2012/splash.article


>It's trying to sell itself as a better solution to Google's existing problems[1]. That's the keyword (no pun intended).

That's a good point - but if that is the case then any comparison to general purpose programming languages is pointless.


I think the point they're making is that the Go community is unusually pitchfork-ey. Having used Go since pre-1.0 days, I certainly agree; there's a very strong sense of, "if you want <feature X>, you're doing it wrong" - despite legitimate concerns, like the ones outlined in this article.


It reminds me of the Java community when Sun stopped adding significant features for years after 2004. Every feature which wasn't added was widely considered by most Java developers as being in the name of protecting us from ourselves. When Java 8 came out suddenly some of those bad/evil features became flavor of the week. There's a tendency to try and convince yourself that the language designers made the best decisions at every turn, which isn't a healthy way of thinking critically.


I've heard this same criticism levelled at the Clojure community, as well.

Honestly, I can't think of any language community that's developed such a reputation for pitchforkiness towards suggestions as the Go and Clojure communities.


What a strange argument. The difference is that clojure is about as extensible a language as it's possible to have.

A macro system in a homoiconic language allows you to implement many types of semantic sugar or things that would be full-on 'language features' in other languages as a simple library. See core.async: https://github.com/clojure/core.async


Steve Yegge has written a lot about this. Much of his writings on the subject are in these mailing-list posts: https://groups.google.com/d/topic/seajure/GLqhj_2915A/discus...

Make sure to expand all of his posts on the thread, because he goes back and forth for a while.

Much of it has to do with the community's attitude towards macros: there's an attitude of "macros are bad and you shouldn't use them", and people who write macros are often jumped on by the community.

Here's one sentence of Steve's that sums it up:

> When people announce: "hey, I made a loop macro!" the response absolutely can NOT be: "why can't you just write it as a series of maps and reductions?"

And another:

> If Clojure people all said "of course you can use macros! Of course you can use CL-style loop facilities! It's your code, do what you like! Feel free to use nonlocal exits all you like!" -- well, then it would be a lot closer to a Yes language.

The problem is the way the community treats people who don't follow the prescriptive norms of the core Clojure people (norms which are often in conflict with the broader Lisp community).


Using 'macros' is no substitute for having taste.

The Clojure developer had a certain vision for a new language - otherwise he could have just continued to use Lisp (which he earlier used for a few years). It might be useful to respect that and develop Clojure along this vision.

Something like Common Lisp follows a different vision. Common Lisp was a large community effort and the language EXPLICITLY had been designed to be morphed by the user into widely different shapes. That's why it reserves characters to the user, why it has a programmable reader, why it has procedural macros, ..., and why CLOS has a Meta Object Protocol. Probably that was also too much flexibility.

But even with Common Lisp, because it gives you little guidance how to use it and there are a gazillion programming styles possible, you need to develop taste. You can design ugly code and extensions and you can learn to develop better code and extensions. Common Lisp supports LOOP, because it was already there (it was introduced with Interlisp in the 70s, then brought to Maclisp and Lisp Machine Lisp) and there wasn't a better alternative at that time.

The 'best' iteration construct in the Lisp world is Jonathan Amsterdam's Iterate. But that would also not fit well into Clojure... But Iterate fits well into Common Lisp and works nicely as an alternative to LOOP.


> You can say that about any language. The people that like the language will always defend it. e.g. PHP, C, Ruby. They all have flaws and yet when one talks about their shortcomings, the people get defensive.

thats more the developers than the langauge. I'm a diehard ruby guy, but when people talk about its shortcomings or the benefits of another language, i listen. It only clarifies what can and cannot be done, and what would be better done another way or in another language.

I think its part the developer, part the community around it.


Talk about the (legitimate) Haskell shortcomings and you'll be met with open, honest acceptance, mitigation strategies, and academic discussion.

IRC, /r/haskell, whatever.


Rust too. The bunker mentality of the Go community is not unique, but its fervency, I think, is not currently matched anywhere else.


I don't know about that.

C programmers can be defensive when it comes to changes in the core language, but they're very receptive of all kinds of third-party libraries, you don't see much of "if you want <feature X>, you're doing it wrong". Take, for example, object-oriented programming. If you try to confront a seasoned C programmer about how C sucks because it doesn't support OOP, instead of being told that OOP is bad and C shouldn't ever support it, you'll most likely get a response saying that C does support OOP with the proper libraries, such as GObject, and pointing out that large C projects like the Linux kernel are already object-oriented.

Go, on the other hand, just pooh-poohes the concept. Clojure has a similar negative attitude. For example, the Clojure community is notoriously hostile to any suggestion of implementing Common Lisp's loop macro. There's no, "well, that's the beauty of Lisp, you can always write your own macros if you don't like what comes with it". Instead, you just get vitriolic condemnation of the whole idea of such a construct. If you write your own loop macro and post it in a Clojure community, the response is typically "why would you even think of writing such an abomination, what is wrong with you?", which is a disgustingly hostile way to treat people who are volunteering their time to contribute to the community.


Is the bunker-mentality a recurring pattern for all Google OSS?

I've heard people talking about it in Dart, Angular, and V8/Chrome, and I'm not sure if it's true or not.


I don't think it's Google specific. I'm quite a Scala fan but there was a time when many of us, probably myself included, had an unhealthy bunker-mentality.




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

Search: