
Replacing Clever Code with Unremarkable Code in Go - kylered
https://vividcortex.com/blog/2013/06/04/replacing-clever-code-with-unremarkable-code-in-go/
======
stcredzero
_> A straightforward solution of the problem is superior to one that makes you
feel like a high priest for having discovered it._

It is also much better for the team and the company. Beginning musicians are
just trying to play. Intermediate musicians are try to play as fancy as they
can. Master musicians play what's needed by the tune.

It takes somebody who's past all the ego issues to devote their intelligence
to making code look simple. It takes great intelligence to solve a complex
problem in a way that looks simple and straightforward.

~~~
mwcampbell
Reminds me of this old Zed Shaw essay:

<http://zedshaw.com/essays/master_and_expert.html>

I think at least two of Go's designers, Ken Thompson and Rob Pike, are true
programming masters.

Note: When I posted this comment before, I accidentally pasted the wrong URL.

~~~
gngeal
_I’d love someday to hear a young coder tell a story about someone they
idolized like, “There was this guy I worked with who once optimized a
complicated red- black tree getting 300% performance boost. I was baffled and
ask, ‘How’d you do that? That’s impossible.’ To which he responded…”

“‘That’s my linked list my son.’”_

I believe that this is exactly what Niklaus Wirth did in the Oberon compiler.

~~~
stcredzero
You sure it wasn't skip lists?

~~~
gngeal
Not unless he had a TARDIS at his disposal. I mean, skip lists were discovered
in 1989, and by that time, the Oberon compiler had already existed for a few
years. I don't recall the source now (I'd have to dig for it) but I believe
that the Wirth anectode referred to his intervention in a case of overzealous
students some time before that.

------
VeejayRampay
For the love of Thor, please provide a few code examples illustrating the
content, hard to get an idea of how to simplify things with Go with mere words
and ideas.

~~~
Dylan16807
Definitely. I'm left with no idea how Go actually helps. You can trivially
implement 'pipelines' in any language by making a handful of queue structures
and using them to feed your functions. Why were there callbacks in the first
place?

~~~
zerohp
My thought exactly. The whole thing strikes me as a case of: Have a hammer and
everything looks like a nail. First with callbacks, and second with channels.

------
mietek
So, Go has message passing. Just like Erlang, since 1986.

Only without per-process heaps, which make crashing processes safe. And
without process linking and supervision, which helps systems built using
Erlang/OTP achieve nine nines of uptime. Instead, it includes null references,
also known as Hoare's Billion Dollar Mistake.

But it's not enough to scorn the industry; Go's designers also look down their
noses at academia.

A modern, ML-derived static type system? Generics, which would enable the
unwashed mashes to write their own `append`? Ain't nobody got time for that —
wait, what? Oh, Rust does?

Go's tooling is fantastic, and its pragmatism is commendable, but ignoring the
last 30 years of programming language research is not.

~~~
enneff
Erlang and Go are very different programming languages. They have a similar
form of message passing, but the semantics are very different. Go's
concurrency model still exists within a single process with a shared heap,
which is very different to Erlang's model.

> it includes null references, also known as Hoare's Billion Dollar Mistake.

The very notion of null references being inherently bad is highly contentious.
Just because someone gave an idea a catchy name, doesn't make it true.

> Ain't nobody got time for that — wait, what? Oh, Rust does?

Go and Rust have very different design goals. If you think Rust is more
appropriate for you, use Rust.

> ignoring the last 30 years of programming language research is not.

This is a common refrain from Go haters, but it's really not true. Just
because you don't do everything everyone else is doing doesn't mean you have
ignored them. Go includes the language features we felt were necessary in a
productive language.

Your comment frames Go as this language that thumbs its nose at a lot of good
ideas, but the reality is that every design decision has tradeoffs. If you
don't have null pointers or generics, then you need a more complex type
system. The Go designers may not have made all the decisions _you_ would have
made, but they were not made in ignorance of the alternatives.

And this leads back to the message of the original article: Go encourages the
programmer to write simple code that is obviously correct. A more complex type
system would detract from that goal.

~~~
mietek
> Go's concurrency model still exists within a single process with a shared
> heap, which is very different to Erlang's model.

