
Why Go Is Not Good - pohl
http://yager.io/programming/go.html
======
mustpax
There's something very seductive about languages like Rust or Scala or Haskell
or even C++. These languages whisper in our ears "you are brilliant and here's
a blank canvas where you can design the most perfect abstraction the world has
ever seen."

But, for systems programming, abstractions suck. They always, _always_ have a
cost. When abstractions break, you not only have to deal with a broken system
but the broken abstraction itself too. (Anyone who has ever seen a gcc
compiler error for C++ knows how this feels.)

Therein lies Go's value proposition. It does not make it possible to make
things pretty (ugh, nil). It just makes it impossible (ok, really hard) to
overcomplicate things. When you write Go code, you can picture what the C
equivalent would look like. You want to deal with errors? Here's an if
statement. Data structures? Here's a struct. Generics? Here's another if
statement, put it inside your for loop.

Obviously, Go is not the right choice of language for most things. When you're
doing application development, you may be able to afford the cost of
abstractions. But for tools that only need to do one thing and do it extremely
well, it's either that or C. And I'm not going back to managing my own memory
anytime soon.

~~~
wyager
>But, for systems programming, abstractions suck.

Could you clarify what you mean by "systems programming"? To me, that means
working with embedded systems, which Go is certainly not appropriate for.

~~~
cjbprime
> Could you clarify what you mean by "systems programming"? To me, that means
> working with embedded systems, which Go is certainly not appropriate for.

The blunt but approximately correct version is that embedded means that you're
running on hardware that isn't powerful enough to run a Linux kernel.

Systems programming just means you're working below the application layer. So
if you take your laptop and write a device driver, or work on filesystem or
networking code, you're doing systems programming without doing embedded.

~~~
waps
Just curious : how does one run go without a linux kernel ? (without a kernel
at all, please, I know about the freebsd port)

~~~
cantankerous
You'll probably be looking for a Go runtime that fills a kernel shaped hole.
Without a kernel, where do all your syscalls go?

~~~
waps
Yes but the point of the parent poster was that go can work on minimal
embedded systems, which to my knowledge it cannot, so I enquired.

~~~
lmm
No, the point of the parent poster was that "systems" is not the same as
"embedded", and that while go may not work on the latter it can work on the
former.

------
tshadwell
For fear of disagree downvotes: I would say that many of the qualms brought up
in this article are problems that are encountered fighting the language.

The problem of 'summing any kind of list' is not a problem that is solved in
Go via the proposed kind of parametric polymorphism. Instead, one might define
a type, `type Adder Interface{Add(Adder)Adder}`, and then a function to add
anything you want is fairly trivial, `func Sum(a ...Adder) Adder`, put
anything you want in it, then assert the type of what comes out.

When it comes to iteration, there is the generator pattern, in which a channel
is returned, and then the thread 'drinks' the channel until it is dry, for
example `func (m myType) Walk() chan->myType` can be iterated over via `range
v := mt.Walk(){ [...] }`. Non-channel based patterns also exist, tokenisers
usually have a Next() which can be used to step into the next token, etc.

The Nil pointer is not unsafe as far as I know, from the FAQ:
[http://golang.org/doc/faq#no_pointer_arithmetic](http://golang.org/doc/faq#no_pointer_arithmetic)

The writer seems to believe that functions on nil pointers crash the program,
this is not the case. It's a common pattern in lazy construction to check if
the receiving pointer is nil before continuing.

Go is not flawless by any means, but it warrants a specific style of
simplistic but powerful programming that I personally enjoy.

~~~
wyager
>The writer seems to believe that functions on nil pointers crash the program,
this is not the case. It's a common pattern in lazy construction to check if
the receiving pointer is nil before continuing.

And what happens when you don't check? It crashes. That's the unsafe part.

These crashes are simply not possible in Rust and Haskell, and the type system
notifies you if failure is possible (because the function will return an
Option/Maybe).

~~~
shadowmint
Woa, Rust is great, but I think you're going a little far on the hyperbole
there.

You can easily generate a segfault in Rust in 'unsafe' (or 'trusted') code;
that might only restrict errors of that nature to code that uses unsafe
blocks.

Practically speaking that's pretty common; once you enter an FFI unsafe block,
you lose _all_ type safety; but you can totally do it without FFI too. Eg.
using transmute().

In fact, there's _no way_ to know if you code contains 'hidden' unsafe blocks
wrapped in a safe api in some 3rd party library that might cause a mysterious
segfault later on.

You can argue that 'if you break the type system you can do anything,
obviously'; that's totally true.

I'm just pointing out the statement: "These crashes are simply not possible in
Rust and Haskell" <\-- Is _categorically false_.

You can chop your own arms off in Rust just like anything else (including Go).

~~~
dbaupp
> You can easily generate a segfault in Rust in 'unsafe' (or 'trusted') code;
> that might only restrict errors of that nature to code that uses unsafe
> blocks, but practically speaking that's pretty common; once you enter an FFI
> unsafe block, you lose all type safety; but you can totally do it without
> FFI too. Eg. using transmute().

Not directly addressing what you're saying, but, IME people are far too quick
to use `unsafe` code. One needs to be quite careful about it as there's a pile
of invariants that need to be upheld: [http://doc.rust-
lang.org/master/rust.html#behavior-considere...](http://doc.rust-
lang.org/master/rust.html#behavior-considered-unsafe)

 _> once you enter an FFI unsafe block, you lose all type safety_

You don't lose all type safety, especially not if the FFI bindings you're
using are written idiomatically (using the mut/const raw pointers correctly;
wrapper structs for each C type, rather than just using *c_void, etc).

------
eloff
I just spent the weekend learning Go and writing a single writer multi-reader
hashtable for threadsafe access. I picked it deliberately because it's against
the philosophy of the language, which is to share by communicating instead of
sharing data structures directly. It was painful to write:

    
    
      // Do NOT reorder this, otherwise parallel lookup could find key but see empty value
      atomic.StoreInt32((*int32)(unsafe.Pointer(uintptr(uint64(slot)+4))), val)
      atomic.StoreInt32((*int32)(unsafe.Pointer(slot)), key)
    

However, the non volatile, non unsafe parts of the code were an absolute joy.
Testing was a joy, compiling was a joy, and benchmarking was a joy. I was
impressed that it allowed me to bypass the type system completely and do
nasty, nasty things in the pursuit of performance. I want a language that lets
me do nasty things where I must, but that makes the other 95% of the program,
and the job of compiling, testing, and maintaining that program easy. Go
excels here. Rust, C++, Haskell, Scala will never be good at that because
they're too damn complicated (although each of them make the nasty parts a
little less painful!)

The end result of my weekend's hacking? On an i7-4770K @ 3.5ghz

    
    
      BenchmarkGoMapInsert-4  20000000               110 ns/op
      BenchmarkHashIndexInsert-4      100000000               25.6 ns/op
      BenchmarkGoMapLookup-4  50000000                78.5 ns/op
      BenchmarkHashIndexLookup-4      100000000               17.7 ns/op
    

About 4x faster than Go's builtin map, for int32 key/value on both insert and
lookup. And it allows any number of reader threads concurrent access with a
writer without any synchronization or blocking, unlike Go maps. It doesn't
allow zero keys, unlike go maps, and it doesn't allow deletes. Hardly apples
to apples, but the performance of pure Go code is impressive nonetheless. 200
LOC, not counting tests.

~~~
pornel
Rust has `unsafe` blocks in which you're allowed to do all nasty hacks you
want.

Rust actually isn't that complicated. Don't get discouraged by comparisons to
Haskell — it's still a C-family language where you can play with pointers and
mutable state.

To me Rust still feels like a "small" language (similar size as Go or ObjC,
not even scratching complexity of C++). It's mostly just functions + structs +
enums, but they're more flexible and can be combined to be more powerful than
in C.

~~~
thinkpad20
I think comparisons to Haskell are not too far off the mark. Haskell is not
that complicated of a language either. You can do all sorts of complicated
things with it, but the language itself is relatively simple. It just has a
lot of stuff that you're likely to have never seen before (extensive use of
higher-order functions, higher-kinded types, etc), and its type system
produces error messages that seem obscure from the outside. Similarly Rust can
have some rather obscure error messages that you're probably not going to have
seen before during compilation - lifetime specifiers, errors about using
things in the wrong contexts, heck, even "Bare str is not a type (what?)"

