
Why Go is elegant and makes my code elegant - lvevjo
http://filippo.io/why-go-is-elegant-and-makes-my-code-elegant/
======
jzelinskie
I think elegant is probably the wrong word. Practical is the one I would use.
Go isn't Haskell; it isn't there to impress the PL guys. They boiled down the
average imperative language and made many decisions for you so that opinions
don't have to collide while working on a project. Because the decisions they
made, they have very nice tooling to go along with the language.

~~~
copergi
Elegant is definitely the wrong word. Scheme is elegant. Go is simplistic. Not
the same thing.

~~~
letstryagain
Simplistic is a negative word, it means over simplified. You probably mean
'minimalistic'?

~~~
copergi
Nope, your definition of simplistic is precisely what I meant.

------
kyrra
Some of the pain points I've seen:

* while "go get" is nice, it doesn't handle version pinning at all. So for a large team working on a go program, you need another solution. Luckily there are tools like godep[0] that will allow version pinning and putting it's source code into your projects root.

* debuggers (gdb) are only partially supported. Most people don't have problems with this, but occasionally it's an issue.

* Windows support is not as strong as Linux and OSX. But it still works surprisingly well.

[0] [https://github.com/tools/godep](https://github.com/tools/godep)

~~~
mitchellh
A serious question (not saying you're wrong, I'm just genuinely curious): What
issues have you run into with Go and Windows? I've been shipping libs and
programs written in Go for Windows without issue.

~~~
kyrra
As for debugging, there were some comments about this recently on golang-nuts.
A discussion about debugging[0]. Also Rob Pike saying GDB support is 'poor'[1]
and there are bugs[2] that aren't a high priority to fix. Though, as many
people pointed out on the mailing list, print statements and unit tests will
cover 95% of your issues.

As for windows support, I should say that it's really just as good as the gnu
toolchain support is on windows. There is no support for using Microsoft's
toolchain on Windows. It can also take some work to make go apps not run
within cmd terminal. I saw a blog post about it recently but I can't find it
right now. But it also mentions things like the syscall[3] library docs on
golang.org are actually for Linux, and it takes some steps to get them for
other platforms, see the header comment here[4].

So Windows support is pretty good, but it is definitely not Google's focus.
But if you've ever heard about what Windows is like within Google, there is a
good reason for that.

[0] [https://groups.google.com/forum/#!topic/golang-nuts/YG-
APRPw...](https://groups.google.com/forum/#!topic/golang-nuts/YG-APRPwkZc)

[1] [https://groups.google.com/forum/#!msg/golang-
nuts/DS5ZGswXC6...](https://groups.google.com/forum/#!msg/golang-
nuts/DS5ZGswXC68/4kBgwVaAApYJ)

[2][https://code.google.com/p/go/issues/list?can=2&q=label%3AGDB](https://code.google.com/p/go/issues/list?can=2&q=label%3AGDB)

[3] [http://golang.org/pkg/syscall/](http://golang.org/pkg/syscall/)

[4]
[http://golang.org/src/pkg/syscall/syscall.go](http://golang.org/src/pkg/syscall/syscall.go)

~~~
mitchellh
I still don't see any problems with Windows support here. Debugging is a
problem, actually, but other than that everything seems fine. The syscall
interface gives you access to call any Windows API you want.

I wouldn't imagine writing a GUI app in Go, Go just isn't made for that and it
would be silly since Microsoft's .NET tooling is so darn good. But Go for
services and command line apps for Windows it has been fantastic.

------
wyager
This may be an unpopular opinion, but I hope Go is relegated to web services
programming. It's really not a good language for much else. The lack of
generic support is really terrible. What you have to do instead (cast things
to {}interface) is like casting things to Object and then hoping they have the
right methods in Java.

For everything else, I think Rust is miles better than Go. It stole a ton of
useful features from Haskell/ML and did a great job of putting them together.
I actually _want_ to use Rust instead of C++, whereas the only reason I ever
really want to use Go is that its decent threading primitives and good
standard library make for simpler web apps.

~~~
NateDad
One of Go's major features is that any particular function is very easy to
reason about. The code is very straightforward, there's very little magic, and
the code usually does exactly what you expect it to do, even if you're not an
expert in the language.

Rust seems nice, but its multitude of different pointers and mutable versus
non-mutable data really makes for complicated code.

I work on Juju. It's over 200k lines of code spanning several applications
that coordinate across multiple machines. The number of times generics would
have helped the codebase I can count on a single hand... and none of it is
huge stuff, just some very minor parts of the code.

~~~
wyager
How is go easier to reason about than competing languages?

~~~
NateDad
For one thing, it's just a simpler language. There are fewer keywords, there's
only one kind of pointer (and no pointer arithmetic). Everything is just pass
by value. There's very rarely anything going on "behind the scenes". The code
does what it says it does. There aren't any dense one liners that do a whole
bunch of stuff. Values are very easy to reason about. You know exactly how
values will be initialized, how much memory they take to copy, when they'll be
copied.

There's no operator overloading, macros, or monkey patching, so you don't have
to know about the entire state of the universe to know what one little
function will do. There's no destructors or finalizers that magically get
called sometime in the future.

There's no exceptions, so you never have to worry that the control flow of
your program is going to jump all over the place. There's multiple returns, so
you almost never have null pointer exceptions, because you always just check
the error before using the value returned.

And of course, channels and goroutines make modern multithreaded code very
easy to reason about, because they're just functions and a little pipe
primitive. They don't do a lot of fancy schmancy stuff, and that's good. It
means it's very easy to tell what go Foo() will do.

There's probably more I can't think of right now.

~~~
wyager
>For one thing, it's just a simpler language.

In what way? It's not a very featureful language, but that doesn't necessarily
mean that using it is simpler.

It's certainly not simple to set up! Most languages, I can put my projects
wherever I want. It took me a while to find out that I had to put my projects
_in_ my Gopath for them to properly compile with third party libraries. There
are lots of weird restrictions like that.

>Everything is just pass by value.

Not entirely true. Goroutine capture is by reference, which is why a lot of
new people have trouble with goroutines in for loops.

>There's no operator overloading, macros, or monkey patching, so you don't
have to know about the entire state of the universe to know what one little
function will do.

True, those things are gone, but the things that cause confusion _most_ of the
time are still very much present. Go, like most imperative languages, can
suffer from global variable overload. Lots of Go code is also so badly typed
that it's impossible to tell what things are supposed to be. I can't tell you
how many times I've seen `x := poorlyNamedFunctionWTFIsThis()` or `foo(x
{}interface){... x.bar()}`

>There's no exceptions

There are `panic()`s.

> There's multiple returns, so you almost never have null pointer exceptions

Rust also has multiple return (because Rust returns via hidden out-param), so
I wouldn't really say this makes Go particularly competitive.

Rust also doesn't even have null pointers at all, at least in `safe` code
blocks. Rust uses `Option` instead. (Like I said, lots of good features from
Haskell/ML.)

>channels and goroutines make modern multithreaded code very easy to reason
about

I agree, Go does this well, but let's be fair; a lot of modern languages have
primitives like this.

~~~
kibwen

      > Rust also has multiple return (because Rust returns via 
      > hidden out-param)
    

Not quite. Rust does have multiple returns, but that's just the obvious result
of supporting tuples in the language (to say nothing of tagged unions). The
hidden out-param you speak of is just there to guarantee return-value
optimization, allowing the compiler to construct return values in higher stack
frames to avoid the overhead of copying.

~~~
wyager
>Rust does have multiple returns, but that's just the obvious result of
supporting tuples in the language (to say nothing of tagged unions).

Yes, but it's (from a user standpoint) practically the same thing!

>The hidden out-param you speak of is just there to guarantee return-value
optimization

Yes, which allows for the efficient return of arbitrary-sized structs, i.e.
multiple return.

------
anonymoushn
OP has never heard of panic() and recover(), which are awful things that
should not exist.

I'll speculate that he hasn't used a language with a usable sum type either,
because I'd expect people who have been exposed to this would take issue with
the idea of expanding every 5 line method to 40 lines of mostly error
handling.

~~~
rubiquity
> _OP has never heard of panic() and recover(), which are awful things that
> should not exist._

OP is using panic in the Heartbleed script he wrote. Source highlighted here:
[https://github.com/FiloSottile/Heartbleed/blob/master/bleed/...](https://github.com/FiloSottile/Heartbleed/blob/master/bleed/heartbleed.go#L33-L47)

~~~
FiloSottile
Please consider that I wrote in a time measured better in minutes.

~~~
rubiquity
I'm not criticizing your use of it, just showing the other person that you do
know what panic is because you used it.

------
vectorpush
I love go, and I concur with almost everything the author wrote here. A small
pain point in my experience though is that Go lacks an elegant way to pass
type information around without using a zero valued (or fully populated, it
makes no difference) instance. For example, I have a factory struct with a New
'method' that returns interface{MySignatures()}, but I cannot inform the
factory of the concrete type I want it to produce without passing in something
like new(ConcreteStructWithMySignatures). Some have suggested I pass in a
string, but what is the point of a type system if I have to use strings to
reason about my types?

~~~
melvinmt
You're probably using Go in The Wrong Way®. It's hard to fit classical object
oriented design patterns onto Go. A factory method that produces structs with
dynamic types is an example of that.

~~~
vectorpush
I created this contrived example to illustrate the basic idea behind the
pattern I want to emulate.

[http://play.golang.org/p/7T2s8Dn0Sr](http://play.golang.org/p/7T2s8Dn0Sr)

Ideally, on line 57 I'd pass in some kind of "first class" type representation
that the switch statement could use to branch into the correct type, rather
than a zeroed (or not, Go doesn't care) struct or a string. Admittedly, this
could definitely be abuse of Go semantics, so I'm also interested in how I
could refactor this into a more canonical Go pattern.

~~~
melvinmt
Well, at line 57 you already know which provider you want to use, so why not
just initialize the struct from there and skip the factory method altogether.

[http://play.golang.org/p/vXJcMJ-j6P](http://play.golang.org/p/vXJcMJ-j6P)

Edit: I think I see what you're trying to do with the factory method. You're
trying to enforce the ServiceProvider interface on every provider struct. You
don't need to do that yourself, the compiler does that work for you. On line
57, you already know what kind of struct you're dealing with so you can safely
call .connect() on the provider.

And if you need to pass on the provider to another method, just make sure the
method only accepts structs with a ServiceProvider interface. The compiler
will stop you when it's not the case.

~~~
vectorpush
I was in a rush and didn't create the most illustrative example.

 _you already know which provider you want to use, so why not just initialize
the struct from there and skip the factory method altogether._

The reason that I want ProviderFactory to generate instances is because I also
want it to act as a manager for those instances. ProviderFactory can keep
track of all ServiceProviders and perform various operations or broker data
between instances.

Here is my previous example slightly improved.
[http://play.golang.org/p/XoXu9x9r8f](http://play.golang.org/p/XoXu9x9r8f)

Now, ProviderFactory injects configuration information into ServiceProvider
instances based on their concrete type, before establishing a connection. A
more complicated app might wrap the NewProvider function into a
gorotune/callback that blocks each provider instantiation until a successful
connection has been established. This is all beside the point though; all I
want is something akin to a special "type" type that the compiler recognizes
as a symbol for that type that the factory can use to evaluate which concrete
type to produce.

~~~
NateDad
Your code seems incredibly backward. Why does the ProviderFactory hold the
knowledge about the query interval for each provider? Shouldn't the provider
hold that information? With the data structure you have, any time you add a
new provider, you'd need to add a new case to that switch statement. That's
like the polar opposite of separation of concerns. Let the query interval be
defined on the type, not in the factory.

The same for the host they connect to. Shouldn't that be defined in the type?
Like make WeatherProvider.Connect() call ProviderFactory.WeatherHost() to get
the host to connect to.

You're doing everything inside out. What do the types even do in your example?

This isn't a Go problem, it's programmer problem.

~~~
vectorpush
_This isn 't a Go problem, it's programmer problem._

Thanks, but just to be clear, this is not a sample from a real application,
it's just a contrived demo designed to illustrate the pattern.

 _Why does the ProviderFactory hold the knowledge about the query interval for
each provider?...Let the query interval be defined on the type, not in the
factory._

As previously stated, the example is contrived. If this were a real
application it could probably make sense to define the query interval on the
specific type, but lets just say, for the sake of illustrating the idea, that
the desired interval for a specific provider could depend not only on the type
but also the total number of providers already allocated for a given type.

For example, perhaps the ProviderFactory might calculate the total number of
WeatherProviders already defined, and reset the query_interval for all
WeatherProviders to be (some_weather_provider_specific_constant *
total_number_of_allocated_weather_providers). Whenever a new WeatherProvider
is requested from the factory, the factory increases the query_interval for
all WeatherProviders. In this way, the ProviderFactory acts as an automatic
rate limiter for each provider by keeping track of each instance.

 _With the data structure you have, any time you add a new provider, you 'd
need to add a new case to that switch statement._

Well yeah, that's pretty critical to the "factory" role of the
ProviderFactory, how else could the factory return a variety of types if it
didn't have a case/branch for each signal that represents each type?
Additionally, if individual logic is required for preparing a certain type of
provider, the switch statement is there to accommodate the needs of the
specific type before returning it.

 _That 's like the polar opposite of separation of concerns._

How do you figure? In this example, the factory is concerned with all logic
related to instantiating and appropriately configuring each instance depending
on the type of instance, the state of the app or the state of other instances.
I'm not sure what concerns are mixed here. Alternatively, I _could_ export all
that logic into a NewFooProvider function for each of the various
FooProviders, but the idea is to encapsulate all that custodial work within
the factory so that a Provider user only has to request their specific
provider and _that 's it_.

 _What do the types even do in your example?_

Nothing. It's just an example.

 _The same for the host they connect to. Shouldn 't that be defined in the
type? Like make WeatherProvider.Connect() call ProviderFactory.WeatherHost()
to get the host to connect to._

I think you're missing the point. Yes, yes, that design would work fine, but
all you're really telling me here is "you don't need to use a factory in this
example". What I'm saying is, _when_ I want to use a factory (lets just assume
that it makes sense in the design of my app, unless your argument is that a
factory is never useful), I have to indicate _which type_ of instance I want
the factory to produce by using a zeroed struct or an arbitrary data type,
what's going on within my factory is actually irreverent; any example factory
snippet could be refactored into something less complex when your only context
is 100 lines in a scratch pad.

 _You 're doing everything inside out._

I'll seriously take that opinion into consideration.

~~~
NateDad
Uh yes, probably a factory is never going to be an optimal solution. If you
know what _type_ you need, why would you need a factory to create it for you?
Just create it. You might need a generic initializer function that sets
various values on the type you create, but there's pretty much never a reason
to have a function create a variety of types for you.

~~~
vectorpush
_Uh yes, probably a factory is never going to be an optimal solution._

Never? That seems a little extreme, no? It's a tool like any other and IMO,
it's a sometimes useful idiom that works pretty much identically in Go as it
does in other imperative languages. However, I'd be interested in any links
you could provide which might elaborate on why a factory is never an optimal
solution.

 _If you know what type you need, why would you need a factory to create it
for you? Just create it._

I already explained why. The Factory abstracts away instantiation and
configuration details for a complex or dynamic initialization process. I
understand you don't agree with that approach, but it's fairly common.

(see page 5)
[http://www.cs.colorado.edu/~kena/classes/6448/f07/lectures/2...](http://www.cs.colorado.edu/~kena/classes/6448/f07/lectures/21/21-Factory.pdf#page5)

(see accept answer)
[http://programmers.stackexchange.com/questions/81838/what-
is...](http://programmers.stackexchange.com/questions/81838/what-is-the-
difference-between-the-factory-pattern-and-abstract-factory)

 _but there 's pretty much never a reason to have a function create a variety
of types for you._

The capability to define a function's return type to as an interface type
would suggest otherwise, but we'll have to agree to disagree in that regard.
:)

~~~
NateDad
_The Factory abstracts away instantiation and configuration details for a
complex or dynamic initialization process._

Is there some reason you can't do that by just having this?

    
    
      func IntializeFoo(f *foo)
    

_The capability to define a function 's return type to as an interface type
would suggest otherwise_

Uh... if you know what type the function is returning you, why do you need it
to return an interface? You don't need the interface until you pass it into a
function that requires an interface, at which time, the type you've made will
be converted for you... why convert before you need to?

Either the consumer should never need to know about types, in which case, you
should probably just make it an enum:

    
    
      type ServiceType int
      const (
          Weather ServiceType = iota
          News    ServiceType
      )
    
      type Service interface {}
    
      func Service(type ServiceType) Service
    
    

Or if the consumer _should_ know about the service types for some reason, you
can use the Initialize method above... or to genericize it better, and remove
any need for the factory to know about concrete types:

    
    
      func Initialize(svc Service)
    

If you can avoid having both sides know about concrete types, it'll make your
life a lot easier.

------
TylerE
It's elegant right up until you need generics. Then it gets ugly fast.

~~~
charlieflowers
What's up with the rampant downvoting the past week or so? I see all kinds of
posts downvoted that shouldn't be (like this one). Seems like it has really
taken off the past week or so.

Downvoting is not something you do merely because you disagree. Valuable,
intelligent discussion of complex topics that matter _requires_ some
disagreement. Downvoting should be saved for comments that are inappropriate
in some manner according to the values of the site.

~~~
abtinf
Downvoting to express disagreement is appropriate on HN. See pg's comment:

[https://news.ycombinator.com/item?id=117171](https://news.ycombinator.com/item?id=117171)

~~~
jnbiche
That's a single comment from pg from the _very_ early days of HN. And that's
not how things developed organically. Instead, downvoting comments has been
reserved for comments that violate community standards.

Here's a more recent comment from pg in which he describes ideal comments, and
the collegial atmosphere he's been trying to promote for HN [1]. In my
opinion, downvoting a well thought-out comment made in good faith is not very
collegial.

1\.
[https://news.ycombinator.com/item?id=7445761](https://news.ycombinator.com/item?id=7445761)

~~~
gress
Nope. Flagging is for comments that violate standards.

~~~
jnbiche
You flag a comment every time you think it breaks a community standard? Like,
every silly "me, too" comment and one-line meme comment?

If that's the case, I can almost guarantee you that your flags don't carry a
lot of weight in HN's algorithmic ratings.

~~~
gress
I do not. I am just pointing out that that's what they are for. Of course
drivel comments are also likely to be downvoted heavily, but that doesn't mean
that this is the only intent of downvotes.

Do you know the rating algorithms?

------
al2o3cr
Having uniquely-named packages is insufficient unless the maintainers of those
packages never update them. The point of tools like Bundler is not about name
resolution, it's about VERSION resolution.

Sample size of 1, but here's a story about what happens when things go wrong:

[https://www.kickstarter.com/projects/2066438441/haunts-
the-m...](https://www.kickstarter.com/projects/2066438441/haunts-the-manse-
macabre/posts)

The bit about packages from github.com being edited _locally_ was particularly
hair-raising. Really hope the smart folks working on Go figure out a
resolution for this kind of thing.

~~~
NateDad
The bit about packages being edited locally shows the problem was between the
chair and keyboard, not in the language. That's like making edits to some
local files and never pushing them up into source control. Of course it'll
never work outside your own local machine.

There are practices and tools for insulating yourself from changes to third
party packages. Generally the answer is to copy the code into your own repo so
that you control when it gets updated. There are tools that will do this
automatically for you, and it's what most big Go projects do.

------
chris_va
The Author touches on a lot of nice points, but does not address some of the
problems with using Go.

Anecdotally, from using Go for a number of projects... As a rough
approximation, I think Go's marginal return tops out at about 1000 lines of
code. Past that point, syntactically it is hard to do abstraction (interfaces
or generics).

As a result, I use it for scripting but not for larger projects.

What about you all?

~~~
shurcooL
Compare:

[https://github.com/shurcooL/Conception-
go](https://github.com/shurcooL/Conception-go) \- Go, 7'500 lines

[https://github.com/shurcooL/Conception](https://github.com/shurcooL/Conception)
\- C++, 15'000 lines

Which would you rather work with? You can look at the commit activity to see
my preference.

~~~
frowaway001
If you are rewriting your original code in a new language and only get a 50%
reduction, the new language clearly sucks.

~~~
shurcooL
They are not feature equivalent, so it's hard to compare directly. That said,
excluding code reuse, to me Go feels like 75%-100% of verbosity of C++ without
header files.

Also, I don't agree with your statement. If it were true, it would mean every
language clearly sucks.

~~~
yohanatan
I think part of his point is that the mere re-write itself should save a huge
chunk of that. Quite probably if you were to re-write it in the original
language you could still get 25-30% decrease in code size (or more).

~~~
frowaway001
Exactly.

------
lectrick
Go exception handling (or lack thereof) sucks. Things can just blow up
silently at runtime unless you check the error code return of practically
every statement.

~~~
mitchellh
> unless you check the error return code of practically every statements

Yes, you MUST. That is just how Go does things. If you don't do this, then of
course things are going to blow up at runtime. Your "unless" is the same as
saying in a language like Java: unless you rescue the exceptions, the program
crashes! Yes, clearly.

Personally, I love Go's error handling. I made a joking tweet one time that
over 10% of all lines of code in Packer are "if err != nil". That is still
probably true, but I don't think its a bad thing.

We also make Serf, which is powering some pretty large infrastructures out
there, and we've never ONCE had a crash in production. Not once. We've had
errors, but they were logged and handled. I attribute this to the fact that we
were forced to handle every error.

Some people like exceptions, but I've always liked Go's way of things in this
department.

~~~
lmm
If 10% of your lines are repetitions of the same thing that's absolutely a
problem with the language. You should find a more concise way to express the
same thing (e.g. error monad).

~~~
wting
To expand, Go's type system is relatively rudimentary. The lack of option
types[0] leads to this fairly common, verbose Go pattern:

    
    
        func DoFoo(Foo foo) Cat {
            bar, err := GetBar(foo)
            if (err != nil) { return nil }
    
            baz, err := GetBaz(bar)
            if (err != nil) { return nil }
    
            cat, err := GetCat(baz)
            if (err != nil) { return nil }
    
            return cat
        }
    

Option types add metadata to a type, essentially combining `bar` and `err`
into a single variable. Let `Maybe x` mean a function will return `Something
x` or `Nothing`. For example (in hypothetical Go):

    
    
        func DoFoo(Foo foo) Maybe Cat {
            bar := GetBar(foo)
            if (bar.(type) == Nothing) { return Nothing }
    
            baz := GetBaz(bar)
            if (baz.(type) == Nothing) { return Nothing }
    
            cat := GetCat(baz)
            if (cat.(type) == Nothing) { return Nothing }
    
            return cat
        }
    

Since this is such a common pattern, Haskell has a bind operator (`>>=`) to
take advantage of option types by passing the output as the input of the next
function if it's a `Something`, or return `Nothing`.

    
    
        func DoFoo(Foo foo) Maybe Cat {
            return GetBar foo >>= GetBaz >>= GetCat
        }
    

If you care about why something failed, use an `Either` instead of a `Maybe`.
In a language that supports algebraic data types, you could create your own
sum type instead.

[0]
[https://en.wikipedia.org/wiki/Option_type](https://en.wikipedia.org/wiki/Option_type)

~~~
jbooth
Now you've got way less code, and way more cognitive load per line.

There's a reason why hideous Java is so successful while beautiful Haskell is
a punchline to jokes.

~~~
wting
Many languages besides Haskell have option types (Rust, Scala, OCaml, Java 8):

[http://www.oracle.com/technetwork/articles/java/java8-option...](http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html)

Using option types produces more robust code and prevents null pointer
exceptions / segfaults. Go's idiom of multiple return values and error is a
poor man's Either, which is an improvement on C's return code idiom[0].

[0] C's return code idiom led to the latest GnuTLS bug:

[http://blog.existentialize.com/the-story-of-the-gnutls-
bug.h...](http://blog.existentialize.com/the-story-of-the-gnutls-bug.html)

~~~
jbooth
Yes, but reading code is much more important than writing it in most contexts,
and you're not limited by typing speed anyways.

The one-liner would be harder for me to unpack, in most situations, than a
3-liner. You've got 3 calls, give me space to work with them.

~~~
copergi
They don't need to be on one line. If you really want to pretend that having
them on one line is a problem, then put them each on their own line. You can
even be needlessly verbose if you want to:

    
    
        do
          a <- GetBar foo
          b <- GetBaz a
          c <- GetCat b
          return c

~~~
jbooth
That's much more to my liking, but still, we've got multiple ways of doing
option types and now I've seen 2 different operators, which I presume are
overloaded to deal with those types somewhere? Or we're dealing with a ton of
built-in types and built-in operators to deal with them?

I'm not trying to sell anyone, code however you like -- I prefer the situation
of "simpler environment, don't write bugs" to "really complicated environment
that makes it theoretically impossible to write bugs". I bet I can still write
bugs.

~~~
copergi
>now I've seen 2 different operators, which I presume are overloaded to deal
with those types somewhere? Or we're dealing with a ton of built-in types and
built-in operators to deal with them?

No. Remember we're talking about using a language that is not broken by design
instead of go. You have one operator (>>=) and it works on one typeclass
(Monad). Do notation (do and <-) is merely syntactic sugar that desugars to
using >>=.

>I prefer the situation of "simpler environment, don't write bugs" to "really
complicated environment that makes it theoretically impossible to write bugs"

And since those are not the options available, people tell you that those are
not the options available. Doing it right is very simple. Your options are
"simple and with lots of chances to create bugs" or "just as simple but with
fewer chances to create bugs".

------
resu
Has the popularity of Go died off recently?

This is the first Go article I've seen on the front page in a while...

~~~
edwinnathaniel
When a programming language doesn't solve mass/common problems, its popularity
will definitely decrease after initial peak.

Rails solve common problems for the most popular platform: web.

Go is created to replace C, a system programming language which could be
considered as a niche. You see popular Go projects specifically addressing
system level stuff (infrastructure: packer.io, docker, etc).

~~~
newaccountfool
Surely systems programming is more popular than web? As you need a system to
access the web, but not every system accesses the web...?

What can I do in Rails that I can't do in PHP?

~~~
Legion
> Surely systems programming is more popular than web? As you need a system to
> access the web, but not every system accesses the web...?

"Systems programming" != "Systems software".

Systems software is more widely used, but written by fewer people.

------
theseoafs
> If you write a package, it will have a name. A unique one. In all the
> universe.

How does that work?

~~~
mediocregopher
Basically to install a dependency you do something like "go get
github.com/mediocregopher/somelib", and the tool will fetch it and put in the
appropriate spot on your GOPATH. So the universal name of your package is also
the address the package is retrieved from. You COULD host the package multiple
places and it wouldn't be true that it would be unique in the whole universe,
but that's up to you.

------
0xdeadbeefbabe
Why the strong opinions about blocking? Go discourages, at least me, from
doing non-blocking IO. I've heard that's a unix philosophy though. For
example, io.Copy will block.

~~~
jbooth
There's a robust syscall package that you can use to do whatever you want.

And, under the hood, all of their net.Conn types use non-blocking i/o combined
with the goroutine scheduler.

Basically, the philosophy is that rather than do it yourself, just write
really simple blocking code using lots of really cheap goroutines, and the
runtime non-blockifies it for you. If you really want nonblocking behavior,
you can easily spin off a goroutine to do your i/o and then send on a channel
or callback or whatever, and if you really want _really_ nonblocking behavior,
you can less easily use the syscall package to write your own conn type.

------
iopq
> Go is compiled. This has a number of advantages, first being all the errors
> that can be detected by the compiler

You can have static typing and type checking without compilation. The
interpreter can just complain to you when you try to run it.

~~~
Dewie
> The interpreter can just complain to you when you try to run it.

That sounds more like strong dynamic typing.

~~~
iopq
But that's wrong. If I have strong dynamic typing and the program never goes
to that part of the code, I don't get an error. What I'm talking about is more
like Haskell repl. It will still complain if you muck things up when you type
them into the repl. Dynamically.

~~~
Dewie
> But that's wrong. If I have strong dynamic typing and the program never goes
> to that part of the code, I don't get an error.

Exactly.

> It will still complain if you muck things up when you type them into the
> repl. Dynamically.

Yeah, because it's a REPL so it has to evaluate every line of code that you
give it. How does that work when you have code as a file and you run the main
function of that file? You would have to run every line of the code. So what
about code paths that are not taken? Do you just give enough input data that
eventually all code has been run through? But then you 're not really running
the main function, you're running all the code of that program has, with
whatever results and output that that might give. Obviously I'm not talking
about Haskell here, which already has a static type system.

Is your claim that you don't need a compiler to do type checking? Well, yeah,
since type checking is not part of the source code translation. But it often
said to be part of a compiler since it it often is often part of the semantic
analysis phase. But if you have an interpreter you wouldn't really have a
compiler as in a program translator.

I interpreted 'The interpreter can just complain to you when you try to run
it.' as "complain to you _as you are running it_ ". Not "complain _right
before you run it_ ".

~~~
iopq
No, you just run the static type check on the entire program. Like you run the
Haskell REPL on a file and it type checks it for you and executes the main. It
can just do this in RAM without generating an executable file.

Yes, it will technically complain before you even run it. Because it will
first analyze it and only then run it if it type checks. The entire file, not
just the reachable parts.

------
cyphunk
one mans trash anothers treasure. When I pointed out that Django is an
annoying framework because it breaks up your ability to prototype flat by
forcing you into some workflow structure I was told "that's a feature". Go is
similar. It's annoying but some people like bumps in the road. Enjoy

~~~
mildtrepidation
_It 's annoying but some people like bumps in the road._

You apparently tried to build a prototype with a framework that very
explicitly tries to provide more than just prototype capabilities. Invoking
"one mans trash anothers treasure [big sic]" and then citing this is
ridiculous; your failure to choose the right tool for your chosen task does
not reflect poorly on the tool, nor does it suggest people who do use it
correctly "like bumps in the road."

~~~
cyphunk
I guess I wasn't clear and just letting out a bit of steam. Still doing it but
to be clearer... Go feels more like a framework than a language. could be by
design, but didn't expect it. also you are right, "bumps the road" is not
fare. Better would be to say, in referring to myself, i guess you can't teach
an old dog new tricks ;)

------
fireblade600RR
interesting..