I've already mentioned Go doesn't have per-process heaps, and I consider this
a step backwards from shared-nothing message passing.

I don't understand how can you argue that facilitating the threads-and-locks
model leads to "simple code that is obviously correct". Writing correct
multithreaded code is widely recognized to be difficult.

> The very notion of null references being inherently bad is highly
> contentious.

If by this you mean people keep[1] pointing[2] out[3] the problems with nulls,
and other people keep ignoring them, then I fully agree. Otherwise, please
direct me to a discussion offering a pro-null argument.

For code to be correct in the presence of null references, the programmer must
remember to check every nullable value of uncertain status, without support
from the compiler.

Alternatively, in a language with option types, only values explicitly marked
as optional must be checked, and forgotten checks are pointed out by the
compiler. Less code, more safety.

Tell me again, how does allowing null references lead to "simple code that is
obviously correct"?

> If you don't have null pointers or _[do have]_ generics, then you need a
> more complex type system.

Certainly. More complexity for the language implementers; less complexity for
the language users.

1\.
[http://qconlondon.com/london-2009/presentation/Null+Referenc...](http://qconlondon.com/london-2009/presentation/Null+References:+The+Billion+Dollar+Mistake)

2\. [http://www.dcs.warwick.ac.uk/~hugh/TTM/TTM-TheAskewWall-
prin...](http://www.dcs.warwick.ac.uk/~hugh/TTM/TTM-TheAskewWall-
printable.pdf)

3\. <http://www.ccs.neu.edu/racket/pubs/dissertation-cobbe.pdf>

~~~
enneff
> For code to be correct in the presence of null references, the programmer
> must remember to check every nullable value of uncertain status, without
> support from the compiler.

If you don't have null pointers then you need to push the responsibility for
checking for initialization somewhere. You seem to be advocating that place is
in the type system. That's fine, but you can't say it wouldn't make the type
system more complex.

I see more Go code than probably anyone in the world, and I don't see anyone
suffering from the scourge of null pointers, however bad people say they are.
I think in certain contexts they can be problematic (SQL is a good example),
but the concept of "nil" in Go is quite useful and easy to understand. For
instance, unlike C++ it is quite valid to call a method on a nil receiver.

> Tell me again, how does allowing null references lead to "simple code that
> is obviously correct"?

It's a tradeoff. You get a simpler type system in exchange for null pointers.

> More complexity for the language implementers; less complexity for the
> language users.

No, not just for the implementers. For the users too.

~~~
mietek
> No, not just for the implementers. For the users too.

You haven't bothered to address option types. This sort of unqualified
assertion may lead people to believe Go's questionable design decisions were,
in fact, made out of ignorance.

A language with null references is exactly like a language with option types,
where every reference value is by default optional.

Removing this default does not increase complexity for the user. Conversely,
it allows the user to write less code and enjoy more safety, which is clearly
a complexity decrease.

~~~
enneff
> A language with null references is exactly like a language with option
> types, where every reference value is by default optional.

Yes.

> Removing this default does not increase complexity for the user.

Yes, it does. They need to be aware of the semantics of the option mechanism,
how to specify whether a type is optional, and when that is appropriate. They
must also perform checks when converting values from optional to non-optional
types.

In Go the choice is made for you: reference types may be nil.

~~~
mietek
> They need to be aware of the semantics of the option mechanism, how to
> specify whether a type is optional, and when that is appropriate.

Being aware of the semantics of the option mechanism is exactly like being
aware of the semantics of null references. You've just agreed one is like the
other with a default.

Specifying whether a value is optional is _less_ complex than the usual way of
specifying whether a reference can be null — a documentation comment. Comments
aren't usually checked by the compiler.

Knowing whether it's appropriate for a value to be optional is exactly like
knowing whether it's appropriate for a reference to be null. However, once a
value is determined to exist, subsequent code can assume the value is non-
optional, avoiding subsequent checks with no decrease in safety. _Less_
complexity.

> They must also perform checks when converting values from non-optional to
> optional types.

Surely you mean the other way around?

Checking whether an optional value exists is exactly like checking whether a
reference is not null. However, fewer checks are necessary. _Less_ complexity.

~~~
enneff
> Surely you mean the other way around?

Yes, I had it backwards. Corrected.

> Specifying whether a value is optional is less complex than the usual way of
> specifying whether a reference can be null — a documentation comment.

Is it? I agree there's definite virtue in having this property checked by the
compiler, but I think you and I will have to disagree on the notion of
complexity here.

~~~
azth
The beauty of Option types is that they are mostly used as return types. Most
function arguments are non-nullable pointers or references; only ones that
might be None are passed as options. This makes reasoning about code much
easier. I just look at the function signature, and I know the types of
arguments it expects. I also don't have to litter the beginning of each
function call with if !null checks to each reference parameter.

> They must also perform checks when converting values from optional to non-
> optional types.

There are multiple ways to manipulate option type variables. The following
comment from a Redditor sums it up very nicely:

"What is interesting is that (coming from scala and haskell) you almost never
pattern-match on option types. The power of option, imho, is that you don't
have to care whether it is Some or None, you write the same code regardless.
If you are pattern-matching the whole time, you haven't gained all that much
over checking for nil/null.

Option is a container, and we can use my_var.chain(...) and my_var.map(...) to
update the things in the container. And the joy of it is that these methods
will automatically do the right thing, with repect to Some/None, so you can
string a bunch of these calls together, and if my_var is Some(...) is will
apply all of the specified functions. If it is None, it will remain None."

source:
[http://www.reddit.com/r/rust/comments/1ewrhz/pattern_matchin...](http://www.reddit.com/r/rust/comments/1ewrhz/pattern_matching_and_the_option_type/)

~~~
enneff
What I get from your comment is that option types work well in Haskell and
Scala. This doesn't surprise me, as both languages places a strong emphasis on
the type system, and so they are easily supported.

For option types to work well in Go you would need to add more support to the
type system. But we don't want a more complex type system. That's a tradeoff
we made.

Again, I'm not really sure why people keep getting bent out of shape about
this. Haskell, Scala, and Rust exist for people who want to write that kind of
code. I prefer to write Go code.

~~~
masklinn
> What I get from your comment is that option types work well in Haskell and
> Scala. This doesn't surprise me, as both languages places a strong emphasis
> on the type system, and so they are easily supported.

Option types need no type system support beyond having option types at all.
Think of an option type as a container. It's either empty, or it has a single
element inside it.

You can manipulate your container by checking if it's empty, getting the value
out, manipulating the value and putting the value in a new collection. That's
the basic low-level primitives of interacting with an option type.

And you can also use higher-order operations, e.g. map over the collection
which will only "do things" if there's a value and will just return an empty
collection if there isn't. These are the higher-level combinators on option
types. They require no type system complexity outside of having option types
in the first place.

~~~
mietek
Yes. Also, these low-level primitives already exist in Go, as support for
pointers, or references. The missing bit is requiring references to be
explicitly annotated as nullable or non-nullable.

------
peterwaller
I think this is one of the really great things about Go. Some detractors ask
"There is so much to be gained by having generics! What would be lost if Go
had generics? Nothing! Ergo, Go is wrong to not have generics".

This line of argument is flawed. Go curbs the ability of people to write crazy
(and ultimately mentally expensive) abstractions. The lack of generics in Go
is very a good thing. Especially when it comes to collaborating on a large
unfamiliar code base.

I have been productive in Go and haven't missed the ability to write generic
code so far. Usually it turns out there is another way to achieve what you
want without them. If there isn't, maybe I'm trying to solve the wrong
problem, or it is the wrong language for the task.

~~~
cwzwarich
If anything, generics _reduce_ the mental expense of reading code, because if
a container has a generic value type I know that it isn't going to be doing
something interesting to those values behind the scenes.

~~~
JeanPierre
I would like to add on to this:

Generics may not be the correct solution, but I would really like a way to
make collections which are general enough to be used by any datatype. As of
now, I cannot do that without doing the typical `interface{}` approach.

The built-in data structures + channels are able to handle this, but if I just
want a set of elements, what do I do? Make a `map[foo] bool`? If I see such a
piece of code in an unfamiliar code base, I have no idea whether to test for
key existence or whether I have to test whether the bool is true. A generic
set would leave me puzzled and type unsafe[1]: What types could possibly exist
in this set? A set of foos is not that hard to comprehend, and is in fact very
much more straightforward to understand than a generic set, which in
unfamiliar code may contain anything, or a mapping from foos to bools.

[1]: Rob Pike mentions in
[http://www.youtube.com/watch?feature=player_detailpage&v...](http://www.youtube.com/watch?feature=player_detailpage&v=rKnDgT73v8s#t=465s)
that type safety is of high importance for Golang, but how does one achieve
that if all the different datatypes I implement/need use `interface {}` where
I have to cast all values afterwards? That seems very type unsafe, from where
I stand.

~~~
scarboy
You should be using map[T]struct{} for sets instead of bool. struct{}s use 0
bytes and there will be no confusion as to what the value is for.

------
jweese
> The channel is the analog to the Unix shell’s | character.

This sentence was italicized, and rightfully so. I've written a few small Go
programs, but I haven't run into a problem where I thought, "A channel is
definitely the right solution here." Yet I love and feel quite comfortable
with shuffling data through big shell pipelines. Perhaps I'll think of a
channel next time I'm reaching for a pipeline.

~~~
mratzloff
I find that I use channels mostly when working with goroutines. It's also a
convenient mechanism for connection pooling.

------
realrocker
I wanted to say this but couldn't find the right words. The author manages to
do it. This is something I wrote: <https://github.com/adnaan/hamster>. The
whole experience of programming in Go is like moving from point A to point B
in a straight line.

------
fixxer
Clever is not smart.

Consider this anecdote: I recently put together a DIY 3d printer. I didn't
have the right tools to cut steel bar, so I made a clever jig and used a
shitty Dremel without enough clearance. Clever? Yes -- it got the job done
with limited resources. Smart? No -- dumb, actually. Smart would have been
driving 15 minutes to Harbor Freight to buy a $20 cut-off saw.

~~~
henrybaxter
Smart _might_ be to buy a cut-off saw. Brilliant would be to find a working
cut-off saw with blade for $20!

~~~
jlgreco
If he has an air-compressor already, he could do it for under $20. Air tools
are pretty damn cheap on the low end. ;)

------
obviouslygreen
Perhaps I'm reading this wrong, but if I'm not, it seems to be making a great
point but missing a far greater one.

This is the more important lesson.

 _Replace clever code with unremarkable code._

Go has nothing to do with it. PERL has nothing to do with it. Switching
languages most certainly has nothing to do with it. Keeping yourself in check
and stepping back from your problems to understand their fundamentals, then
looking for more appropriate and elegant methods of dealing with them in _any_
language is what does this.

------
xaprb
I didn't put much code into the article, but ask me if you'd like more details
on exactly what I'm talking about -- in terms of real code.

~~~
JulianMorrison
Like this, I presume:

    
    
        go func(){
            for {
                select {
                case <-time.After(time.Second):
                    log.Println("timeout")
                case <-stop:
                    return
                case val, channelOpen := <-inputs:
                    if !channelOpen {
                        return
                    }
                    outputs <- process(val)
                }
            }
        }()

~~~
itsmonktastic
Nice. I still haven't given Go much time, but I really like this form, where
you can switch on multiple blocking calls.

I've definitely wanted this in other languages where I'm using thread safe
queues for communication, and had to do manual multiplexing into a new queue
whenever I wanted to do a blocking get on multiple queues.

Does anyone know of a convenient way to do this when handling multiple
instances of python's Queue or similar with Haskell's Chan?

~~~
njs12345
Looks like you can use STM in Haskell:
[http://stackoverflow.com/questions/5879128/a-way-to-form-
a-s...](http://stackoverflow.com/questions/5879128/a-way-to-form-a-select-on-
mvars-without-polling)

~~~
itsmonktastic
Thanks for posting this. I found something similar but on first blush didn't
think it was quite right as I was mistakenly thinking that you could only do
this for TChans with the same type. Looks like it might be just what I'm
after! When I think about it, STM seems like it makes sense here, since you
probably want the ability to cancel/rollback the blocking gets that didn't
return.

EDIT: Actually, this isn't right. The example linked does "if get from channel
1 fails, get from channel 2". I'm looking for "select" on multiple channels,
where the result is based on the first channel to return.

EDIT again: Apparently I can't read, and this does achieve what I'm looking
for, I just have no basic experience with STM. Awesome, will try this later!

------
tharshan09
I just kept staring at that GIF ...

~~~
k3n
I've seen it many times before, and it's always been amusing, but looking at
it within the context of "clever code" is brilliant. That's exactly what it
looks like some people have done (figuratively) to produce the code that they
did.

------
gpvos
Hmm, up till now I though Go wasn't anything special, but now I'm certain I
will check it out.

------
cgag
It's so strange to me that people describe things like higher order functions
and map/filter/reduce as being clever / complicated and think manual iteration
and indexing into an array is "simple".

I hate to keep linking to this talk because I don't want to look like too much
of a clojure fanboy, but I think a lot of people would benefit from re-
examining their definition of simple:
<http://www.infoq.com/presentations/Simple-Made-Easy>

~~~
jstelly
Agreed. This is a wonderful talk for anyone who writes code in any language.

------
mc-lovin
This article didn't make much sense to me. In particular, what was it about
the problem that prevents having a "stage" object with a "do_stage" method,
which takes the input object and returns the output object (or some error code
etc.).

I feel like the answer has to do with concurrency but the description in the
article was to vague to get a more precise idea.

------
Vendek
Congratulations, he realized the advantages of monadic composition of
computations.

------
gypsobelum
<http://www.paulgraham.com/avg.html>

_Programmers get very attached to their favorite languages, and I don't want
to hurt anyone's feelings, so to explain this point I'm going to use a
hypothetical language called Blub. Blub falls right in the middle of the
abstractness continuum. It is not the most powerful language, but it is more
powerful than Cobol or machine language._

 _And in fact, our hypothetical Blub programmer wouldn't use either of them.
Of course he wouldn't program in machine language. That's what compilers are
for. And as for Cobol, he doesn't know how anyone can get anything done with
it. It doesn't even have x (Blub feature of your choice)._

 _As long as our hypothetical Blub programmer is looking down the power
continuum, he knows he's looking down. Languages less powerful than Blub are
obviously less powerful, because they're missing some feature he's used to.
But when our hypothetical Blub programmer looks in the other direction, up the
power continuum, he doesn't realize he's looking up. What he sees are merely
weird languages. He probably considers them about equivalent in power to Blub,
but with all this other hairy stuff thrown in as well. Blub is good enough for
him, because he thinks in Blub._

 _When we switch to the point of view of a programmer using any of the
languages higher up the power continuum, however, we find that he in turn
looks down upon Blub. How can you get anything done in Blub? It doesn't even
have y._

 _By induction, the only programmers in a position to see all the differences
in power between the various languages are those who understand the most
powerful one. (This is probably what Eric Raymond meant about Lisp making you
a better programmer.) You can't trust the opinions of the others, because of
the Blub paradox: they're satisfied with whatever language they happen to use,
because it dictates the way they think about programs._

~~~
NullSet
I think this basically nails the attitude of every single go convert that I
have spoken to. I am starting to get tired of hearing lines like "that lisp
stuff is too complicated, I am a go programmer" or "why would you do x in y
way, it's so much better in go".

I don't think I have ever seen such unrelenting fanboyism in a programming
language. It very well may be a fantastic language but its not an end all be
all, we would do well to remember that.

~~~
blub
Ruby and Javascript were (are?) pretty similar when it comes to fans. It's
rather depressing to see how cultish programming is.

~~~
k3n
Hah, nice pseudonym.

I think it's mostly inevitable, to me it's no different than asking group of
farmers which make of pickup or tractor is best, or a deliveryman which brand
of shoes are best... I'm pretty sure you could come up with at least one
analogue for most any vocation.

In programming, we have: language, practice (formatting, documentation,
framework, etc.), methodology (testing, planning), development environment
(integrated or otherwise), etc. and each of these is a lightning rod for
disagreement. I think it just seems to be more prevalent for software
engineers due to the fact that a majority of our discourse occurs online.