I'm much more familiar with Haskell than Rust, but having played around with
Rust I think they're on a par with each other in terms of difficulty,
depending on your background.

------
plainOldText
Reading this article and the HN comments made me realize, how people want to
use the same language for everything. Unfortunately, this is not possible,
since each language was design with certain use cases in mind. I too, am
guilty of wanting a language to do everything, to be fast, memory efficient
and also easy to program in.

Perhaps our ultimate quest in terms of designing languages is to design one
smart enough that can be used to program toasters and clusters alike. Until
then, we might as well use the right tool for the job.

~~~
jamesaguilar
> to be fast, memory efficient and also easy to program in.

I think the issue Go detractors have is that it is none of those things, nor
is there any pair of those things for which there is no better alternative
than Go. If you want fast and memory efficient, you could pick C++ or C. If
you want something a little easier to program in, you could pick C#, which
dominates Go in all three categories. If you want to go easier to program in,
there are plenty of languages like Python that are more powerful than Go.

------
s_kilk
This was a good read. Can anyone comment on whether they find the problems
outlined in the article to really be painful in day-to-day go development?

From my initial dabblings with the language, it feels like its constraints may
not actually be a big deal in practice, and may even be more of a help than a
hindrance in large projects. It would be nice to get some commentary from more
experienced go users.

~~~
mholt
In practice, Go has caused me less frustration than any other language I've
used. I feel like the author's complaints here aren't really grounded in much
experience, or maybe he's trying to use the wrong tool for the job.

The author's conclusion:

    
    
      · Go doesn't really do anything new.
      · Go isn't well-designed from the ground up. 
      · Go is a regression from other modern programming languages.
    

is hardly sustainable. Go was production-ready in 2011 with a stable version
1.0. It has a surprisingly mature tool chain and vibrant community. Go cross-
compiles from my 64-bit Mac to a 32-bit Raspberry Pi or ARM Android phone on a
whim. I can deploy my app by copying a single, self-contained binary. Tell me
again that Go does nothing new for us.

Go makes concurrent programming safe and easy (with a nice syntax) --
something that we frankly should have done 30-40 years ago when we first
started thinking about multiprocessing. Go was invented by folks like Ken
Thompson (who created Unix) and Rob Pike (who created the Plan 9 operating
system and co-created UTF-8). Tell me again that there isn't good engineering
behind Go.

Finally, Go attacks the needs of modern programming from a different paradigm
than we have been using for the last 10-20 years. From the first paragraph of
Effective Go:

> ... thinking about the problem from a Go perspective could produce a
> successful but quite different program. In other words, to write Go well,
> it's important to understand its properties and idioms.

So of course it's different than a lot of other aged languages. Go tackles
newer problems in a newer way. Tell me again that Go is a _regression_ from
other programming languages.

~~~
Peaker
> Tell me again that Go does nothing new for us

None of the things you mentioned are new.

> Go makes concurrent programming safe and easy

Mutability & concurrency, nils, interface casts -- these things all go
_against_ safe.

> Tell me again that there isn't good engineering behind Go.

You seem to think that a language that has baked in syntax for concurrency, or
that has famous people behind it necessarily has "good engineering" behind it.
I don't understand how one leads to the other.

When so many mistakes and regressions go into a language, one shouldn't care
that famous names are behind it.

> Go tackles newer problems in a newer way

Go is essentially Algol 69 with baked in concurrency syntax.

> Tell me again that Go is a regression from other programming languages

Losing null safety, sum types & pattern matching, parameteric polymorphism and
type-classes, all form a _huge_ regression in PL design from the state of the
art.

~~~
AnimalMuppet
You're thinking wrong. You're also proving the grandparent's point.

You're thinking in terms of "here's this set of bullet point features that I
think a language has to have to be a proper, modern language." But the
grandparent was asking you to consider that a different set of features might
have value form some real-world problems that Go's authors had really bumped
into. You reply, "Nope, couldn't have - it doesn't have my bullet point
features!"

