
Simplicity and the ideas Go left behind - calavera
https://sourcegraph.com/blog/live/gopherconindia/111854129512
======
SirWart
One thing that I've appreciated now that I've spent a decent amount of time
writing Go as an individual developer is that it's much easier to jump into
open source code and make small improvements and fixes because the language is
very "context-free." When you're reading code, the control flow is always
spelled out, property accesses never magically invoke getters, and it's
generally hard to make things too complicated. There are things that annoy me
but the downside is almost always bounded.

~~~
Ironballs
Code readability is the original intent behind striving for simplicity.

Though I find some of the design choices a bit frustrating, e.g., generics,
lack of min/max; for each design choice I disagree with there are _dozens_ of
other choices I _do_ agree with. Structural typing, built-in concurrency, a
rich standard library, the syntax, the list goes on.

There is a certain brutal elegance to the way the Go standard library is
designed, it is really easy to read and comprehend. It took me less than half
an hour to make sense how the net/http server was implemented.

This doesn't only apply to the standard library: because the language style is
standardized--there are no coding styles, there is just _a_ coding style--
other libraries or programs are very easy to understand. If the program
architecture is easy to understand, it doesn't require any significant domain
expertise to understand.

Languages aren't supposed to be cargo cults, though some culture can be _nice_
, let's not kid ourselves: programming languages are means to to an end. Go is
pragmatic, it gets things done, it is a tool, and most importantly, it doesn't
get in the way of design or architecture. You are free to build programs in
any style or way you want.

Ultimately, languages are just expressions of grander designs that are far
more important than arguments for and against a particular syntax or language
feature.

------
kmontrose
This oversells golang's simplicity I think. Not a lot, but enough to rub me
the wrong way a tiny bit.

\---

> Go programs are built from just their source, which includes all the
> information needed to fully build the program.

Still have to deal with GOPATH, vendor your dependencies, and have everything
a `go generate` comment wants to invoke. It's _certainly_ better than
makefiles, but it's hardly just the source.

> C# is joined at the hip with Windows. Objective-C and Swift are for Apple.
> Java and Scala and Groovy might benefit from JVM bytecode and its
> independence… until you realize that Oracle isn’t interested in supporting
> Java on anything other than Intel hardware.

C# has Mono, you can use Objective-C with gcc, the JVM has a bajillion
implementations. I suppose Swift is more or less unportable at the moment. 1
out of 4 ain't great.

> Go is helping pioneer a command-line renaissance that reintroduces a
> generation of programmers to the idea of writing tools that fit together
> like segments in a pipe (the original Unix philosophy).

This never went away. Heck we were going over this in college, which for me
was in Scheme, Java, C, and C#.

~~~
bsder
I like the fact that the Go guys continue to avoid talking in detail about
Rust and Clojure.

That tells me that the don't think they can win the comparison.

~~~
NateDad
The core goals behind Rust and Clojure are very different from those behind
Go. This article/presentation would not be appropriate for those languages. I
don't think anyone would say that Rust or Clojure are simple languages, or
that simplicity is a core goal for them. Rust's core goals are performance,
memory safety, and lack of race conditions (AFAICT). And clojure is a LISP
which puts it in its own category, really.

It's like saying a jeep can't compare to a ferrari or a minivan - other than
having 4 wheels, they're really not at all designed to do the same kinds of
things... Sure, maybe driving to the corner store they're all pretty much the
same, but which do you want in 8" of mud? Which do you want in a car chase on
the highway? Which do you want to bring your 4 kids to soccer practice?

~~~
steveklabnik
> Rust's core goals are performance, memory safety, and lack of race
> conditions (AFAICT)

We usually formulate this as "memory safety without garbage collection," which
has secondary implications on speed and concurrency, but yes. (Also, 'data
races' rather than 'race conditions,' technically).

~~~
NateDad
Thanks for clarifying :) Yes, I should be more careful about specifying data
races vs. race conditions :)

------
iopq
Consider the following:

it is actually more difficult to code in a language that's simple

Why? Because a more feature-complete language allows you to ELIMINATE the
concept of `nil` through an `Option` type.

An `Option` type is an `enum` that consists of either `Some(x)` or `None`.
That means it is always checked. You can never accidentally use a value that
is `None` because the type checker would not let you use `Option<T>` instead
of `T` itself.

The code snippets on the websites are far from simple. You HAVE TO remember to
do `if err != nil` in every single function. A more advanced type system would
actually make this a requirement.

