
Everyday hassles in Go - friendly_chap
http://crufter.com/2014/12/01/everyday-hassles-in-go/
======
tarikjn
I couldn't agree more with this article.

I did some Go a year ago and liked it. Then coming back to it a year later
after having done some functional programming in Clojure, it's not just the
lack of generics that disrupt my flow but also having to think about all sorts
of imperative programming details like naming and creating variables and scope
placements in cases that would otherwise be unnecessary in a functional
language.

I feel like I have been tainted by functional programming, and now Go feel
like a joyless programming language which inevitably affect productivity.

I am looking for a replacement programming language to solve small problems
for which Python/Ruby would have traditionally been used, good file system,
stream and networking APIs, but concurrency as a first class citizen, garbage
collected, fast startup time. Doesn't need to be good at long running
programs, but it would be great if it can evolve to play a repeat role in a
large system/process without the hassles of having to duplicate every written
line of code into tests to prevent small changes from breaking things.

I am considering giving Haskell a try, but wondering if it might be overkill.

Any suggestions?

~~~
danidiaz
I think Haskell is a possible alternative to Go/Python/Ruby for these kinds of
tasks.

But nothing's perfect. Some possible annoyances you might encounter:

* Much of the functionality required to be a valid "Python alternative" is not on the base packages. You'll have to install extra packages much sooner than with Python. And you'll have to know what packages to install.

* Despite having proper sum types, most I/O libraries in Haskell rely on exceptions for signalling errors. And Haskell has no proper stack traces. If you are not careful, you might find yourself pining for Go's multiple return values.

(Some would add "having to use the IO monad" to the list of annoyances, but I
think it actually helps.)

