
Things about programming I learned with Go - mjkpl
http://mjk.space/5-things-about-programming-learned-with-go/
======
rbehrends
> It’s better to compose than inherit

I know that this is just a restatement of the "composition, not inheritance"
mantra in Go, but it still makes about as much sense as "product types, not
sum types". A more meaningful statement would be: "use inheritance to express
sum types, use composition to express product types." There's no "better"
relation between the two concepts, each has its own distinct purpose.

Yes, inheritance has traditionally sometimes been abused to implement a
limited form of product type (for example, to work around Java's lack of
proper value types), but that's a misunderstanding of what inheritance is used
for.

> Go doesn’t have the concept of inheriting structs by design.

Go has automated delegation, and as we know, (automated) delegation IS
inheritance [1, 2]. Some implementations of delegation are a bit more limited,
some are a bit more expressive, but fundamentally they have the same purpose.

[1]
[http://dl.acm.org/citation.cfm?id=38820](http://dl.acm.org/citation.cfm?id=38820)

[2]
[http://dl.acm.org/citation.cfm?id=900985](http://dl.acm.org/citation.cfm?id=900985)

~~~
pm215
'A more meaningful statement would be: "use inheritance to express sum types,
use composition to express product types."'

I'm not sure how well that works as a meaningful statement, because it assumes
that the reader has a solid grasp of what a sum type and a product type are.
If not (and I make the perhaps dubious assumption based on my own experience
and knowledge that most programmers, especially most people using mainstream
languages like Go or C++ or Python will not) then it doesn't really convey any
information, unfortunately. I think a lot more people will know roughly what
inheritance and composition are.

~~~
yen223
That's ... sad. Sum types and product types are simultaneously easier to
understand _and_ are more useful to know about than inheritance.

Sum types are simply types whose values can be one of several choices - surely
that's something a _child_ can reasonably understand!

Product types might be a little harder to grok - they're essentially structs -
but surely no harder to understand than inheritance and the whole IS_A/HAS_A
mess

~~~
s_ngularity
Inheritance is not a way to express sum types, it's a form of subtyping. A sum
type is like a discriminated union, it can only be one thing at a time.
Subtyping allows a value to have multiple (related) types simultaneously,
which is much more expressive. I suppose you can use one level of single
inheritance to emulate a sum type, but you could just as well emulate it with
a discriminated union in Go, e.g. a struct with a type identifier and an
interface{} holding the value.

~~~
rbehrends
> Inheritance is not a way to express sum types, it's a form of subtyping.

A distinction without a difference. This is probably most visible in languages
like Scala and Kotlin, which implement algebraic data types by way of
inheritance.

That inheritance creates a subtyping relationship is irrelevant; there's a
similar subtyping relationship between variants (or groups of variants) and
the overarching type using a traditional sum type notation as in Haskell or
ML. This is most clearly visible in OCaml's polymorphic variants [1, 2].

[1] [http://caml.inria.fr/pub/docs/manual-
ocaml-400/manual006.htm...](http://caml.inria.fr/pub/docs/manual-
ocaml-400/manual006.html#toc36)

[2] [https://stackoverflow.com/questions/16773384/why-does-
ocaml-...](https://stackoverflow.com/questions/16773384/why-does-ocaml-use-
subtyping-for-polymorphic-variants)

~~~
mcguire
Pony ([https://ponylang.org](https://ponylang.org)) uses sum types, perhaps
excessively. Just yesterday, I wrote:

    
    
        (None | (In, USize))
    

I.e., None (the null valued type) or a pair made of a type variable (In) and a
USize.

The thing is, the values that satisfy this type are not subtypes of None and a
pair. (That would be silly, given None.) Such a value is either None, or a
pair.

~~~
rbehrends
> The thing is, the values that satisfy this type are not subtypes of None and
> a pair. (That would be silly, given None.) Such a value is either None, or a
> pair.

Unless I'm misreading you, this seems to be a misunderstanding of what sum
types are. A sum type `T = A | B` represents the (disjoint) union of all
possible values of `A` and of all possible values of `B`, simply put, not the
intersection (as you seem to indicate by the phrasing of "not subtypes of None
and pair"; correct me if you meant something else).

Recall what subtyping means (I'm going with Wikipedia's definition here for
sake of accessibility):

> _[S]ubtyping (also subtype polymorphism or inclusion polymorphism) is a form
> of type polymorphism in which a subtype is a datatype that is related to
> another datatype (the supertype) by some notion of substitutability, meaning
> that program elements, typically subroutines or functions, written to
> operate on elements of the supertype can also operate on elements of the
> subtype._

This holds in the case of sum types. Operations that work on the sum type will
generally also work on the variants that constitute the sum type.

The same goes for inheritance. If an abstract class T has two concrete
subclasses `A` and `B`, then a value of type `T` belongs to the union of
values of type `A` and of type `B`.

------
erikb
I'm no Go expert, so the following may just come from having not used it
enough.

The two things that shocked me out of continuing with Go were exception
handling and package management.

Exception handling is basically not implemented. Instead the Go developers
dediced to add half a step from return codes towards exceptions and work with
that. And now the community seems to have decided to just reraise everything
that comes their way. Nobodoy seems to see that such a simple basic feature
shouldn't require a piece of code, it should be done automatically by the
language. And the community doesn't seem to see why it's nearly impossible to
debug. I really hope that Go developers have some secret tools I just don't
know about, because there are no stack traces, and on the abstraction level of
a user getting an error from an underlying framework printed out doesn't
really help at all, especially when the code seems to continue after printing
the error (making it a bug, by reporting a warning as an error). My favorite
example is kubernetes helm reporting a connection error in my job's
infrastructure, every time you use it, but after that error it actually
switches from IPv6 to IPv4 and just works. But still it feels good about
reporting that error. wtf.

The next thing is package management. I had high hopes here after going
through the trouble of working on that topic in 2012 when the Python community
was developing its package management. I mean if you don't add that on top of
an existing world, but create it from scratch, there is a chance to just do it
right and not have to bother with all the pains of legacy systems, right?
Well, Go decided it doesn't need versioning on imports. Just put the Github
link there, don't even choose a branch. How can any code ever get finished
that way? My best guess is that now they have to develop stuff on top of that
already-born-as-legacy system and also try to integrate that with their core.
Sad.

I really hope someone can add some corrections to this view.

~~~
nicpottier
I'm about 8 months in to using Go for a few largish projects and I'd say these
are probably the two biggest things I still struggle a bit with. (not generics
as others seem to obsess about)

On errors, I'm really of two minds. In a way, it is a lot like how Java
started with checked exceptions, it forced you to deal with the error. But at
some point most people decided that was annoying and switched to runtime
exceptions for everything, which while requiring a lot less code, still led to
errors often bubbling up all the way to the user.

I think checked is the right thing, but it does require developers to be
thoughtful and not just throw errors upwards. If you accept that checked is
the route you want to go, I don't find Go's use of error values worse than
exceptions.

On the package management front, I think the current best practice for
projects that must not break is to vendor your dependencies. Go is working
towards having better tools to allow you to specify SHA's for your
dependencies easily but we aren't fully there yet.

While it took a bit to wrap my head around using `govend` to vendor my
dependencies, in the end it really hasn't ended up being a big pain point in
practice. I also never have to worry about a dependency either disappearing or
pulling a trick of shipping the same version # with different code. (or having
the repo/package manager be down, which anybody who has shipped a lot of code
will tell you has happened)

So yes, I agree these are both, weird, but they aren't deal breakers. For our
particular application, I really love Go, more so than I have any other
language in recent memory.

~~~
curun1r
> not generics as others seem to obsess about

One reason people obsess about generics is specifically because of error
handling. With generics, you could implement Result and Option types, which
make error handling significantly more sane.

~~~
alphaalpha101
Personally I loathe this style of programming. It's not that it's difficult,
it just seems to obscure code a great deal.

Writing this sort of thing in Rust:

    
    
        fun some_function(a: &A) -> Result<&B, SomeError> {
            let c = foo(a)?;
            let d = foobar(a, c)?;
            Ok(if xfoo(c) {
                let e = blah()?;
                bar(d, e)?
            } else {
                baz(d)?
            })
        }
    

where you have to write every function in this pseudo-do-notation where
'return' is just wrapping the return expression in 'Ok' and `a <\- expr`
becomes `let a = expr?;` is just horrible.

I'd much rather write this:

    
    
        fun some_function(a: &A) -> &B throws SomeError {
            let c = foo(a);
            let d = foobar(a, c);
            if xfoo(c) {
                let e = blah();
                bar(d, e)
            } else {
                baz(d)
            }
         }
    

See how that's so much cleaner? It's not actually any different from
exceptions anyway, you're basically using them like exceptions, and they're
implemented in the same way. The difference is that in the latter the code is
much simpler and easier to understand. That's all.

In fact, that syntax could be added to Rust (after 6-12 months of bikeshedding
as usual) and just have it automatically translated to the above anyway.

The other issue with Result/Option is that people start doing really horrible
things like adding Option::map. Sorry but it's not a container that has 0 or 1
things in it. It's an optional value. That they're mathematically equivalent
doesn't mean that they're the same thing conceptually. It's as bad as
pretending that Result<T, Err> is useless and everyone only needs Either<L, R>
where by convention R is the error value. God please just no.

~~~
curun1r
> See how that's so much cleaner?

No, I don't. I look at the former snippet and I can easily tell each and every
function invocation that can cause SomeError. In your theoretical style, I
have no idea whether foo, foobar, xfoo, bla, bar and/or baz will throw that
error. I prefer explicit over implicit since I find it far more readable.

> really horrible things like adding Option::map

You can quibble about the names (Option and map), but Option is essentially
the Maybe monad and map is bind, so you're kinda arguing against core
functional programing constructs.

~~~
alphaalpha101
>No, I don't. I look at the former snippet and I can easily tell each and
every function invocation that can cause SomeError.

The reason that functions have type signatures is that you can read them. You
can tell which functions can cause SomeError by going and reading their
definitions.

>I prefer explicit over implicit since I find it far more readable.

'Explicit over implicit' is dogma. Rust requires you to annotate your code
with gibberish in cases where it is not necessary.

>You can quibble about the names (Option and map), but Option is essentially
the Maybe monad and map is bind, so you're kinda arguing against core
functional programing constructs.

That's literally my entire point. The attitude that it's technically a Functor
so it makes sense for it to be called map? No, it doesn't. It's not a map.
You're not mapping over anything. Naming is important.

Calling it 'the Maybe monad' shows that you actually have no idea what you are
talking about. It's not 'the Maybe monad'. The Maybe monad is the instance of
Monad for Maybe. It is _not_ Maybe itself.

The entire concept of having the literal 'Monad' word as a word in your
language, a thing that you use in programming, is very stupid. Monad is not a
useful or good abstraction. Maybe is a good abstraction. Or Optional, or
Option, or whatever you decide to call it. But Monad is a bad abstraction.
Abstracting over superficial syntactic similarities between completely
different constructs is completely stupid.

The name being terrible is not 'quibbling' by the way. Naming is incredibly
important. Calling it 'map' just shows how out of touch Rust is with real
programmers.

------
znq
Don't want to hi-jack the article, but we've built Bugfender using Go. The
problem is, we thought this is an internal (experimental) project and so Go
(as a new language) would be fun to try out. But then Bugfender started to
take off and we ran into serious problems making Go scale. Not because it is a
bad language, but simply we were new to it ourselves.

Today we're still running on go and we're more or less "ok" with it now, but
it was a difficult path to get there and while it's fun to use a new language
(or new anything) it's probably not the best choice for a startup product.
Also when you need to hire people.

We've summarized our experiences here:

\- [https://bugfender.com/blog/one-year-using-
go/](https://bugfender.com/blog/one-year-using-go/)

\- [https://bugfender.com/blog/go-pros-cons-using-go-
programming...](https://bugfender.com/blog/go-pros-cons-using-go-programming-
language/)

\- [https://bugfender.com/blog/three-years-
bugfender-9-5m-users/](https://bugfender.com/blog/three-years-
bugfender-9-5m-users/)

~~~
IshKebab
I would have thought Go is easy to hire people for. It's such a simple
language and has such a well-designed standard library you can pick it up
extremely quickly.

I think "anyone can learn it" is one of its main benefits. Compare that to
Rust or Haskell for example...

~~~
lordCarbonFiber
I think the issue is finding people that _want_ to learn it. Systems
programmers are turned off because of it's limitations and your average
pythonista or rubyist isn't interested in mucking around in type definitions
or even thinking about concurrency.

~~~
chimeracoder
> Systems programmers are turned off because of it's limitations and your
> average pythonista or rubyist isn't interested in mucking around in type
> definitions or even thinking about concurrency

Systems programmers were the bulk of Go's early adopters, even before it hit
its first stable release.

I can't speak for Ruby, but as for Python - I've been writing Go
professionally for five years, and my first talk on Go was actually at the New
York Python Meetup. They specifically asked me to speak about Go because they
had significant interest from their members in learning or using Go. This was
back when Go was still on its 1.0 release.

Soon after that, Python added type hinting and concurrency to address needs
that Python programmers had. That was enough for some, but there clearly has
been interest from Python programmers in Go's approach, and there continues to
be; Python is one of the more common language backgrounds for new Go
programmers that I see.

Furthermore, there's no shortage of experienced developers who already know
Go. While the numbers are evening out a bit as more companies have begun to
adopt Go, there are still more experienced Go programmers looking for jobs
where they can write Go full-time than the other way around. (And that's not
even looking at people who are inexperienced programmers or experienced
programmers who don't know Go but might be interested in learning.)

Go may not be for everyone, and that's fine, but there's really no shortage of
good programmers who can write Go. If a company is evaluating languages and
considering Go, availability of talent is a _selling point_ , if anything, not
a concern.

~~~
unscaled
My subjective impression (supported by informal surveys [1]) is that most
Gophers come from a Python or Node.js background, doing mostly network code,
DevOps automation or CLI tooling. Rubyists are less likely to fall in love
with Go, given that the huge philosophical gap, though the pragmatist among
them adapt go for what it excels at (performant microservices and statically
linked binaries).

Still, most newcomers to Go seem to come from dynamic typing background, and
it shows with all the buzz about Go being 'strongly-typed' and 'helping you to
detect typing errors', which sounds crazy to anyone who played with C++ or C#,
let alone an ML-family functional language.

And yes, Go also has a small but prominent contingent of C developers, and
while some of them _also_ do systems programming, this is generally not what
they do in Go. This crowd seems to be mostly focused on tools and networking-
oriented code, two areas where Go excels. The thorough standard library and
top notch static linking support make go a hard to beat choice for this type
of work, but its a less obvious choice for traditional systems programming,
which usually still eschews garbage collection.

You'll rarely find Go being used in most of the traditional systems loads:
drivers, kernels, high-spec graphic engines, emulators, JIT compilers,
filesystems, browsers and definitely not in real-time programs. The one
traditional systems realm where Go does see some activities is lightweight
databases and key-value stores (BoltDB, TiDB), but databases performance is
often dependent on concurrency no less than it depends on memory, and in fact
Java has been long popular for NoSQL databases (HBase and Cassandra), and even
way more niche languages like Erlang gave rise to popular NoSQL solutions
(Riak, CouchDB).

[1]
[https://blog.golang.org/survey2016-results](https://blog.golang.org/survey2016-results)

------
unbearded
> 1\. It is possible to have both dynamic-like syntax and static safety IHMO:
> This convenience causes more harm than good in the long term. Note: My
> experience is that this is a minefield of bugs and defeats the point of type
> safety. Also, because Go lacks generics, there's a lot of boiler plate and
> interface being used as function argument (just check large open source
> repos on github and you will notice).

> 2\. It’s better to compose than inherit IHMO: It's better to have both tools
> available. Note: Inheritance, like static types, offer a more rigid
> structure and helps on large project that will exist in the long term. In a
> language that does not offer something like traits, "composition of
> behavior" ends up being a hack. Of course, we also have the tendency of
> blaming the language for the fault of the programmers. I would think the
> example of the Vehicle is not the best to explain the paradigm of behavior
> composition.

> 3\. Channels and goroutines are powerful way to solve problems involving
> concurrency IHMO: Powerful, yes. But I would use actors instead. Note: Same
> problem: as your business problem gets complex, goroutines start to become a
> pain and the code will become unreadable and littered with hacks.

> 4\. Don’t communicate by sharing memory, share memory by communicating. I
> read other comments here that express what I think better than I can
> articulate Note: You still have to "synchronize" if you are working with
> shared resources. There's no magic solution.

> 5\. There is nothing exceptional in exceptions Oh, Boy! This causes more
> wars than "space vs tab". IMHO: Good in theory. Too rigid in practice. Note:
> While works for small stuff, the the lack of exceptions becomes a problem in
> large projects, causing people to just ignore "for now" (and usually,
> forever).

In conclusion: The "cool features" of Go might teach some people about
programming, but take a toll on more complex projects. I would use Go for
small system utilities and tools but would never touch business logic with it.

~~~
mjkpl
Hi, author here. Thanks for your opinion.

> 1\. On a daily basis I work in a 200k LOC Ruby project and to me from that
> perspective lack of static typing is a minefield ;)

> 2\. Since I have finally learned how to do proper composition (~a year ago)
> I haven't use the inheritance even once. Of course, you may say that my
> project is special, but I can't help feeling that inheritance is often
> overused.

> 3\. Yes, I've also come across opinions that Go's channels are too low level
> to be used in a large commercial project. But still, as a concept I find
> them interesting.

> 4\. True. But, you can have one goroutine that'll just "guard" that resource
> and communicate with it from many places using one shared channel.

> 5\. TBH I've seen more flame wars about the "space vs tab" thing ;) As I
> mentioned in the article I don't think that's the best error handling
> pattern ever invented, but I just like the concept of treating errors as
> regular return values. IMO it's good to have it at the back of your head,
> regardless of the language you use.

------
brango
"There is nothing exceptional in exceptions"

No there's not, but it's a royal pain the arse to have to keep passing them up
through your function calls to the level that actually cares about them and
will do something about them. Try/catch eliminates boilerplate.

~~~
ordu
Doesn't Go have any syntactic sugar for pass errors out of function? Something
like Rust's Carrier/From traits and try! or `?`.

~~~
camus2
No, go "errors" are just a convention, there is absolutely nothing special
about them.

Go has panic/defer, which are exceptions, but done badly. They were probably
retrofitted in the language when its creators realized they needed exceptions
anyway, which makes the whole "error as value" a bit hypocritical. If they
truly cared about error as value, then yes, Go would support some form of try!
macro.

~~~
stevedonovan
Yes, Rust would be seriously irritating without try! (and its short friend the
question mark operator). So I feel the pain of gophers here.

------
krylon
Minor nitpicks, from someone who really likes Go:

> It is possible to have both dynamic-like syntax and static safety

Well, you can learn that one by coding in C#, too. In fact, I have the feeling
this is a general trend across several relatively popular languages these days
- provide as much as possible of the benefits of dynamic typing while keeping
the benefits of static typing.

I sometimes think how nice it would be if I could write my code entirely
without type declarations and have the compiler or some preprocessor figure
out as much of the type information as possible.

> It’s better to compose than inherit

It depends, really. Personally, I think composition is the simpler solution
more often than inheritance, but sometimes it is not.

I completely agree about the error handling, though - at first it was very
tedious to handle all errors explicitly, but after I while I came to
appreciate it. Once I had fallen into the habit of checking for errors without
having to think about it too much, detecting errors became much easier, and
deciding if I could "deal" with some error or escalate it (possibly to the
point of terminating my program) became more straightforward, too.

~~~
skocznymroczny
> In fact, I have the feeling this is a general trend across several
> relatively popular languages these days - provide as much as possible of the
> benefits of dynamic typing while keeping the benefits of static typing.

Some languages go the opposite way. Dart 1.0 has optional typing, but Dart 2.0
will be statically typed (with type inference though).

~~~
rbehrends
> Dart 1.0 has optional typing, but Dart 2.0 will be statically typed (with
> type inference though).

Not quite; it's more that compile time typing is getting cleaned up. You can
still omit types, which will then either be inferred or set to `dynamic`. The
following is valid strong mode Dart:

    
    
      f(n) {
        if (n <= 1)
          return 1;
        else
          return n * f(n - 1);
      }
    
    

The following will no longer work:

    
    
      int x = "foo";
    

In short, you can still omit types. It's just that _if_ you declare them, they
are enforced [1]. Dart will also infer them if possible, i.e. the following is
illegal:

    
    
      var x = 1;
      x = "foo";
    

Here, x is inferred to be `int`, which makes the assignment of a string
illegal. However, the following works:

    
    
      var x = true ? 0 : [];
      x = "foo";
    

Here, the type of `x` cannot be inferred, so it becomes `dynamic`, and
therefore the assignment of "foo" becomes valid.

You can turn this off with --no-implicit-dynamic; with this option, all types
must either be declared or have to be inferable.

[1] Sometimes not at compile time, though: Dart allows implicit downcasts and
covariant generics at compile time and will insert runtime checks to catch
those situations.

------
oelmekki
I came to really dislike embedding interfaces and types, but I know I'm a
minority opinion, on this.

When I came to go from OO (ruby), and before I learnt to use embeddings,
something blew my mind : I could always say where a function was coming from,
and which functions were available in the current scope. No need to grep
anymore to find from which parent class or which included module a method was
coming from.

And after I started using embedding all over the place, I realized that
problem was back again. Go made me love the idea of "simplicity, not easiness"
and the fact that I can get what code was doing immediately, without wondering
where parts were coming from. Today, I prefer to avoid embedding altogether. I
use function parameters instead of types, and this also has the advantage of
avoiding initialization problems (and if I omit a parameter, I'm immediately
warned by compiler).

------
amelius
> Don’t communicate by sharing memory, share memory by communicating.

Well, if you're using immutable data-structures, then structural sharing can
be very useful. You can then pass large data-structures in constant time, and
of course you can "modify" them efficiently using immutable techniques.

As long as you don't perform any writes in a shared data-structure, the memory
hierarchy should be perfectly happy and no delays or locks should be
necessary.

Of course, as you start scaling across multiple machines, there will be
different trade-offs, as there will not be a shared memory bus.

~~~
masklinn
You're not "communicating by sharing memory" though, at least in the idiom's
sense (in which "memory" is an alias for "mutable state"), the structural
sharing is just an optimisation.

Which may not even be desirable: despite being built entirely upon immutable
data structures, Erlang only share large binaries between processes (AFAIK
even the new maps are copied despite being HAMT) to ensure process heaps and
thus garbage collections are completely independent.

~~~
amelius
> Erlang only shares large binaries between processes to ensure process heaps
> and thus garbage collections are completely independent.

That sounds unnecessarily restrictive. At least they should give developers a
choice (e.g., "this process should receive integral data, and should not be
disturbed by a GC cycle of other processes").

Also, concurrent (not stop-the-world) GC techniques could make this problem
moot.

~~~
masklinn
> Also, concurrent (not stop-the-world) GC techniques could make this problem
> moot.

It would mostly introduce insane additional complexity. Erlang GC works per-
process (each process has its own private heap and stack) and you'd normally
create lots of small processes, so the GC is concurrent as an emergent effect
of the system construction.

Not to mention processes can be distributed across nodes for which your scheme
completely breaks down, what's supposed to happen if you ask for memory
sharing across the network?

~~~
amelius
> It would mostly introduce insane additional complexity.

GoLang has a concurrent garbage collector.

> what's supposed to happen if you ask for memory sharing across the network

Yeap, I mentioned that. Again, as a developer you want the choice. You don't
want your language telling you "sorry, that's too complicated for your brain,
so you can't do that".

~~~
masklinn
> GoLang has a concurrent garbage collector.

Golang also has a single, shared, mutable heap, it does not have tens or
hundreds of thousands of individual heaps.

> Yeap, I mentioned that.

No, you did not.

> Again, as a developer you want the choice.

Er… no?

------
suchabag
> Authors of Go wanted to give users more flexibility by allowing them to add
> their logic to any structs they like. Even to the ones they’re not authors
> of (like some external libraries).

Except that you can't. "You "cannot define new methods on non-local type[s],".
You have to compose them. That makes the struct function definition syntax a
bit moot in my opinion.

~~~
majewsky
I've never thought of that syntax as conveying a particular statement. They
could also have done

    
    
      func *Receiver.method(arg int)
    

instead of

    
    
      func (r *Receiver) method(arg int)
    

The advantage is that you get to name the receiver instead of having to use a
keyword name like `this` or `self`. I've come to like this design decision.

~~~
icebraining
_The advantage is that you get to name the receiver instead of having to use a
keyword name like `this` or `self`. I 've come to like this design decision._

self, at least if you're talking about Python, is not a keyword, it's just a
convention. You can use whatever your want.

~~~
leshow
It's not just python that uses self. Rust, for example, it's sugar for self:
Self

------
Ixiaus
"Error theater" and zero initialized values, while understandable due to other
design decisions, are the biggest source of frustration whenever I have to
work on go code.

Off topic from the article, I think, but what go has taught me about
programming is that some parts of our industry are stuck in time and
intellectually stagnant.

Go is a better C. I would prefer Go over Python. But really, when the state of
the art (and production ready!) is light years ahead of Go, it frustrates me
to no end to see colleagues spending so much time on things in their day to
day that are solved problems in other languages.

~~~
alphaalpha101
This sort of attitude is never going to win over people that you are talking
about. I hate to bring politics into things, but it's like calling all Trump
supporters hopeless and backwards. Yeah maybe they are, but they're going to
react to that by never listening to anything you say again, so even though
it's true, it's not helpful.

The 'state of the art' is increasing, over-the-top complexity to a truly
ridiculous level. Go programmers don't want to open up the documentation for a
library and see this:

[http://i.imgur.com/ALlbPRa.png](http://i.imgur.com/ALlbPRa.png)

or look at the language reference and see this:

[http://en.cppreference.com/w/cpp/language/constraints](http://en.cppreference.com/w/cpp/language/constraints)

and I don't blame them.

------
andreareina
> Thanks to goroutines and channels Go programmers can take a different
> approach. Instead of using locks to control access to a shared resource,
> they can simply use channels to pass around its pointer. Then only a
> goroutine that holds the pointer can use it and make modifications to the
> shared structure.

How does this prevent data races if more than one goroutine holds a pointer to
the shard structure and they're running concurrently?

~~~
innocentoldguy
It doesn't. I think goroutines make concurrency easier that it is in, say,
Java, but Go still passes around state and that is where a lot of the issues
with concurrency arise. Concurrency is much easier to deal with in functional
languages, where data is transformed via chains of functions, rather than
stored in state.

~~~
alphaalpha101
The difference is that in Go, it is unidiomatic to share mutable state across
threads, while in C/C++/Java it's not.