So what is more important, the simplicity of the language or lack of bugs?

~~~
nexneo
Go depends on tools to check correctness that type system doesn't cover.
Check, [http://blog.golang.org/error-handling-and-
go](http://blog.golang.org/error-handling-and-go) and
[https://github.com/kisielk/errcheck](https://github.com/kisielk/errcheck)

~~~
iopq
That's the answer for everything in the Go community. More ad-hoc tools to
replace the type system. Have concurrency bugs? Use a tool that detects some
types of data races. Have problems with errors? Have a tool that detects not
checking for errors.

How is approaching every single problem with a different tool more simple than
using the type system as the one tool for static checking? The Go ecosystem is
creating a ad-hoc, informally-specified, bug-ridden, slow implementation of
half of the type checker of Haskell.

~~~
pacala
The jury is out on whether, for large time frames and for large communities,
having a centralized type system is better than having a decentralized set of
independent tools.

I'm not very excited by GHC's model of language extensions,
[https://downloads.haskell.org/~ghc/6.12.2/docs/html/users_gu...](https://downloads.haskell.org/~ghc/6.12.2/docs/html/users_guide/flag-
reference.html#id598783). And in spite of the large number of Haskell
extensions, there are still pragmatic niches that aren't covered, see for
example Rust's encoding of memory management in the type system.

In a sense, GHC contains a ad-hoc, informally-specified, bug-ridden, slow
implementation of half of the type checker of Coq ;) Which is to say that
there are many flavors of sophisticated type systems, and it's not clear which
flavor is most conducive for writing good software on a tight time budget.

------
Daishiman
This answer is slightly infuriating:

> Q: Lack of generic collection classes like in Java’s Guava library?A: For
> 90% of cases, slices and maps do what you need. For the other 10%, you might
> consider whether your package should own the logic of those special
> containers, instead of using an external package.

I read this as:

"We're not willing to put in the hard work on thinking of a decent generics
implementation, despite decades of working solutions with a myriad of choices
in tradeoffs, so you'll have to do the hard work of integrating a dozen
different collections libraries and who knows how many different, after-the-
fact, mediocre strategies to the generics issue, each slighly off, all of them
conceptually incomplete somehow, and with the slight bugs that come from not
having a well-trodden path for an essential component of most programming
languages."

~~~
davecheney
I am not a member of the Go team, just a contributor. I represent only my own
opinions, please don't put words into my mouth.

~~~
Daishiman
This is my own personal opinion on core dev's position and no one else's, but
to me generics and dependency management have always been the pink elephant in
the room.

I can wait for features; I'm patient. But the downright refusal of Go's core
team to even begin to address this issue is not just a technical problem, but
a communications one. This is clearly big for a lot of people, and I have yet
to see any serious responses aside from either "you domain model's wrong if
you need generics" or "deal with it".

In contrast (just picking this language for its community outreach, not
because of any perceived technical competition), Rust's core devs have been
forthcoming about practically _every_ objection they've gotten. Their answers
are clear, detailed, not demeaning, and constructive. When they don't know,
they're honest about it, and when answers are hard, they take the time to
explain. But I have _never_ heard of anyone being belittled for not
understanding lifetimes or the borrow checker.

Yet it seems that somehow if I have a bone to pick with Go not being able to
dispatch functions by argument or arity, or with how its simplicity ends up
with codebases a lot of people would consider much more verbose than
necessary, it's just that I don't "get it". The problem isn't even in the
accusation; I just don't even receive examples or reasonable explanations,
something I'm used to in related language discussions.

I was willing to give the language a pass on these things when it was just
getting started and a lot of the classical, early product criticisms were
abound. But it's gotten tiring; the number of unanswered questions is
remarkable by now.

~~~
crawshaw
Several members of the Go team have put a lot of time into studying and
prototyping various implementations of generics.

If you look through the mailing list archives you will find many emails from
Ian Lance Taylor on the topic.

~~~
Daishiman
I'll be completely honest: it's one of the most BS reasons I've ever read in a
technical discussion.

How can we talk about high performance when:

* The current "generics" mechanism (interface et al) does runtime introspection, which is just about as slow and unwieldy as it gets * There is no option for pervasive, truly high-performance data structures since everything is a map (which comes with its own type parametrization as an exception to everything else), and if you don't like the hashing algorithm, tough luck. * You have a garbage collector running in the background, which is barely tunable compared to the options other runtimes have

Talking about performance when it's convenient as an argument against generics
but disregarding the other holes in the language is not reasonable, because I
would say that the choice of implementation for native maps is probably far
more important for high performance. Yet here we are, no one complaining.

So we can really discard the performance argument, thus there is now an ample,
valid set of choices for generic programming, several of which don't go
against the goals of fast compilation.

Speaking of fast compilation; pretty much everything is going to be faster
than C++ templates, since the entire compilation chain in C++ is slow.

~~~
crawshaw
It would be unreasonable to have designed a generics implementation into Go 1
that did not cover the builtin polymorphic map, slice, and append. A simple
set of orthogonal features is an important principle in Go.

For these, performance is most certainly critical.

------
SixSigma
It is interesting that the OP things that static binaries is related to being
born in Google. The thing is that Plan9 doesn't have shared libraries, all
binaries are statically linked. And the reason for this is that plan9 is a
networked operating system, needing to load multiple files at runtime would
severely harm startup time for a binary.

Run trace on a Linux binary and see the slew of "file not found" errors from
syscalls looking for shared libs at startup and then imagine each one of these
is taking place over a 9600 baud connection.

Good design realises benefits that authors never needed to consider.

~~~
justincormack
Static binaries used to be common, the normal way of doing stuff. People tend
to think that package management killed them, but it was actually glibc which
cannot make proper static binaries as it insists on dynamic functionality for
some functions, such as name resolution. Now we have Musl libc there may well
be a revival in static binaries from C applications, and the C-derived
ecosystem.

~~~
SixSigma
As a demonstration I traced date(1) on CentOS. Here are the file accesses, if
you are running diskless, each of these needs a round trip to the file server.
(except the final 3, of course)

access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)

open("/etc/ld.so.cache", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=54185, ...}) = 0