~~~
bstpierre
I found ocaml to be a more flexible alternative -- I can sprinkle printf for
quick debugging without having to alter types. (I think this is what you mean
by "having to use the IO monad". I didn't work with haskell much, so it's
possible there's an easy way around this there.) And stack traces in ocaml are
reasonable, though not quite as verbose as python's.

But your point about needing to install extra packages stands for either
language.

~~~
gnuvince
OCaml is currently my language of choice. I love functional programming, been
doing it for a while now, but it's nice to know that if I read an algorithm in
a textbook, I can just implement it as-is instead of doing a translation to be
purely functional (and sometimes having to fight to get back the asymptotic
complexity). The module system is also absolutely great, and should be copied
shamelessly by more languages.

~~~
Peaker
Haskell doesn't ban imperative programming, it simply distinguishes it in the
types.

~~~
gnuvince
True, what I mean is that I can be "messy" in OCaml for the sake of
expediency. In principle, I prefer Haskell's clean approach, in practice I'm a
bad programmer who sometimes does naughty things.

~~~
Peaker
Well, Haskell does force you to go and update your types about your effects,
and fix the use sites to do proper lifting.

I don't feel this costs much, though, so even in quick&dirty mode, I feel that
Haskell is quite productive enough.

------
bsaul
Although i'm still very unsure about rob pike's argument that go don't need
generics since it has interface, a recent experience :

After having implemented a mini web services in go and being fed up with its
limited type system, i decided to stop coding in go and start recoding my
project in java using what is often advertized here as the most minimal
framework : dropwizard.

Well, i downloaded the framework, configured maven, started the hello world
tutorial, and quickly found myself dealing with 2 xml files, 1 yaml file,
multiple classe files ecerywhere, and couldn't get the whole stuff work
immediately.

Then i realized that my golang implementation was already working, and that i
completely understood my code and its potential performance characteristic,
without having to have deep knowledge about the jvm internals, things like
sevlet containers, or how the whole stack would deal with my global variables
in a multi-threaded context. All my code was just one simple program.

My temporary conclusion right now is that golang type system is indeed
extremely shite, but that if what you need is to develop simple yet efficient
web services, it doesn't matter that much.

~~~
lmm
I don't think Java will get many defenders here. But try that in Scala, with
Spray. There're a couple of lines of boilerplate to create the actor system,
but that's about all. And you get a system that's flexible enough to let you
write route definitions that look like a config file, but everything's
typesafe. Your routes are just functions, you can refactor them like ordinary
code. So too are the kind of "cross-cutting concerns" like authentication or
async calls that you'd have to either have built into your framework, or use
some kind of "magic" (AOP, monkeypatching) to add to all the methods where
they were needed.
[https://github.com/spray/spray/tree/release/1.1/examples/spr...](https://github.com/spray/spray/tree/release/1.1/examples/spray-
can/simple-http-server/src/main/scala/spray/examples)

~~~
kasey_junk
> use some kind of "magic"

No it doesn't use magic, but it does take a language that is burdened with a
complex type system and throws it out the window.

You also are glossing over a huge host of complexity around build/deployment.
What jvm are you targeting? Is it on the servers you are deploying to? Are you
going to make a fat jar? If not, how are you doing dependency resolution? Is
your build artifact something that sbt does out of the box or slightly
different (lord help you if it is)? Are you going to do ivy, s3, or maven
resolution?

All of that complexity falls away with golang and this is a conscious choice
of the golang team. I have lots of things I love about Scala and lots of
things I hate about golang, but from a "get simple web service out the door in
a scalable way" golang wins hands down.

~~~
lmm
> No it doesn't use magic, but it does take a language that is burdened with a
> complex type system and throws it out the window.

What are you talking about? Spray works hand-in-glove with the Scala type
system; would, in fact, be impossible in a language without it.

> You also are glossing over a huge host of complexity around
> build/deployment. What jvm are you targeting? Is it on the servers you are
> deploying to? Are you going to make a fat jar? If not, how are you doing
> dependency resolution? Is your build artifact something that sbt does out of
> the box or slightly different (lord help you if it is)? Are you going to do
> ivy, s3, or maven resolution?

If you're just playing around, you push the button in your IDE and it runs. If
you're a serious business you make these decisions once and reuse them in
every project. (To answer your questions explicitly: the default is to build
for java 1.6, which has been on servers since before go even existed; if
you're building for a newer version it's because you know what you're doing.
My personal choice would be the maven appassembler plugin, but using the shade
plugin to make a fat jar works fine too. I wouldn't touch SBT, it's too
complex).

Yes, you do have to make some choices, put a bit of effort into deployment.
But I think that's a necessary cost, because it's the only way to allow
tooling to evolve. Imagine if ant had been built into the JVM back in 2000;
maven would never have been able to replace it, so we'd be stuck with it
forever. Look at the Python standard library; when Python first got big, it
was this great selling point full of really useful tools. Now, it's where
modules go to die, because things that are built into the language can't
evolve at the same place as things that are outside it. I fear exactly the
same thing will happen to the Go tooling (though of course, we won't know one
way or the other for ten years). If I can think of one language that really
emphasised an "easy deployment model", it's PHP. Not only did that approach
lead to terrible deployment practices, but now that the landscape has shifted
and Apache is no longer universal the way it once was, it's not even
particularly easy to deploy PHP any more.

~~~
kasey_junk
> What are you talking about? Spray works hand-in-glove with the Scala type
> system; would, in fact, be impossible in a language without it.

In the example you linked to:
[https://github.com/spray/spray/blob/release/1.1/examples/spr...](https://github.com/spray/spray/blob/release/1.1/examples/spray-
can/simple-http-server/src/main/scala/spray/examples/DemoService.scala#L19)

The "main" function dispatch is not type safe (nor can it be given Spray's
reliance on the current akka type model).

My experience is that anything that relies on akka ends up throwing out a huge
majority of useful type safety because it is hard to minimize it. A bunch of
the advantages you mention around AOP and not monkey patching are due to this.

~~~
lmm
Ah, my bad.
[https://github.com/spray/spray/tree/release/1.1/examples/spr...](https://github.com/spray/spray/tree/release/1.1/examples/spray-
routing/on-spray-can/src/main/scala/spray/examples) (or even
[https://github.com/spray/spray/blob/release/1.1/examples/spr...](https://github.com/spray/spray/blob/release/1.1/examples/spray-
routing/simple-routing-app/src/main/scala/spray/examples/Main.scala) if you
want the very simple case) are better examples, that use the lovely type-safe
routing DSL.

------
Animats
The article author points out, correctly, that Go code has far too much
"interface{}" in it. That's an indication of real need for more power in the
type system.

Generics may be overkill. Parameterized types may be enough. The difference is
that you have to explicitly instantiate a parameterized type. Generic
functions and classes get instantiated implicitly when used, which gets
complicated. (See C++'s Boost.)

Go already has built-in parameterized types - "map" and "chan". Those are
instantiated with "make", as with

    
    
        p := make(map[string]int)
    

That's an instantiation of a parameterized type in Go. Go already has a full
parameterized type mechanism. It's just that users can't define new
parameterized types - all you get are "chan" and "map".

For parameterized types, struct definitions would need type parameters. A
parameterized type would later be instantiated explicitly in a type
declaration, with the parameters filled in and a new name given to the
generated type. This is more explicit and less automatic than generics.
There's no automatic specialization, as in C++.

This is enough that you can write, say, a binary tree library once and use it
for multiple purposes. It's not enough to write C++'s Boost. That seems in
keeping with the design of Go.

~~~
burntsushi
That's not really the full story though. The parameterized type isn't just
`map[K]V`, it's more than that. The type `K` needs to be hashable, and not all
types support that. e.g., slices do not:
[http://play.golang.org/p/IKp_I25NW2](http://play.golang.org/p/IKp_I25NW2) It
gets more complicated then that too, because composite types can be used for
keys, but only if the type does not contain any non-hashable type.

Similarly, if you're going to build a binary tree data structure, then you
probably need a way to compare elements. How do you express that constraint?

Standard ML has a similar limitation, which resulted in the presence of an
`eqtype`, which is basically a way of saying, "any type T that can be compared
for equality." But of course, ML has its modules...

So I'd argue that type parameterization alone doesn't really buy you much. You
might say: well, we need bounded polymorphism then. OK. How do you define
bounds? Maybe you figure out a way to abuse interfaces (but a bound isn't a
type while an interface _is_ a type), or you add a new way of specifying
interfaces to the language.

The road to generics is long, complex and fraught with trade offs. Blessing a
few key parametric types is a really interesting compromise, and I believe
it's one that was absent in a similar practical manner in pre-generics Java
and C++.

~~~
Animats
True. A useful question to ask is this: what's the minimal parametric type
design that would allow writing "map" and "chan" within Go? (Ignoring, of
course, that "map" and "chan" both have special syntax; map has a "[]"
operator, and "chan" has "select')

If we needed to write, say, "thread-safe map", or "network chan", with type
parameterization, that should be possible.

------
kator
"Every computer language is an imperfect way of describing a potential
solution to a poorly understood problem" \-- Me

I find it interesting when people wax poetic about how one language is better
than another and how if we just did x, y or z then we'd have this perfect
solution.

That said I appreciate the author's write-up of some challenges with Go. In
the end the reality of any "product" is building what the user's need not what
they ask for. That said I do worry that the Generics argument seems to be
slowly approaching a religious war that will distract people from the other
enjoyable aspects of Go. Worse yet it may calcify the Go development team in a
way that will keep them from addressing the basic issues that generics might
help solve. Either way Go is just another computer language and I'm happy to
use it often to solve problems I'm working on. That said I use a lot of
computer languages every week when I'm working on stuff, none of them are
perfect and my solutions to the underlying problems are fragile and constantly
evolving as the problems unfold. This is the nature of our business, to
constantly do battle with poorly understood problems using imperfect tools in
a world where we are fooled into thinking everything is black and white
because at the core of our technology everything is a 0 or a 1.

"One Computer Language to bring them all and in the darkness bind them".. LOL

~~~
dons
Languages may not be perfect, but language features can be compared, and some
features are strictly more powerful than others.

There is scope for legitimate criticism of language design based on the
expressive power^1 of their features.

In this case, generics would make Go strictly more expressive, as, without it
you must write O(n) more code or perform a global refactoring to simulate it.

1\.
[http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.4...](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.4656)

~~~
skybrian
You can compare language features this way, but it won't tell you which
language is a better fit for a particular problem. JSON is strictly less
powerful than JavaScript and it's easy to come up with examples of data that
can be better compressed in JavaScript. But for what we use JSON for, not
being Turing complete is a feature.

When programming in an intentionally restricted language, the question is
whether the limitation in power is put to good use in some other way.

------
im_dario
This article has some good points but I'm going straight for the first one: I
don't miss generics.

Indeed, they are useful. I use them in Java every time I find a good reason to
do it. So, I should say in another way: I don't miss generics in Go.

Given my experience [0] in Go in the last years, I realized that if you miss
generics in Go, your code is trying to cope with too much.

I didn't miss them when I developed Mergo [1], a simple library to merge
structs. I think we all agree if we should rewrite this in Java (or other
language with similar type system) we would use generics at some point.

[0]
[https://github.com/imdario?tab=repositories](https://github.com/imdario?tab=repositories)

[1] [https://github.com/imdario/mergo](https://github.com/imdario/mergo)

~~~
friendly_chap
Did you miss it when you were working on larger codebases? The library you
mentioned clocks at 500 LOC, and while the LOC metric is pretty inaccurate, it
takes a lot more time to really miss language features.

~~~
im_dario
No. I didn't. I have around 2K public LOC in several projects (I can't check
now my private projects, I guess I had typed more than that) but I would trust
better NateDad's experience.

I have two years of sustained Go usage (since end of 2012) and never missed
them.

------
brianolson
The first point "no generics, no code reuse" is the best one. Go really needs
generics/templates/macros or _something_. It doesn't even have subclassing,
although you can kinda extend a type if you only use its public interface.
I've been tempted to see if I could reasonably use the "text/template" package
and feed that back into the Go compiler to achieve this.

The rest is mostly a Haskell fanboy whining that Go isn't Haskell.

~~~
vardump
Best joke in the industry:

"Code reuse!"

Cracks me up every time. ;-)

We fall for those words over and over again. But in reality, sadly, reuse
almost never happens. Maybe one day, maybe even in the next project...

~~~
falcolas
Like c's 'libc', or C++ boost, or pythons 'requests', or...

Lots of code reuse there that depends on generics.

~~~
vardump
Yes, libraries get used. That's their point. My point was, that very little of
application code ever gets reused, although we somehow always think it will.

~~~
falcolas
And most people are asking for generics to write libraries which don't rely on
unblocking interfaces.

Even in the process of writing a single program, there are a number of times
where you reuse certain bits of logic, and having generics makes it easier to
refactor those into a single function, instead of duplicate code with multiple
types (or overly generic types).

------
zzzcpan
Well, I want Go to have refcounting instead of current GC, so my programs
could guaranty latency. Single-threaded runtime, without all the locks, slow
channels, races and so on, because there is no point in so much overhead and
complexity for the majority of programs. More consistency couldn't hurt, so I
wouldn't have to assign anonymous function to a variable just to return it.
Fast regular expressions compiled by the language compiler into a native code
could improve matching, parsing and validating consistency, instead of writing
lots of loops everywhere. Compiler warnings instead of errors on unused
variables, imports, etc. to allow faster prototyping. Better testing culture
with meaningful test names and line numbers with actual failed tests. Table
testing suggested by Golang team doesn't even support that with the means of
standard library, you have to use your own runtime.Caller(1) wrapper.

But generics? "Safer" type system? No, that's unnecessary complexity. As
others pointed out, we already have Haskell and Rust for all those things.

~~~
vardump
You don't like locks, but you want refcounting that requires a large number of
atomic ops for acquire/release semantics and thus scales like crap? Ah, you
really mean, single threaded as in no other threads? Well... ok. But that'd
make Golang a lot less useful.

For compiled regular expressions: just use PCRE library for compiled regular
expressions. I bet there's already some library that does it for you.

Maybe you should take a look at Lua and especially LuaJIT [1]. You might like
it. Lua(JIT) is single threaded, but provides co-routines [2] as a language
construct. Performance is about same as Golang, sometimes faster, sometimes a
bit slower. Lua has GC, but you can control latency by controlled GC
invocations. It can be made pretty predictable, a lot of current AAA games use
Lua internally.

LuaJIT's FFI [3] is excellent, calling native C libraries is a breeze, very
easy and requires no bridge libraries or code.

[1]: [http://luajit.org/](http://luajit.org/)

[2]: [http://www.lua.org/pil/9.1.html](http://www.lua.org/pil/9.1.html)

[3]: [http://luajit.org/ext_ffi.html](http://luajit.org/ext_ffi.html)

~~~
zzzcpan
Refcounting and a single threaded runtime, instead of GC and a multithreaded
one.

By compiled regexpes, I meant replacing things like bytes.Equal(foo, "qwe")
with things like foo.m(`^qwe$`) or even foo.m/^qwe$/ that have the same
performance. All the loops that scan through slices could benefit from it,
golang has a lot of them. Plus more overall matching/scanning consistency,
that should lead to fewer mistakes.

~~~
theseoafs
Go would need to be completely rearchitected in order to move to a single-
threaded runtime.

~~~
im_dario
As far as I know, it should be enough with runtime.GOMAXPROCS(1) to run as
single-threaded.

~~~
yepguy
No, the runtime will still create new threads whenever you call a C function
or syscall.

------
jmnicolas
If the author reads this, I think he's confusing "quiet" and "quite".

------
nhaehnle
_> For those who are bothered about the exponential algorithmic complexity of
nub_

There is nothing exponential about nub. Given only equality comparisons,
detecting and eliminating duplicate elements requires quadratic running time.

~~~
friendly_chap
You are right! Others noted that mistake as well.

------
zachgersh
Wanted to say, you can potentially do better than this in go for arbitrary
JSON:

type JSON map[string]interface{}

Go ahead and use the json.RawMessage type instead:

[http://golang.org/pkg/encoding/json/#RawMessage](http://golang.org/pkg/encoding/json/#RawMessage)

Not even going to wade into the lack of generics war, still very happy without
them and going on a full year of Go usage.

~~~
chimeracoder
Yeah, I almost never use map[string]interface{} when unmarshalling JSON. Most
JSON has a static structure (and I'd argue that any JSON that doesn't is
misbehaving). If the structure is static, you can simply use a struct that
represents this[0].

If it doesn't, then you just use json.RawMessage to defer unmarshalling, but
you can still dispatch to a finite number of structs that represent the
universe of possible responses.

[0] And no need to write it all out by hand - if you have a single example
JSON response, you can generate it automatically:
[https://github.com/ChimeraCoder/gojson](https://github.com/ChimeraCoder/gojson)

------
gws
Every time I read an article criticizing Go I end up appreciating it even
more.

Maybe it's because I've never felt the need to use generics and in all these
articles the examples they give are functions of a few lines that would be
quicker to write 2-3 times for different types than remembering generic
syntax.

Maybe it's because they exalt one line functional functions over a nice,
simple, easy to read FOR loop when the former are so difficult to read, figure
out what they really do, what is the performance cost, debug...

Maybe it's because of the bogus examples they give like criticizing

> file, _ = os.Open("file.txt")

> file.Chmod(777)

for not handling explicitly a possible error when it's just because the
example is ill written and the proper code is

> file, err = os.Open("file.txt")

> if err != nil {

> ... handle error ...

And here I stopped reading the article, it's always the same arguments over
and over: more elegant and complex code vs. the un(cool) but oh my, so much
simpler Go code.

What I find really amusing though it's how people are so smug in their
writing, pointing out the "obvious errors" (billion dollar mistakes!) that the
Go authors made and their "ignorance" of proven modern programming language
constructs they could implement in Go.

There are two possibilities here, pick your preferred one.

1) Pike, Thompson & co. made obvious errors in designing Go because of their
ignorance of programming languages and/or ineptitude

2) These bloggers claiming obvious errors in Go design don't really fully
understand the trade-offs involved in what they ask for and ignore the fact
that the Go authors have carefully thought about them and optimized
accordingly

I will go back to programming in Go, I'd take writing for loops all the time
vs. writing a one liner in Haskell and then agonize over using the lazy or
strict version of it :)

~~~
kasey_junk
I think there is a third option that you are ignoring. Pike, Thompson & Co.
are designing for a different problem set than the blog authors.

Golang seems to shine at very simple, concurrent tasks that can be passed from
one set of developers to another regardless of sophistication levels of those
teams. It seems to fail pretty miserably at making a single sophisticated
developer vastly more productive. It's what Java would have been if they'd
thrown away the write once/run anywhere goal and never gone down the J2EE box
canyon.

There is nothing wrong with either goal btw, they just are in some ways
opposites.

------
pothibo
One thing that is perhaps counter intuitive is how you do a lot of for loops
compared to other languages (Compiled/Statically typed included). Once you let
go your previous expectations, Go gets a lot nicer to use.

The amount of for loops you will write will also makes it obvious when you
start doing O(n^2) operations in your methods. Same can be said with variables
declaration and error verifications in Go. They are annoying at first, but
then you understand by reading your code the places where error can occur or
where you decided to ignore errors (By using _).

It's a different approach and it's refreshing.

~~~
NateDad
Yeah, it seems like a lot of the criticisms of go boil down to "I don't like
writing simple loops".

~~~
nine_k
Rather, I don't like to copy-paste and slightly modify simple loops over and
over again. Abstraction is a basic tool of programming; where has it gone?

~~~
NateDad
They're simple loops. You don't need to copy and paste, you just write them
out because it's just ridiculously simple logic. Just like you don't copy and
paste if statements.

~~~
rjberry
All loops encode simple logic? I don't think that's true.

Either way, a very good reason for not using loops is to make code more
readable. If I see a loop I have to run it inside my head to figure out what
the intent is, and if there's multiple things going on, that can take time. If
I see a 'map' I think "right, this is tranforming every element in this list
in this way"; if I see a filter, I think "right, this is removing elements in
this list that don't satisfy this property", if I see a groupBy, I think
"right, this is grouping elements in this list by this property", etc., etc.

Code isn't only more succinct, it's more human friendly.

~~~
ominous_prime
Expanding a map to a for loop is very simple. Yes it removes a shortcut
available when writing code, but when reading code I can consume that simple
chunk of logic in one mental bite just as easily as I can a call to map.

~~~
rjberry
Sure ... but if a loop is effectively doing a map, a filter, and a bunch of
other operations all at once? It's a lot quicker to figure out what's going on
if it's been written with combinators (once you're familiar with them) than if
it's the vanilla loop.

~~~
dangerlibrary
It can be a lot less performant to chain several combinators.

~~~
rjberry
If we assume the operations we're talking about take time linear in proportion
to the list, you've just gone from a * n time to b * n time, where b > a. Both
of these are still O(n) in Big O notation, which drops constants, because
constants unless they're very large or n is extremely large, tend to have
relatively little effect on the running time of an algorithm.

Choosing to write more verbose, difficult to decipher, difficult to maintain
code, under the claim that it will perform better, is not a good thing:
"premature optimisation is the root of all evil".

In practice, if this becomes an issue (which you find out through benchmarking
once you know there is a perf issue), most modern languages offer an easy way
to swap out your data-structure for a lazily evaluated one, which would then
perform the operations in one pass. Languages like Haskell or Clojure are lazy
to begin with, so do this by default.

~~~
dangerlibrary
My comment was carefully worded in order to denote that it is not true in all
cases.

Your Big O analysis is correct. However, in a real-world case the list could
be an iterator over a log file on disk. Then you really don't want to use
chained combinators, repeatedly returning to the disk and iterating over
gigabytes of data on a spinning plate.

And yeah, you could benchmark to figure that out, or you could use the right
tool for the job in the first place.

~~~
rjberry
Or you could use a library explicitly designed with these considerations in
mind, that offers the same powerful, familiar combinators, with resource
safety and speed. e.g.,

Machines in Haskell:
[https://hackage.haskell.org/package/machines](https://hackage.haskell.org/package/machines)

Scalaz Stream in Scala: [https://github.com/scalaz/scalaz-
stream](https://github.com/scalaz/scalaz-stream)

I've no doubt an equivalent exists for Clojure, too, although I'm not familiar
enough with the language to point you in the right direction.

One of the most amazing things about writing the IO parts of your program
using libraries like these is how easy they become to test. You don't have to
do any dependency injection nonsense, as your combinators work the same
regardless of whether they're actually connected to a network socket, a file,
or whether they're just consuming a data structure you've constructed in your
program. So writing unit tests is basically the same as testing any pure
function - you just feed a fixture in and test that what comes out is what you
expect.

I found this really useful when writing a football goal push notifications
service for a newspaper I work for. I realised that what the service was
essentially doing was consuming an event stream from one service, converting
it into a different kind of stream, and then sinking it into our service that
actually sent the notification to APNS & Google. The program and its tests
ended up being much more succinct than what I would normally write, and the
whole thing was fun, and took hardly any time.

I would say that is the right tool for the job.

~~~
dangerlibrary
In many cases, YAGNI.

This started as a conversation about for loops vs. chained combinators and
wound up with specialized libraries for IO. You're not wrong, but that's a lot
more work than a for loop to parse a log file efficiently.

------
TeeWEE
While the author has a lot of valid points, he forgets the goal of Go. Go was
designed as a simple language, that is fast, but has similar power to existing
dynamic languages such as python.

The tradeoff in these designs are to prevent tuple types, always keep using
structs, prevent using algebraic types over structs. Allow for nil, but try
prevent common Null errors etc.

Haskall is theoretical a much better language. But Golang was designed to be
practical & simple over being mathematically sound, and theoretical better.

~~~
rjberry
I dislike it when people say languages are either 'practical' or 'well
designed'. As if a language is only useful if it's not built on sound
mathematical ideas ...

Generics are not a particularly new idea and nobody who uses language that
implement them yearn for the days when they weren't around.

See Paul's post on the idea that 'worse is better':
[http://pchiusano.github.io/2014-10-13/worseisworse.html](http://pchiusano.github.io/2014-10-13/worseisworse.html)

~~~
jbooth
The point is that you reach an area where the mathematical ideas don't map
perfectly to the underlying functionality you're providing.

You can make your language "unsound" by violating those mathematical
principles, or you can make something like the I/O monad. It is indeed
"soundness" vs "practicality".

EDIT: Oh, and that link seems to have misunderstood the 'worse is better'
philosophy -- C++ isn't the champion of 'worse is better', C is.

~~~
rjberry
Paul explains fairly well why he thinks C++ is a good example of "worse is
better", including quotes from the language designer to that effect.

I think when you get to more complicated (or at least alien) abstractions like
the I/O Monad, it's not about whether it's practical to build software with
it. It's perfectly practical - I know people working at big investment banks
writing large scale software in Haskell, and they don't have any issue with
it. What it comes down to is whether the people you work with will understand
or want to invest the amount of time required to learn those abstractions. If
you're working with very talented programmers, that might be yes. With the
majority of programmers it is probably no.

But we're not talking about the I/O Monad. We're talking about generics, which
are not difficult to understand, and that Go lacks them is a shame, as it
means that whole layers of abstraction are not available to the programmer, so
they end up having to spend more time writing boilerplate.

~~~
jbooth
So he invested time in building up his straw-man before tearing it down?

The problem with being in such a rush to confirm your own worldview is that
you don't learn anything from anyone else's. The 'worse is better' philosophy
is about valuing simplicity over completeness, specifically in the context of
UNIX/C vs Lisp machines back in the 80s. How do you think most of those people
feel about, say, the STL?

~~~
rjberry
Not that it particularly matters whether we're talking about C++, as it's
rather tangential, the original essay by Richard P. Gabriel that coined the
term "Worse is Better" talks about C++ ([http://www.jwz.org/doc/worse-is-
better.html](http://www.jwz.org/doc/worse-is-better.html)):

    
    
      The good news is that in 1995 we will have a good operating system and programming language; the bad news is that they will be Unix and C++.
    

In his later essay on the same topic
([http://dreamsongs.com/Files/IsWorseReallyBetter.pdf](http://dreamsongs.com/Files/IsWorseReallyBetter.pdf))
he again talks about C++:

    
    
       In the computer world, the example of interest to JOOP readers is C++. Many would concede that languages like Smalltalk, Eiffel, and CLOS are vastly “better" in some sense than C++, but, because of its worse-is-better characteristics, the fortunes of object-oriented programming probably lie with C++.
    

Simplicity is more often simplicity of implementation than interface. Anyone
who's worked with the C standard library knows that it's anything but simple.
There's a lot of incidental complexity that is due to design inconsistencies.
The complex parts of a language like Haskell are the bits where the ideas
themselves are complicated, but the power you get for overcoming that initial
learning curve is very great.

------
NateDad
"Why Go is not Haskell"

~~~
friendly_chap
Author here. I think that is a very valid point. However, a lot of features I
mentioned are present in other imperative languages, like Rust. The Go authors
however picked other solutions I consider inferior, hence the article.

~~~
NateDad
It seems like Rust is what Go would become of you added all the Haskell-ness
to it. Which is fine, but we already have Rust. So... Maybe we should be
asking what Rust is missing from Go? Or if Rust is good enough, just use that.

~~~
lmm
Rust is missing garbage collection (which is by design, but IMO manual memory
management is the wrong hair shirt to wear), and maturity.

What I want is OCaml without people complaining about concurrency (which I
suspect is mostly FUD - how many people who choose go "for performance"
actually profile their programs? I get the sense there is this group of people
who think any high-level language is inherently as slow as Python). Or Haskell
without laziness (maybe Idris will eventually become this?), and perhaps with
a lower-friction approach to I/O. Or Scala with native compilation (again I
think complaints about this are probably mostly FUD, but if nothing else the
JVM startup latency is real). Or any of these three getting the hype that Go
gets, tbh.

~~~
jbooth
So, uh, what are you actually after? Just use Scala if that's what you want.
If you can't be bothered to manage memory in rust, then it seems hard to
believe that native compilation makes a difference for you.

Is it all just about the hype? Write code, ship products.

~~~
lmm
As I said, the JVM startup latency is a real issue. I'd like to be able to
write little command-line utilities in Scala (or a Scala-like language) and
not have to wait a second when I run them.

But yeah, I'm writing and shipping in Scala, and life is good, I shouldn't
care about people hyping what I don't like.

------
likeclockwork
"When the three of us [Thompson, Rob Pike, and Robert Griesemer] got started,
it was pure research. The three of us got together and decided that we hated
C++. [laughter] ... [Returning to Go,] we started off with the idea that all
three of us had to be talked into every feature in the language, so there was
no extraneous garbage put into the language for any reason."

------
lmm
Agree with a lot of this, but I find the lack of any punctuation one of the
most confusing things about Haskell code.

    
    
        Map String Any
    

Is that a function call? A type declaration? What's a parameter to what? The
go style

    
    
        map[string]interface{}
    

is, much as I love to hate on all things go, much clearer.

~~~
ufo
> Is that a function call, A type declaration?

If its following a "::" then its part of a type signature and its a type
constructor application. Otherwise, its part of an expression and its a
regular function call.

BTW, Haskell capitalization is significant in Haskell. Identifiers starting
with lower case are always regular functions. Types and type constructors
always start with upper case.

> What's a parameter to what?

Application in Haskell is left associative so its

    
    
        ((Map String) Any)
    

Haskell is a functional language and programs are filled with function
applications so using spaces for function application avoids a lot or
syntactic noise. The only time I find this really bites me is that, due to
automatic currying, passing the wrong number of arguments to a function can
lead to hairy type errors. This is not a big deal if you use add a healthy
amount of type annotations to your code though.

~~~
waps

      Application in Haskell is left associative so its
    
      ((Map String) Any)
    

You seem to have stumbled on the exact conflict. Anybody who's seen the theory
of lambda calculus will recognize the significance of what you've written, and
how it applies to partial binding of functions.

Anybody who hasn't will either be thinking of lisp or just be totally
confused.

This is where the conflict is. Many developers actually know some programming
theory and find Go woefully insufficient for anybody calling himself a
programmer. And the ones that just fiddle around with things and make barely
working programs without really knowing what they're doing are very happy that
they don't have to know this in Go.

What's really going on is that people with real programming background find
that Go forces them to think at the lowest possible level of abstraction, with
no way up. And people without any programming background say "that's GREAT !".

~~~
the_af
I'd find it depressing if there was any programmer out there who had trouble
understanding the concept of "left associativity". I think I learned it in
high school.

~~~
Peaker
He's talking about the concept of currying, not left-associativity.

~~~
the_af
Original question:

> _What 's a parameter to what?_

Answer:

> _Application in Haskell is left associative so its_

> _((Map String) Any)_

Which looks crystal clear to me. This is also the part of the answer that user
_waps_ later quoted.

------
ufo
The article mentions many uses of generics but it doesn't go over one of my
favorites, which is that parametricity gives you "theorems for free".

For example, I recently wrote a compiler in Ocaml for a scripting language.
The type I used for the syntax tree has lots of type parametsrs:

    
    
        type ('id, 'str, 'enum) stmtf =
            (* big ADT definition goes here... *)
    

The 'id type parameter is the type of identifiers (names), 'str is for strings
(they get interpolated) and 'enum is for constants.

These type parameters give me some flexibility. When the parser builds the
first syntax tree everything is still a string:

    
    
        type syntaxtree = (string, string, string) stmtf
    

and after a pass to bind names the strings get converted to more informative
types:

    
    
        type boundtree = (nameId, interpolation, string) stmtf
    

Up to now, we could get this flexibility by being untyped and defining all
these fields in stmtf as "void pointer" or "interface". But the generics lets
us make everything type safe. Central to this is the "functor map" over stmtf:

    
    
        val map_sstmtf:
            id: ('i1 -> 'i2) ->
            str: ('s1 -> 's2) ->
            enum: ('e1 -> 'e2) ->
            ('i1, 's1, 'e1) sstmtf ->
            ('i2, 's2, 'e2) sstmtf
    

It takes 3 functions telling what to do to the id, str and enum fields,
respectively and converts the stmtf by applying those fucntions to each field.
The neat thing is that in the implementation of map_stmtf if we forget to
apply apply one of these callbacks to one of the fields the code would not
typeckeck. The code also won't typecheck if we apply a callback to the wrong
field - say apply the "id" callback to a "str" field. This lets us catch at
compile time bugs that wouldn't even have been caught at runtime because in
the initial syntaxtree both "id" and "str" fields are represented by "string"
and are thus prone to being confused with one another. Both of these turned
out to be pretty useful as I evolved my language and changed the structure of
my syntax tree.

Liberal use of type parameters also gave many other benefits. For example it
was trivial to modify the tree to add a line number next to all identifiers,
all I had to do was update the type definition and then see the compilation
errors to find the parts of my code that had to be updated.

    
    
        type syntaxtree = ((position * string), string, string) stmtf
    

Finally, one extremely neat trick is that you can use type parameters in some
places where you would have used recursive types. This pattern is described
pretty well in this post I'm linking to and its the sort of thing that you
can't really do without generics.

[http://lambda-the-ultimate.org/node/4170#comment-63836](http://lambda-the-
ultimate.org/node/4170#comment-63836)

------
jackielii
Ok, we all know the problem, but do you have a solution? Have you considered
the researches done by others, for example this one:
[http://research.swtch.com/generic](http://research.swtch.com/generic)

~~~
frowaway001
Yes, and Go apologists would also know at least one solution if wouldn't keep
pretending that they can't keep reading the post until the very _first_
comment on the same page.

~~~
jackielii
What I had in mind was that Go is an open source project, if C#'s solution is
a good one, why not write up a proposal and try an implementation?

~~~
frowaway001
Why am I supposed to do that?

A) I'm using a working language already. B) Read the comments on the mailing
list regarding third party proposals/implementations from the Go authors.

In short: Complete waste of time.

------
sacado2
Looks like someone else discovered Go isn't as good as Haskell to program in
Haskell. It's a good, very pedagogical article, however.

------
fishnchips
I _really_ like Go but have always missed generics. Wondering if some kind
soul would fork Go, add generics and see how that fares.

------
callesgg
How can sort be considered generic. Every type must be sorted by different
stuff, the compiler can't possibly know what that stuff is.

I see what the author is coming from but generics would break what makes go
great.

There has been times when I wanted generics, but it has generally meant that i
was looking at the wrong side of the problem.

~~~
lmm
> How can sort be considered generic. Every type must be sorted by different
> stuff, the compiler can't possibly know what that stuff is.

What. Have you even used a language with generics? Yes it can, very easily;
you can do it in a traditional-OO way by having a sortable interface, or if
you want to be really cool you do it with a typeclass.

------
mikesf888
golang is a great language for devops to automate infrastructure tasks, but
for application dev not so much

~~~
NateDad
Have you used it in a real application? Is actually quite good.

------
_jp__
I loved VB6 so i like go really much...

------
sea6ear
This really seems like a guy who doesn't want what Go has to offer. At every
place he compares Go and Haskell, he wants Go to be like Haskell.

There's already a language like that - Haskell.

------
jondubois
Statically typed languages impose unnatural hardware-oriented constraints on
your business logic - It forces you to spend extra time and effort to make
sure that your logic is in a format which the compiler can understand.

Yes, this can sometimes help you to find silly errors in your code at compile
time (and thus occasionally save you a bit of time) but, unfortunately, that
occasional benefit doesn't make up for the productivity loss incurred from
constantly trying to 'please the compiler'.

Dynamically typed languages offer fluidity in your logic where it is needed.
For example, if you add an integer and a decimal (floating point) number
together, there is nothing wrong about this mathematically, but a static
compiler will complain! It's a limitation of the compiler not the programmer!

Statically typed languages force you to implement rigid class/type schemas
within your logic (sometimes with complex hierarchies) - These schemas have to
be significantly reworked whenever your business requirements change.

~~~
theseoafs
Have you never used a statically-typed language? Most statically typed
languages can handle adding an integer and a float together. The only one I
can think of that isn't capable of that is OCaml, though this is probably a
strength of OCaml rather than a weakness (int-to-float casting can be a common
source of bugs if it happens unexpectedly).

~~~
jondubois
Try to add an int and a float32 using 'Try go' on
[http://glolang.org](http://glolang.org) \- I get this error:

prog.go:13: invalid operation: x + y (mismatched types int and float32)
[process exited with non-zero status]

I'd have to cast one of the variables to get it to work. Casting is unnatural
- Maybe it's not so bad in this case, but in many cases it isn't desirable.