There are more things in programming than are dreamt of in your philosophy of
what a programming language should be.

~~~
Peaker
He said Go had something new to offer, and listed old things.

He said Go makes concurrency safe & easy, when Go emphasizes features that
contradict safety and ease.

He said Go tackled problems in a newer way, when Go is really Algol69 +
coroutines.

He denied Go regressing from other languages, when it throws away decades of
PL research.

In none of this did he say "Here's an alternate set of features that ...". No.
He said concrete, specific, wrong things.

What you are saying is a different thing -- and I also disagree with you.

These "bullet list" features weren't invented for the lols. They were created
to solve _real problems_. Problems that Go doesn't have an alternate solution
to.

Go programs dereference null. That is a bad thing. Languages with my "bullet
point features" do not dereference null.

Go programs duplicate code to handle different types. Ditto.

Go programs can (and thus do!) mutate some data after it was sent to a
concurrent goroutine. Ditto.

Go programs can cast down to incorrect types. Ditto.

The "bullet point features" are important. There are alternatives, but Go
doesn't sport any of them.

------
andrejewski
I am completely in support of Haskell and functional languages in general.
There are some gaps in Go and definitely some glaring problems. But this
comparison also only lists the bad. Go is good for what it was intended for
which is concurrent programming and server/web application.

Just a note: I don't think it is fair to say Go has absolutely no immutability
as it was defined, it does have "const". See
[http://golang.org/ref/spec#Constant_expressions](http://golang.org/ref/spec#Constant_expressions)

~~~
AndreasFrom
`const` is for compile-time constants where something like `let` in Rust can
be used for values generated on the fly. The latter is immensely more powerful
and actually provides the described benefits like guaranteeing that data is
not changing under your feet.

Edit: While `const PI = 3.1415…` is threadsafe, it's not very useful in
comparison to runtime-immutability :)

~~~
andrejewski
Yes, the guarantees provided by true immutability are not met by "const". I
wrote the note loosely using the author's expectations and advantages of
immutability, not the exact definition. But thank you for letting me clarify.

Also I just noticed with Go's Unicode support we can write `const π = 3.14..`
if we really wanted.

------
mseepgood
So he wants Haskell. Haskell already exists and has all the features he wants.
He should have written his blog in Haskell, but he didn't, and I know why:
because a language, which throws all these features together is no longer a
practical language. He only sees the benefits of features, not the cost they
introduce.