close(3) = 0

open("/lib64/librt.so.1", O_RDONLY) = 3

read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@!\0\0\0\0\0\0"...,
832) = 832

fstat(3, {st_mode=S_IFREG|0755, st_size=43880, ...}) = 0

close(3) = 0

open("/lib64/libc.so.6", O_RDONLY) = 3

read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\1\0\0\0\0\0"...,
832) = 832

fstat(3, {st_mode=S_IFREG|0755, st_size=1921176, ...}) = 0

close(3) = 0

open("/lib64/libpthread.so.0", O_RDONLY) = 3

read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340]\0\0\0\0\0\0"...,
832) = 832

fstat(3, {st_mode=S_IFREG|0755, st_size=142640, ...}) = 0

close(3) = 0

open("/usr/lib/locale/locale-archive", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=99158576, ...}) = 0

close(3) = 0

open("/etc/localtime", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=3661, ...}) = 0

fstat(3, {st_mode=S_IFREG|0644, st_size=3661, ...}) = 0

read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\7\0\0\0\7\0\0\0\0"...,
4096) = 3661

lseek(3, -2338, SEEK_CUR) = 1323

read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\0\0\0\10\0\0\0\0"...,
4096) = 2338

close(3) = 0

fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0

write(1, "Tue Feb 24 08:57:58 GMT 2015\n", 29) = 29

close(1) = 0

~~~
justincormack
Ok, here is strace of date(1), which is dynamically linked, on Alpine Linux
which uses Musl libc not glibc.

execve("/bin/date", ["date"], [/* 16 vars */]) = 0

mprotect(0x7777dcd5a000, 4096, PROT_READ) = 0

mprotect(0xdc2d89a3000, 4096, PROT_READ) = 0

arch_prctl(ARCH_SET_FS, 0xdc2d89a4268) = 0

set_tid_address(0xdc2d89a4298) = 2439

clock_gettime(CLOCK_REALTIME, {1424769563, 611556639}) = 0

open("/etc/localtime", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=118, ...}) = 0

mmap(NULL, 118, PROT_READ, MAP_SHARED, 3, 0) = 0x7777dcd57000

close(3) = 0

ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS,
{B38400 opost isig icanon echo ...}) = 0

writev(1, [{"Tue Feb 24 09:19:23 UTC 2015", 28}, {"\n", 1}], 2Tue Feb 24
09:19:23 UTC 2015 ) = 29

close(1) = 0

close(2) = 0

exit_group(0) = ?

+++ exited with 0 +++

~~~
SixSigma
That is worlds apart.

~~~
SixSigma
OpenBSD's ktrace is similar

