Hacker News new | ask | show | jobs
by Spiwux 996 days ago
You discovered the Zen of Go. There are no magic one liners. It's boring, explicit and procedural.

Proponents argue that this forced simplicity enhances productivity on a larger organisational scale when you take things such as onboarding into account.

I'm not sure if that is true. I also think a senior Python / Java / etc resource is going to be more productive than a senior Go resource.

2 comments

... so the code ends up being really long then.
Yes, pretty much. It's a pain to write, but easy to read. On a larger scale the average engineer likely spends more time reading code than writing code
I don't find go that easy to read. It is so verbose that the actual business logic ends up buried in a lot of boilerplate code. Maybe I'm bad at reading code, but it ends up being a lot of text to read for very little information.

Like a one-line list comprehension to transform a collection is suddenly four lines of go: allocation, loop iteration, and append (don't even start me on the append function). I don't care about those housekeeping details. Let me read the business logic.

It's a tradeoff; I too find one-liner list comprehensions like simple transforms or filters easier to read than the for loop equivalent.

However, it's a dangerous tool that some people just can't be trusted with. Second, if you go full FP style, then you can't just hire a Go developer, they need additional training to become productive.

Here's an example of functional programming within Go taken far: https://github.com/IBM/fp-go/blob/main/samples/http/http_tes.... It basically adds a DSL on top of Go, which goes against its principles of simplicity.

There was another great resource that explains why functional programming in Go is a Bad Idea; one is function syntax (there's no shorthand (yet?)), the other is performance (no tail call optimization), and another is Go's formatter will make it very convoluted; I think it was this one: https://www.jerf.org/iri/post/2955/

First time I hear that list comprehension is a dangerous tool. The way python implements it is awkward I'll give you that, but there is a lot of success in how Java and C# implement it for example. golang just chose the easy and overly verbose way out, it's a theme they have that is visible in the rest of the language.
Go offers a programming interface at a lower level of abstraction than languages like Python or Ruby. What you call boilerplate or housekeeping, I consider to be mechanical sympathy.

Modulo extremes like Java, the bottleneck for programmers understanding code is about semantics, not syntax -- effectively never the literal SLoC in source files. It's not as if

    for i := range x {
        x[i] = fn(x[i])
    }
is any slower to read, or more difficult to parse, or whatever, than e.g.

    x.transform(fn)
in any meaningful sense.
You don't need to go the python or ruby route to get such benefits. I daily write rust that has a pretty comprehensive iterator system, while still getting the nitty-gritty in your hands. As some other commenter put it, `x.iter().map(function).collect()` is mentally translated to "apply function to the collection x" at a glance.

between

    var y []int
    for _, x := range x {
        y = append(y, function(x))
    }
and

    let y = x.iter().map(function).collect();
I'll take the second form any day. You express the flow of information, and think about transformations to your collections.
So my 2ยข as someone who's just been skimming this thread: I read the second example faster. I mean it's like 2 seconds vs 5 seconds, but in the first I have to actually read your loop to see what it's doing, whereas in the latter I can just go "oh apply fn over x".
Your example is very simple though.

What's the go equivalent to

  x.map(fn).filter(predicate)
ie returning a new collection of transformed items which is filtered by some predicate? Now we are talking more like 5-6 lines of Go.
> What's the go equivalent to x.map(fn).filter(predicate)

Probably something like

    var output []T
    for _, val := range input {
        if newval := transform(val); allow(newval) {
            output = append(output, newval)
        }
    }
No problem?
Now add filtering and groupBy and watch that loop become several dozen lines. I worked on one of the largest golang codebases in existence, and it's definitely harder to see the underlying logic compared to something like Java or C#.
Mechanical sympathy in Go? When you think you've seen it all...

Go is not a high performance language which made a lot of decisions that don't lend its usage to be nice in scenarios where people want C and Rust. However, with the hype around it, the management continues to make decision, to everyone's detriment, to utilize Go in performance sensitive infrastructure code which one could write in Rust or C# and achieve much higher performance.

What?

Go is all about mechanical sympathy, and is absolutely a high performance language. I guess it all depends on your context, though. If you're used to writing assembly or C, things may look different.

(A "for" loop expresses much more mechanical sympathy than a list comprehension, as an example.)

But at least in the context of application services -- programs that run on servers and respond to requests, typically over HTTP -- Go is the language to beat. I've yet to see an example of a program where the Rust implementation is meaningfully more performant than the Go implementation, and I've got plenty of examples where the Go implementation is much better.

One caveat; if `fn` is declared inline when calling that function, it's not very pretty because Go doesn't have a function shorthand (yet?):

    x.transform(func(value int) string { return fmt.Sprintf("%b", value) })
This quickly becomes more difficult to read, especially if you want to chain some operations this way.

But this applies to other languages as well, in JS (which has a function shorthand) I prefer to extract the predicates and give them a meaningful name.

I don't write much Golang -- mostly using it for my own needs because it allows quick iteration but haven't made a career out of it -- but for any such cases I just extract out the function. I deeply despise such inline declarations, they are a sign of somebody trying to be too clever and that inevitably ends up inconveniencing everyone else, their future selves included.
I think that shorter code is easier to read - to a point! on balance most code is too long, not too short.
Go seems like the antithesis to Lisp.