~~~
thinkpad20
Hindley-Milner inferrence, operators-as-functions, and certain other features,
I guess I can see arguments against (although I think they're overstated).
However, I don't see any reason why generics, no null pointers, pattern
matching, and certain other features would be problematic, let alone not be
useful, in a language like Go. I'd be curious as to hear your justification of
the claim that "a language, which throws all these features together is no
longer a practical language."

~~~
Paradigma11
I do think that Meijer and Griesemer agree somewhere in this intervew that
generics are hard to get right (co and contravariance) and that forgoing
generics is a valid design choice:
[http://channel9.msdn.com/Blogs/Charles/Erik-Meijer-and-
Rober...](http://channel9.msdn.com/Blogs/Charles/Erik-Meijer-and-Robert-
Griesemer-Go) It's been a while and might have been another interview tough.

~~~
Shorel
I think D got generics right.

------
dcposch
> A Good Solution: Constraint Based Generics and Parametric Polymorphism

> A Good Solution: Operators are Functions

> A Good Solution: Algebraic Types and Type-safe Failure

> A Good Solution: Pattern Matching and Compound Expressions

People have tried this approach. See languages like C++ and Scala, with
hundreds of features and language specification that run into the thousands of
pages.

For an unintentional parody of this way of thinking, see Martin Odersky's
"Scala levels": [http://www.scala-lang.org/old/node/8610](http://www.scala-
lang.org/old/node/8610)

For additional hilarity, note that it is an _undecidable problem_ whether a
given C++ program will compile or not.
[http://stackoverflow.com/questions/189172/c-templates-
turing...](http://stackoverflow.com/questions/189172/c-templates-turing-
complete)

\--

Go was created by the forefathers of C and Unix. They left out all of those
features on purpose. Not unlike the original C or the original Unix, Go is "as
simple as possible, but no simpler".

Go's feature set is not merely a subset of other langages. It also has
canonical solutions to important practical problems that most other languages
leave do not solve out of the box:

* Testing

* Documentation

* Sharing code and specifying dependencies

* Formatting

* Cross compiling

Go's feature set is small but carefully chosen. I've found it to be productive
and a joy to work with.

~~~
runT1ME
You seem completely ignorant of the things you're attempting to talk about.
Scala doesn't have "hundreds' of features, nor is the language specification
thousands of pages. It's just an outright fabrication to say so.

>Go was created by the forefathers of C and Unix.

Yeah, and it's obvious (and sad) they ignored the last twenty years of PL
research and progress.

>They left out all of those features on purpose

Did they? I don't believe this is the case, as I've heard from the creators
many times that they want to add generics but haven't figured out the details
yet.

Are you _really_ going to sit here and argue that static typing is important
_EXCEPT_ for when working with collection? That parametric polymorphism
doesn't make things simpler?

~~~
masklinn
> Yeah, and it's obvious (and sad) they ignored the last twenty years of PL
> research and progress.

More than thirty years (at the time it was released), the first language with
"modern" generics was ML in 1973.

------
briantakita
This article presumes that everybody _wants_ an elaborate type system. I'm not
sure that is the case. I still see an elaborate type system as incidental
complexity. I may be in the minority and I may not have worked in domains
which benefit from such modeling. Maybe I'm stuck in a blub paradigm.

Here's my reasoning. I'm a fan of human language & domain ontologies. Word
definitions are quite flexible & do not have an elaborate type system. I don't
feel the need to have a provably correct logical system to have a useful
conceptual tool (i.e. analogies). I enjoy ambiguity. Ambiguity can lead to
paradoxes, which in turn leads to exploration & novelty.

Strongly typed systems, by default, give me the impression that the domain
ontology is figured out on a highly precise level. That is never the case. You
can almost always go deeper. Domain language precision is tough to model &
express.

I prefer data structures to drive operations. I suppose that a schema is often
useful, however I don't feel like I need the programming language to enforce
the schema.

I also like to evolve the design, using tests. Tests are really examples to
exercise the program's API with expected I/O.

People often equate an evolved programming language/paradigm as being better.
In the case of Javascript, they point out that is was created in a few days as
evidence that it's "bad". The thing about an evolved language/paradigm is it
has evolved down a certain path. That often means restricting the freedom of
the programmer to evolve the program down another path. I'm not picking one
way to be better than another. However, I do see tradoffs to both approaches.
I personally prefer a more flexible language. It can be evolved, as long as
the evolution does not restrict my freedom to evolve the program.

~~~
Peaker
I recommend Dijkstra's paper: "On the foolishness of "natural language
programming" [1].

It explains how natural language is a very poor way to express programs, and
how it held back science and progress for many centuries.

Strong types describe your code precisely. If your code doesn't match the
model yet, that's fine.

But _your code_ has invariants in it. Things like: "this variable can never be
nil, that variable can". These invariants can relatively easily be encoded as
static types. Not doing so is just throwing away safety and documentation for
virtually no benefit.

Other invariants are similar. Instead of documenting them or keeping them in
your head, you let your compiler worry about them. And then when you break
those invariants, the compiler is your friend! He helps you go and find all
the other pieces of code that relied on the broken invariant, so you can fix
them.

You say you prefer a more flexible language: But Go is extremely inflexible.
It has a very primitive set of tools to do everything, clumsily. A language
like Haskell, for example, lets you have Go-like coroutines and channels. But
it also lets you program with software transactional memory, or use other
parallelism constructs.

Also, the _inability_ to specify invariants of your program isn't flexibility.
The ability to specify them or opt out of specifying them, which is what
strong type systems give you, is.

1\.
[https://www.cs.utexas.edu/users/EWD/transcriptions/EWD06xx/E...](https://www.cs.utexas.edu/users/EWD/transcriptions/EWD06xx/EWD667.html)

~~~
briantakita
> It explains how natural language is a very poor way to express programs, and
> how it held back science and progress for many centuries.

I'm not interested in using natural language to implement the software (write
code). I'm interested in using natural/technical language to create an
ontology for the architecture. This is where things get gray.

When I think of an architecture, I think of something that evolves over time.
I think of architecture as a tool to facilitate communication between
programmers, designers, project management, domain experts, & users.

This ontology evolves over time as the software, understanding of the domain,
& the domain itself changes.

I see this fuzziness as an accurate model of the conceptual domain, which is
ultimately based on the understanding of multiple humans. This understand is
fuzzy and heavily dependent on context. And yet, the ontology attempts to
coral this fuzziness into more strongly defined concepts, which map to the
implementation. The implementation should not be fuzzy at all.

> Other invariants are similar. Instead of documenting them or keeping them in
> your head, you let your compiler worry about them.

I usually use guard clauses to protect against nulls. I rely on my tests &
production monitoring systems to prove that the implementation is incorrect.
When there is such proof, I correct the system. In environments that support
rapid feedback & deployment, like the web, this works well. In environments
that don't support rapid deployment, iteration, and where lives are at stake,
not so well.

> But Go is extremely inflexible. It has a very primitive set of tools to do
> everything, clumsily. A language like Haskell, for example, lets you have
> Go-like coroutines and channels. But it also lets you program with software
> transactional memory, or use other parallelism constructs.

That sounds good to me.

> Also, the inability to specify invariants of your program isn't flexibility.

That mostly sounds good. I would want invariants to be optional, which sounds
like is the case.

~~~
lmm
> I usually use guard clauses to protect against nulls.

An interesting (to me) insight was that in a language with a flexible type
system, the types are effectively just a set of assertions at the start and
return of every function, that say that the inputs and outputs have certain
properties. With a compact syntax and zero runtime overhead, which is nice.

I do think that some strongly typed languages make it too difficult to step
outside the type system; in Scala I have to do something like
(x.asInstanceOf[{def foo(): String}]).foo() whereas in Python I can just write
x.foo(). But once I started seeing the type system not as a fixed piece of the
language but as a framework for encoding my own assertions, it became useful
enough that I can't stand to live without it.

~~~
Peaker
It's much more than just at the start end return of every function. But OTOH,
it's much less than assertions, since they're restricted to a subset that can
be proven (usually automatically).

Note the Scala verbosity here is Scala-specific. In HM-style languages, type
inference works much better and you don't have to do such things. You might
still have to explicitly "lift" a value from one type to another (e.g: Wrap a
value in "Just", or use "lift"), but that's a much more minor price to pay.

~~~
lmm
Lifting in Scala is not at all verbose; I'm talking about casting, calling a
method that the type system doesn't know is present.

~~~
ches
> I do think that some strongly typed languages make it too difficult to step
> outside the type system; in Scala I have to do something like
> (x.asInstanceOf[{def foo(): String}]).foo() whereas in Python I can just
> write x.foo()

 _Have to_ make an explicit cast with a structural type? Surely you can do
better, like, say, a trait.

> I'm talking about casting, calling a method that the type system doesn't
> know is present.

I think a larger example is necessary to see how you ended up in such a
situation, but I suppose it's off-topic...

~~~
lmm
> Have to make an explicit cast with a structural type? Surely you can do
> better, like, say, a trait.

Usually, yes. But the only way that's as general as the Python line is to use
the structural type.

------
platz
For robots, Haskell is not used directly, but can be used as a DSL to generate
the appropriate C code. Here's one example:
[http://smaccmpilot.org/languages/index.html](http://smaccmpilot.org/languages/index.html)

~~~
wyager
True! Embedded programming DSLs are a very cool area of research. I figured
they might be a bit beyond the scope of the article.

------
aheu
Cached version -
[http://webcache.googleusercontent.com/search?q=cache:gmYvtpF...](http://webcache.googleusercontent.com/search?q=cache:gmYvtpFGhzsJ:yager.io/programming/go.html+&cd=1&hl=en&ct=clnk&gl=us)

------
wtbob
Regarding generic data structures, the author should consult the sort package,
which has typed generics; much the same approach can be used for generic data
structures.

More complex type inference requires a more complex (and hence buggier)
compiler.

Finally, the author should investigate the unsafe package; I believe the
following code will do what he wants:

    
    
      *(*byte)(unsafe.Pointer(uintptr(0x1234))) = 0xFF
    

Verbose? Sure.

------
otikik
One man's "must haves" are other man's "cruft".

------
tjholowaychuk
I also don't get why the for-loop uses a "range" keyword at all, isn't that
what typing is for, can't it just figure out that the type is enumerable?

I like most of Go so far, but interface{} is possibly the ugliest artifact of
a programming language that I've seen, next to pretty much all of c++

Rust and Go should have a baby

~~~
jamesmiller5
There is a subtle yet key difference between range and for. Ranges will only
start execution on input of known size and guarantee termination. Regular for
loops do not require a terminating state.

~~~
burntsushi
> Ranges will only start execution on input of known size and guarantee
> termination.

`range` can be used to receive values on a channel, which is certainly not a
known size and doesn't have guaranteed termination.

------
bayesianhorse
Go is designed to be a niche language. A very big niche, but essentially still
a niche: service components in clouds, where computing efficiency financially
trumps development times.

Go is more productive than C++, but less so than Python or some other
alternatives. Go's tooling, linking and libraries make it useful in the Cloud,
less so on mobile or personal computers. And the lack of third party
libraries, combined with a relatively slower speed of developing these
libraries (again compared to Python, javascript and others) means that Go will
have a hard time going beyond cloud services.

------
koffiezet
To be honest, I feel a lot of people are missing the point of Go, and I think
none of the points made are important. The single most important thing about
Go is it's simplicity:

For me it is a language that feels like a hybrid between a scripting language
and a 'real' programming language. Simple syntax with some powerful, easy to
use features, impressive library support for being only a few years old, but
compiles (static) to native code.

That fills a gap that Haskell and Rust don't, These more advanced languages
sacrifice simplicity for an attempt to be perfect. Go makes the clear
statement of being simple above everything else.

Give an average python/ruby/<insert scripting language here> coder the link to
"A 30-minute Introduction to Rust" ( [http://doc.rust-
lang.org/intro.html](http://doc.rust-lang.org/intro.html) ), and he/she will
give you a strange look and not understand half of what is being said there.
In the end they'll conclude it's not something for them. Give it to a C++
coder and he'll say 'oh nice, but I can do that in C++, use Boost<whatever>,
because C++ is superior to all!' \- and that coder there is their target
audience. A decent C++ coder will have invested too much time to learn another
language to solve problems he already learned to solve for himself in C++ a
long time ago. Rust might be better and would make his life easier in a lot of
cases, but still the majority won't make the switch.

Give the same <insert scripting language here> coder the Go documentation, and
he'll be off in no time, writing better code than he used to do, producing a
single binary which will not be an absolute nightmare to deploy. And that's
what every coder of scripting languages has always dreamed of - being able to
make programs in a simple straight-forward way, with as little dependencies as
possible, without needing a <language X> runtime. On top of that, Go makes
cross-compilation dead-easy.

There are a LOT more <insert scripting language here> coders out-there than
there are C++ programmers. Giving them Go makes running the stuff they write
more efficient confronts them with Git/source control (you would be surprised
how many don't know about SCM)

~~~
swah
I agree with you partially, but OTOH Go, I don't know exactly why, feels
pragmatic and ready for production.

Maybe its because of the creators, Google backing it, or the promise that 1.x
remains compatible, or that it ships with a standard library good enough to
write useful server stuff.

So despite all those flaws (I miss generics the most), I think it will become
the static Python replacement for the next 10 years.

(Its like how Factor handled 3rd party contributions: one library for some
particular task is blessed and shipped w/ Factor. Of course it doesn't
scale..)

------
oscargrouch
Go, Rust, Haskell, come from diffent ways of thinking about how to solve
problems using programming languages..

Go aims to be more simple and concise, in the end you write less code to do
the same thing, as you would in C++, Haskell or Rust.. because those 3
languages decided to "cover everything" and are worried about other things,
creating more burden to the programmer, but with something else to gain

Go is more of a productivity language.. it remind us the we have better things
to do in life, and not spend all the time coding, but enjoying that extra time
with your family for instance..

Therefore Go is good.. its only MAYBE "not good" for the same thing that Rust,
or Haskell are..

Besides.. this is the wrong way to market some technology or idea.. the best
way would be "Why Rust or Haskell are Good" instead of envy the success of
others..

Its all about tradeoffs.. and i think this article misses the point.. and care
only some things that will obviouslly make some languages more fit.. like, if
you care more about memory control,type systems and safety.. its obvious that
Rust and Haskell will look good and "correct"

But this is not all about it.. theres team working, productivity.. its a
balance.. and always depends of the problem domains.. some language are more
fit than others.. theres no need for bashing

~~~
codygman
You made the claim that "in the end you write less code to do the same thing,
as you would in C++, Haskell or Rust". Can you provide any examples where the
Haskell equivalent isn't more concise than the Go equivalent?

As a data point, here are links to the Haskell and Go implementations of the
TechEmpower benchmarks:

Haskell (78 sloc)

[https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...](https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/snap/bench/src/Main.hs)

Go (164 sloc)

[https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...](https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/go/src/hello/hello.go)

~~~
oscargrouch
Anyone can encapsulate or "hide" the inner workings so the resulting code will
look small.. how much of that code in the frontend is already implemented in
the standard library of each language??

To see the reality of it.. a better example would be something without any
support library...

Cherry picking is easy..

From the same benchmarks Game:

spectral-norm - Go

[http://benchmarksgame.alioth.debian.org/u64/program.php?test...](http://benchmarksgame.alioth.debian.org/u64/program.php?test=spectralnorm&lang=go&id=1)

spectral-norm - Haskell

[http://benchmarksgame.alioth.debian.org/u64/program.php?test...](http://benchmarksgame.alioth.debian.org/u64/program.php?test=spectralnorm&lang=ghc&id=4)

~~~
codygman
Picking something without a library will give an advantage to the other
language in most cases because Haskell is very library based.

I didn't cherry-pick btw, I just went to the first benchmark which included
community-written snippets of both Go and Haskell I could think of. Cherry-
picking would be me intentionally skipping over examples such as the one you
provided and instead posting my example.

~~~
oscargrouch
I´ve provided the links as a example of cherry-pick from my part.. showing
that its easy to come with something that looks good..

FP tend to be more expressive, and make you feel more powerful.. but they also
tend to be more complex and more verbose.. thats the price of power.. it's the
trade of

I think its not enough to show something simple, but with the real complexity
hidden in some hidden layers..

Also if line of code is the measure of simplicity the Brainfuck language would
win everything.. but it doesnt mean you can read what the code express with
less effort..

Nothing against Haskell in particular, only that it doesnt shine when the
matter is simplicity..

Expressiveness.. Abstraction power.. shure.... but that was not the original
point i was making in the original article

~~~
igouy
>>I´ve provided the links as a example of cherry-pick<<

And they make curious examples of code-size when -- "Each program must
implement 4 separate functions / procedures / methods like the C# program."

------
jvermillard
I'm reading that while doing my daily Java code and I stumble on this error :

Bound mismatch: The generic method immutableEnumSet(Iterable<E>) of type Sets
is not applicable for the arguments (Integer). The inferred type Integer is
not a valid substitute for the bounded parameter <E extends Enum<E>>

Sorry for advanced type systems but I really want to go back hacking Go code
:)

~~~
KMag
Who holds Java up as a poster child for advanced type systems? You've mad an
unintentional straw man argument.

In this particular case, you're using an Integer where a subclass of Enum is
called for. The value you're passing may very well be a valid value for the
Enum you're trying to use. You may be doing something silly, or it's possible
that this is an artifact of generics being bolted on to Java after the fact.
In this case, it may be possible for the compiler to infer which Enum you
really wanted and insert a runtime check and cast, but then it would be doing
you a disservice by silently inserting an opportunity for a runtime error.

I'm not advocating Java's type system, but be glad Java at least has typesafe
enums. Every couple of years (across several employers) I run into a very bad
bug due to two different return code enums happening to use either 0 or 1 as
their "ok" values, and enums of one type being silently cast to the other,
resulting in silently okay behavior in the common case and spectacularly bad
error recovery in corner cases. Just today I fixed an error where someone had
designed an API where an enum of one type was passed to a function needing an
enum of a different type, without any translation. Some users had gotten
correct behavior by passing an enum of the incorrect type to the API, and some
users had followed the API documentation and gotten wrong behavior.

------
vorg
I've recently tryed out Go for its Unicode integration. What I liked at first
sight...

* the indexing makes a map[something]boolean act like a set. Sets and maps are so similar it always felt wrong for them to be two separate constructs.

* making exported functions/vars/etc begin with a capital letter. When naming important stuff, it's a relief not worrying about naming conflicts with keywords. When naming locals, just use _i,j,p,q,p2_ anyway.

* using _defer_ and _recover_ instead of _catch_ and _finally_. The _catch_ clause is really two functionalities rolled into one and using _defer_ and _recover_ decomplects them.

Other languages should copy those.

What I'm concerned about...

* printf and regex notation are used so much they're really part of the language, but have an entirely separate syntax embedded within strings which must be learnt. But unlike the rest of Go's syntax, they're unintuitive, especially regexes for Unicode. Unicode is meant to be one of Go's strengths. I understand quick parsing is one of Go's primary reason's for existing, but the regex and printf notations could have been cleaner. When you think about it, why are the arithmetic and bitwise operators generally part of a language's primary syntax, but string matching and formatting delegated to sub-languages?

* statements, like if/else and ++/\--, don't return values in Go which is hard to get used to. I understand making statements generally be shorter so the code looks good after running through gofmt could have motivated this.

Overall, I think Go's a systems language intended for quick parsing and
eliminating C++'s complexity, and the author's comparing it to languages with
far higher level constructs. The correct solution is for people to implement
languages like Haskell and Clojure in Go, making them execute as fast as
possible.

~~~
wyager
>The correct solution is for people to implement languages like Haskell and
Clojure in Go, making them execute as fast as possible.

Uh, what? Haskell already compiles to native code, and is faster than Go in
many cases.

Also, if you were implementing a programming language, there are much better
languages to do it in than Go.

~~~
stcredzero
Depends on your particular value of "better." If you're optimizing for
programmer time, writing a language in Go is not a bad tactic. You get out of
having to write your own GC, you can incorporate a few nice concurrency
features with little effort, and you still get pretty good performance.
(Admittedly, far from the best performance, though.)

~~~
Peaker
Writing a language in Go, which doesn't have sum types won't be fun.

Also, if you want to implement a compiler, and not an interpreter, the host
language having GC is not very useful to get GC.

Haskell is going to make working with ASTs much easier and safer. It also has
a superset of the concurrency features of Go.

~~~
zik
I'm writing a language in Go. It is fun. Sum types (and ASTs) are implemented
with interfaces which provide a pretty clean and type safe way of doing this.

~~~
Peaker
Can you show some small example encoding of an AST in Go?

~~~
stcredzero
The code for Twik has a simple AST in it. Of course, it's Lisp, so it's
perhaps too simple.

[http://blog.labix.org/2013/07/16/twik-a-tiny-language-for-
go](http://blog.labix.org/2013/07/16/twik-a-tiny-language-for-go)

~~~
Peaker
The duplication there in each AST node kinda hurts the eyes :)

Also, the type-switch on interface {} is ugly.

Consider how an AST looks like in Haskell:

    
    
      data AST
        = LiteralInt Int
        | LiteralFloat Float
        | List [AST]
        ...
    

And then an eval looks like:

    
    
      case ast of
        LiteralInt int -> ...
        LiteralFloat float -> ...
        List nodes -> ...
    

and it is safe, rather than interface{}, you get the AST type and you get
exhaustiveness checking that you covered all cases.

Also, if you add an annotation to each AST element (e.g: inferred types), you
can very easily and safely map over them, etc.

------
xxchan
Every single thing listed in the article can be added to Go at any point,
since it currently has a very minimal feature set. Once something like
generics are added, they have to support it until the end of time or risk
having an unstable API like Rust did for a while there.

~~~
vorg
> Once something like generics are added

The <> notation in _id <T>(item_ for generic bracketing is harder to read than
other bracketing symbols, e.g. () [] and {}. Unlike those others, angles are
used for comparison ops and arrow heads also. If Go ever introduces generics,
the Scala-like [] notation looks cleaner and would fit into Go's existing
grammar.

~~~
nanofortnight
The only point of confusion I can see out of using [] is:

    
    
        type Foo[T] T
    

versus

    
    
        type Foo []int
    

for example.

    
    
        type Array[T] []T

------
kazuho
Type inference and operator overloading often leads to less-readable code.

IMO when evaluating programming languages, we should not only consider
writability but also its readability. This is especially true if many
engineers are going to be involved in the development.

It is good to have type inference and operator overloading in terms of
writability. Nobody wants to type verbose code.

OTOH some verbosity within the source code helps reading the code. And type
information (which type inference and operator overloading tries to hide) is
one of them.

So I can respect Go's decision not to support operator overloading / only
support some part of type inference.

------
whocares
Sheesh. A laundry list of issues. So why use Go at all? Quit yer whining and
just use Haskell or whatever. Go works for some people, not for others. Why
hang around and complain? Move on, use something else.

~~~
wyager
>So why use Go at all? Quit yer whining and just use Haskell or whatever.
[...] Why hang around and complain?

Well, I think it's a good idea to get people thinking about programming
language design. Sometimes it's really hard to tell what's wrong with
something if you don't know about anything better.

------
ludamad
As a love-hater of C++, Go did indeed feel like nothing new under the sun.

------
mholt
The author is using a sharp blade as his implication for _good_ : features
that make a language more complex.

If the added complexity is "good" to you, then fine. In modern systems,
simplicity is a powerful debugger.

Adding all those features that the author talks about -- "Constraint-based
Generics and Parametric Polymorphism", "Algebraic Types and Type-safe Failure
Modes", and "Pattern Matching and Compound Expressions" \-- even if useful,
would defeat the purpose of Go.

~~~
autodidakto
People use the word "complex" in different ways. Do you mean number of
features? Do you mean the size of the compiler? By some measures, operator
overloading adds complexity; By another measure, it add simplicity.

~~~
mholt
I use complexity here the same way the Go community does: it is whatever its
authors and users consider it to be. So chances are, if it slows down
compilation, it's complexity. If it adds significantly to the grammar or
syntax or keyword list, it's complexity. If it does things that can already be
done, just differently, it's complexity. If it makes code less clear, it's
probably complexity. (You get the idea.)

~~~
twic
> I use complexity here the same way the Go community does

To mean anything Go doesn't currently have?

------
autodidakto
Possible error:

>If you want to modify a data structure, you have to create an entirely new
data structure with the correct changes. This is still pretty fast because
Haskell uses lazy evaluation.

I believe the issue is persistent data structures -- the new data structure
"remembers" the old one (instead of recreating it) and records changes.
(Clojure works like this as well) -- and not lazy evaluation.

~~~
wyager
I meant that, despite the fact that adding an element to a tree in Haskell
naively constitutes "making a new tree", Haskell usually doesn't end up
actually make a whole new tree (because of lazy evaluation), so trees are
still really fast.

~~~
chris_j
That's still not quite right. It's not lazy evaluation that makes it feasible
to "make a new tree" in Haskell (although it doesn't hurt), it's the fact that
the new tree shares most of the state of the old tree and the data structures
make the asymptotic time complexity of both update and retrieval very close to
(but not quite) O(n).

If you haven't looked at persistent data structures yet then I'd definitely
recommend doing so because they are fascinating. A few people have written
about Clojure's data structures and the following article looks like it gives
a good introduction:

[http://hypirion.com/musings/understanding-persistent-
vector-...](http://hypirion.com/musings/understanding-persistent-vector-pt-1)

~~~
wyager
That's fair. I'll fix the article. Thanks!

------
eikenberry
I agree with almost all points but still think Go is a good language for the
long term because it follows in the tradition of worse-is-better design. And
while worse-is-better produces systems with a lot of warts, it seems to work
better in practice than systems that do it 'right'.

------
exabrial
"A Good Solution: Operators are Functions"

NO. No. No.

Allowing users to change the language specification and side effects on a per-
file, per-project, per-anything basis is a terrible terrible terrible idea.

"This is all covered in Knuth, and we don't have time to go over it again."

~~~
sparkie
Perhaps you've not used Haskell?

There are no "built in" operators in Haskell, other than " ", which is
function application, and which cannot be overridden. Additionally, any "infix
operator" can be used in prefix form, by surrounding it in parens - and any
function name can be used in infix form, by surrounding it in `backticks`.

All other operators, like +, -, $, <, <$>, >>=, are defined in libraries
(specifically in Prelude, the "standard library") - these operators cannot be
overloaded in ad-hoc manner as operator overloading is done in other languages
(bar qualified module imports) - to make use of one of these operators you
must implement an instance of the class which contains it, such as Num for +,
-. There's also the requirement to implement fromInteger, signum, negate, etc
in Num. (If you can't think of a valid "fromInteger" implementation for your
custom type, it's obviously not a Num). Also, some classes have associated
laws which should prevent you from using them incorrectly

Admittedly the class/instance scenario could be improved with further checking
of these "laws", but that would basically require a full theorem prover baked
into the language - something that Haskell will probably get sooner or later.

There's still what I would consider "operator abuse" in Haskell - it's not
that of overloading existing operators, but that of introducing new operator
aliases for virtually every function in your library, as
[http://hackage.haskell.org/package/lens-4.1.2/docs/Control-L...](http://hackage.haskell.org/package/lens-4.1.2/docs/Control-
Lens-Operators.html) does (yuck).

A nice convenient feature of Haskell is that you can scrap the official
Prelude library (-XNoImplicitPrelude) and roll your own, as some others have
done (e.g, [http://hackage.haskell.org/package/classy-
prelude](http://hackage.haskell.org/package/classy-prelude)). This allows you
to effectively "clean up" some of the warts from the early design of the
language, so it can continually improve - rather than needing to invent a
whole new language when we decide it's not what we want. Num is an example of
a class which gets lots of stick, because we'd often like to implement either
of addition or mulitplication, but not both.

------
xkarga00
"Go does not support immutability declarations."

Doesn't Go have constants (const) which are immutable? What am i getting wrong
here?

~~~
dbaupp
Those are just compile time constants, you cannot have a value computed at
runtime stored in an immutable variable (i.e. one the compiler will complain
about if you attempt to mutate it, to assist with program correctness).

------
ChikkaChiChi
Posting articles about how Go sucks because it lacks generics and operator
overloading has become the "I'm going to be different and grow a moustache" of
the programming set.

Go is a tool in your kit like any other language. You can't blame the
architects for not providing an end all be all solution for every person's
needs 100% of the time.

------
pilif
_> I like Go. I use it for a number of things (including this blog)_

and yet it goes down once it is posted on HN. I have had some articles of mine
end up on HN and I never went down, even when my blog was still WordPress
hosted on a machine of mine.

This is probably more of a shortcoming of the various cloud providers than of
the language/environment itself I guess.

~~~
IgorPartola
Not a great metric. My blog once got unexpectedly slash dotted (ended up on
Reddit and HN front pages for a day; silly enough the post was the one I put
the least work into by far and the idea was not even mine). I was at the time
running WordPress with WP Supercache using only right PHP workers. The site
never had a hiccup. Ergo PHP is the best language out there? :)

In reality, do use something like WP Supercache. It will save your hide.

------
jackielii
I see you wrote "It would be nice"

------
lein
kudos, i totally agree with your arguments.

------
personZ
We've seen this same article re-written countless ways. Seriously, this is
(intentionally or not) a rewording of every existing criticism of Go, by
people who complain that it isn't a language that it isn't.

No, Go's solution to generics is _not_ interface{}. The moment you say that,
_you have lost_. You are trying to fight Go and make it a language that it is
not.

Always remarkable that such critiques always focus on the utterly trivial,
while absolutely ignoring things like concurrency or composing complex
systems. As always, the color of the shed is what the laymen want to argue
about.

~~~
Daishiman
But the fact that Go proponents don't _actually have solutions_ to those
problems is an issue.

How do you make a custom, generic data structure without syntax overhead? I
have not seen any counter proposal to this aside from "maps should be enough
for everybody".

How do you avoid the noise from not having operator overloading or a similar
alternative? This, again, goes unadressed.

What are the succint alternatives to functional abstractions for quickly
processing collections of data?

"Just use a channel" doesn't really ring like a reasonable alternative to
these questions.

~~~
personZ
_How do you make a custom, generic data structure without syntax overhead?_

In real-world code, the need for generic data structures is shockingly
uncommon. It really is. This requirement exaggeration comes about by people
acting as language tourists, building amorphous code of uncertain purpose,
where things like "I'm going to sum up a bunch of unknown objects" seems like
a serious need.

For most people who find Go to be a compelling language, it excels for
practical, real-world needs.

~~~
gnuvince
Notice how you're not giving an answer to Daishiman's question, merely
downplaying the importance of having general data structures, though I'm sure
you're quite happy that slices, maps and channels can be parametrized by the
type of data they contain. Saying that general data structures are uncommon in
the "real world" sounds like an example of Sapir-Whorf. I use Java at work and
OCaml for a personal project and in both of them, having data structures that
can be parametrized is very helpful. For instance, writing an AST with a type
parameter allows to go from a AST<String> (generated by the parser) where ids
are strings to a AST<Symbol> where a node's id is now a symbol (generated
during semantic analysis).

------
zkirill
Haskell (with third party library)

[http://snapframework.com/docs/tutorials/snap-
api](http://snapframework.com/docs/tutorials/snap-api)

main :: IO () main = quickHttpServe site

site :: Snap () site = ifTop (writeBS "hello world") <|> route [ ("foo",
writeBS "bar") , ("echo/:echoparam", echoHandler) ] <|> dir "static"
(serveDirectory ".")

echoHandler :: Snap () echoHandler = do param <\- getParam "echoparam" maybe
(writeBS "must specify echo/param in URL") writeBS param

\-----

Rust (with third party library)

[https://github.com/chris-morgan/rust-
http/blob/master/src/ex...](https://github.com/chris-morgan/rust-
http/blob/master/src/examples/server/hello_world/main.rs)

//! A very simple HTTP server which responds with the plain text "Hello,
World!" to every request.

#![crate_id = "hello_world"]

extern crate time; extern crate http;

use std::io::net::ip::{SocketAddr, Ipv4Addr}; use std::io::Writer;

use http::server::{Config, Server, Request, ResponseWriter}; use
http::headers::content_type::MediaType;

#[deriving(Clone)] struct HelloWorldServer;

impl Server for HelloWorldServer { fn get_config(&self) -> Config { Config {
bind_address: SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 8001 } } }

    
    
        fn handle_request(&self, _r: Request, w: &mut ResponseWriter) {
            w.headers.date = Some(time::now_utc());
            w.headers.content_length = Some(14);
            w.headers.content_type = Some(MediaType {
                type_: String::from_str("text"),
                subtype: String::from_str("plain"),
                parameters: vec!((String::from_str("charset"), String::from_str("UTF-8")))
            });
            w.headers.server = Some(String::from_str("Example"));
    
            w.write(b"Hello, World!\n").unwrap();
        }

}

fn main() { HelloWorldServer.serve_forever(); }

\-----

Go (native)

package main

import ( "fmt" "net/http" )

func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hi
there, I love %s!", r.URL.Path[1:]) }

func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil)
}

\-----

Yeah, ok.

~~~
woah
Node.js, native:

    
    
        var http = require('http');
        http.createServer(function (req, res) {
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end('Hello World\n');
        }).listen(1337, '127.0.0.1');
    

What's the point here?