31144 date CALL readlink(0x3c001c16,0xcfbe7048,0x3f)

31144 date NAMI "/etc/malloc.conf"

31144 date RET readlink -1 errno 2 No such file or directory

31144 date CALL open(0x3c001967,0<O_RDONLY>)

31144 date NAMI "/etc/localtime"

31144 date RET open 3

31144 date CALL open(0xcfbde9d4,0<O_RDONLY>)

31144 date NAMI "/usr/share/zoneinfo/posixrules

31144 date RET open 3

~~~
justincormack
Yes, in many ways Musl is a BSD style libc for Linux (even down to the
license). Add pkgsrc and it is pretty BSD-like.

------
threeseed
> until you realize that Oracle isn’t interested in supporting Java on
> anything other than Intel hardware.

Oracle does support the JVM on embedded hardware. It's right here:
[http://www.oracle.com/technetwork/java/embedded/embedded-
se/...](http://www.oracle.com/technetwork/java/embedded/embedded-
se/downloads/index.html)

Also since when was Oracle the only JVM vendor. There are plenty of others
that support different hardware:
[http://en.wikipedia.org/wiki/List_of_Java_virtual_machines](http://en.wikipedia.org/wiki/List_of_Java_virtual_machines)

And finally since when has the choice been between slow, interpreted languages
and fast, compiled ones ? Plenty of options exist in the space between.

------
qznc
I'd argue that in most cases "easy > simple" with the definitions of Rich
Hickey. Easy means you understand it quickly. Simple means it uses few
concepts.

Go doesn't want to use the concept of generics. However, if your code uses
"List<IP>" instead of "List", it is easier to understand, because it
additionally tells you it is about IPs. Python is a language which tries to be
easy by resembling pseudo code.

If you really want simple, you could as well use SML, TCL, or Lua.

~~~
masklinn
> If you really want simple, you could as well use SML, TCL, or Lua.

And if you truly _want_ simple, you use Smalltalk, Scheme or Forth.

~~~
kazagistar
I dunno about smalltalk, but Scheme and Forth start off simple until a
programmer writes tens of thousands of lines of code, at which point it gets
harder to read and follow the code exactly.

~~~
qznc
No matter how much convoluted code your write, it is still a "simple
language". Your code however is not necessarily simple or easy.

~~~
masklinn
That tends to be the flip side of the "simple language".

And at least the 3 languages quoted are so simple that they _must_ provide the
tools for building new abstractions (which are the tools for building the
language in the first place), so you can cut down on code by building a
reusable toolbox of abstractions.

Go is complex enough that they can get away without that, and even get praised
for pushing the complexity to userland code and providing no way for users to
manage that complexity.

------
fineIllregister
>He currently works at Canonical, where part of his work involves porting Go
to ARM 64.

That's interesting. Has Canonical stated what their interest in Go is?

~~~
Mikeb85
Probably something to do with Docker...

~~~
dengnan
IIRC, Canonical joined the go community around 2010/2011 when docker has not
been created. They are actually one of the early adopters of Go. Some major
projects from Canonical using Go are juju[0], mgo[1], etc.

0\. [https://juju.ubuntu.com/](https://juju.ubuntu.com/)

1\. [https://labix.org/mgo](https://labix.org/mgo)

Edit: format.

~~~
georgemcbay
Also go-qml

~~~
threeseed
Interesting.

Perhaps Go becomes the language for Ubuntu Phone apps ?

~~~
NateDad
I believe that is an intended purpose of go-qml, yes.

------
codexon
I was using Go recently and I ran into some simplicity issues. It is not
straightforward to create a map of net.IPs or manipulate netmasks. You will
have to copy to and from a separate array/integer.

~~~
bsder
Congratulations. Welcome to why you _don 't_ use Go.

Lack of generic access to data structures is one of their bigger fails.

However, they don't see it that way. One point of Go was to prevent needing to
describe things before being able to compile it. Most things that people
regard as "failures" in Go were deliberate choices to enable large codebases.

~~~
misframer
No, that's not why you don't use go. What the parent comment is dealing with
is the fact that you can't have maps keyed by a net.IP, which is implemented
as a []byte. Byte slices are not valid keys. This isn't really about generics.

~~~
peterfirefly
It is _very_ much about polymorphism and generics.

------
swah
Would we be better off if the big Apache projects were written in Go instead
of Java? Would that have been harder (starting today, say)?

