Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Thank you Go team and project!

Go continues to be my favorite language and experience to build and maintain in.

They got so much right from the start, then have managed to make consistent well reasoned, meaningful and safe improvements to the language over the years.

It’s not perfect, nothing is, but the “cost” of maintaining old code is so much lower compared to pretty much every other language I have used.



Go is such a productive language to work with, it's absolutely mind blowing how little adoption it has around where I live. Well I guess Lunar went from node to java to go, and harvested insane benefits from it, but a lot of places have issues moving into new languages. Not that I think that you should necessarily swap to a new hipster tech, I really don't, but Go is really the first language we've worked with that competes with Python as far as productivity goes. At least in my experience.

We'll likely continue using Typescript as our main language for a while since we're a small team and it lets us share resources better, but we're definitely keeping an eye on Go.


I typically develop in Python, C++, and Typescript, and recently had to implement some code in Go. So far I've found it a pretty unpleasant language to use. It feels pedantic when I don't need it to be, and yet I have to deal with `interface{}` all over the place. Simple things that would be a one-liner Python or TS (or even just an std::algorithm and a lambda in C++) feel like pulling teeth to me in Go.

I'd love to hear of any resources that can help me understand the Zen of Go, because so far I just don't get it.


I write Go every day, and can count the number of times per year I have to involve an `interface{}` literal on one hand. Unless you're doing JSON wrong or working with an API that simply doesn't care about returning consistently structured data, I can't fathom why you'd be using it "all over the place."


Me too. We have around a dozen go services and I have maybe used or seen interface{} once or twice for a hack. Especially after generics. I think the parent comment is suffering from poor quality go code. It’s like complaining about typescript because things in your codebase don’t have types


Dealing with databases and data scanning into custom structs, you would be writing lots Scanner/Valuer custom functions which use interface{}

If you are the lucky ones not dealing with databases, I sort-of envy you...!


Applications should basically never need to write custom Scanner/Valuer functions that deal in interface{}, if you find yourself doing that it's a red flag


I use sqlc and it makes this a non-issue.


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.


... 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?


It's definitely less clear though, in that it involves an if statement with an assignment, three temporary variable declarations, etc. Also, type inference won't detect the type of the output automatically from the transform function type, and this of course assumes you wanted to collect into a slice, but it could be a set, or a list.

For some operations, the Go style of explicit, mostly in-place mutations produces more complicated code. Whether that's balanced out by the code being "simpler" is not clear to me, but I haven't worked with Go.


I see it as unambiguously more clear, because it makes explicit what the machine will be doing when executing the code. Whether map/filter copy values, or mutate in-place, or etc. etc. is non-obvious. I mean I'm not saying my way is the only way and I appreciate that other people have different perspectives but I just want to make it clear that "clear" in code isn't an objective measure, that's all.


I agree that there are no objective measure. I guess it's just different expectations.

I would not say it's obvious what the machine is doing in the Go example though. For example it wasn't clear to me that append() mostly doesn't copy the full vector, but does a copy of the slice pointer. I had to look it up from a blog post, because the source for append() is gnarly

https://github.com/golang/go/blob/go1.16.7/src/cmd/compile/i...


> For example it wasn't clear to me that append() mostly doesn't copy the full vector, but does a copy of the slice pointer.

Well I guess you do have to grok the language spec and semantics in order to understand how builtins like append behave, I'm not sure that's avoidable.


That is significantly worse than the FP version and I think it proves the point.


It's fine that you judge it that way, but it's not like that judgment is any kind of objective truth. I find it superior to the FP version because it is less ambiguous.


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.


Go is the language that's not made for you, it's made to make the life of the next guy who has to maintain your code easier! :-)


This is partially correct. It is made to solve internal Google's politics and the hubris of yet another graudate with a CS degree and an itch to justify hours he or she invested in practicing Leetcode (which, in turns, is a skill of writing mediocre stdlib code for languages that have inadequate stdlib)


It's harder to maintain compared to similar logic written in Java or C#.


That's a great way to phrase it, I'm going to steal that :D


One of the big things that I’ve found helped is to “stop being an architect”. Basically defer abstraction more.

People, esp from a Java-esque class based world want class inheritance and generics and all that jazz. I’ve found at work like 50% of methods and logic that has some sort of generic/superclass/OOP style abstraction feature only ever has 1 implemented type. Just use that type and when the second one shows up… then try to make some sort of abstraction.

For context, I can’t remember the last time that I actually used “interface{}”. Actual interfaces are cheap in go, so you can define the interface at use-time and pretty cheaply add the methods (or a wrapper) if needed.

If you’re actually doing abstract algorithms and stuff every day at work… you’re in the minority so I don’t know but all the CRUD type services are pretty ergonomic when you realize YAGNI when it comes to those extra abstractions.

Edit: also f** one liners. Make it 2 or three lines. It’s ok.


If I asked you to carve wood, would you prefer a carving knife or a Victorinox multipurpose tool? I get that it’s a bit or a cheesy analogy, but it’s basically why I liked Go. To me it’s the language that Python would have been if Python hasn’t been designed so long a go and is now caught in its myriad of opinions. Because I certainly get why you wouldn’t like an opinionated language, I really do. It’s just that after more than a decade, often spent cleaning up code for businesses that needed something to work better, I’ve really come to appreciate it when things are very simple and maintainable, and Go does that.

Similarly I’m not sure you would like working with Typescript in my team. Our linter is extremely pedantic, and will sometimes force you to write multiple lines of code for what could probably have been a one liner. Not always, mind you, but for the things we know will cause problems for some new hire down the line. (Or for yourself if you’re like me and can’t remember what you ate for breakfast). The smaller the responsibility, the less abstraction and the cleaner your code the easier it’ll be to do something with in 6+ months. Now, our linter is a total fascist, but it’s a group effort. We each contribute and we alter it to make it make sense for us as a team, and that’s frankly great. It’s nice that the ability to do this, and the ability to build in-house packages, is so easy in the Node ecosystem, but it’s still a lot of work that Go basically does for you.

So the zen is in relinquishing your freedom to architect the “linguistics” of your code and simply work on what really matters.

I’ve never used Interface{}.


Since the advent of generics I rarely ever use `interface{}`.


interface{} is a pretty strong code smell.


One of the reasons I like Go is that it really doesn't try to be a hipster language. It's kinda boring, which is great!


Boring is good when you want to build things that are maintainable by 100s of devs.

Something we have experienced over and over is that devs moving from languages like C# or Java just love how easy and straight forwarding developing in Go is. They pick it up in a week or two, the tool chain is just so simple, there's no arguing around what languages features we can and can't use.

Almost everyone I've spoke to finds it incredibly productive. These people want to be delivering features and products and it makes it easy for them to do so.


Maybe a 100 devs Go is fine, but it gets to be a nightmare as you scale beyond that.

Language abstractions exist to prevent having developers build their own ad-hoc abstractions, and you find this time and time again in languages like Go. You can read the Kubernetes code and see what I mean, they go out of their way to work around some of the missing language features.


Yeah, and that nightmare gets even worse in other languages; one motivation for creating Go was the use of C/C++ by thousands of developers at Google.

Can you link to some of these workarounds? I'm curious to see whether they actually make a lot of difference. In theory (and I have no experience with any software project with more than ten developers working on it), they only made it more difficult by adding cleverness.


> They got so much right from the start, then have managed to make consistent well reasoned, meaningful and safe improvements to the language over the years

In which universe? They have to constantly patch the language up and go back on previous assumptions.


Fast compiler, simple tooling, baked in fmt, simple cross platform compilation, decent standard library, a tendency towards good enough performance if doing things the Go way, async without function coloring. They got some things right and some things wrong. When tossing out orthodoxy, you’ll tend to get some things wrong. I think a lack of sum types is my biggest gripe.


The std library is a big part of the magic. It’s so shocking to go to JS land and see that there are 10 different 3rd party libraries to make http requests, all with wildly different ergonomics all within one code base due to cross dependency heck.

In Go there’s pretty much only the http package, and any 3rd party packages extend it and have the same ergonomics.

For a while my biggest gripe was package management but it’s a dream where we are now.


That's the first language change that can in theory break programs (in practice, it won't). Everything else was just additions to the existing language with full backwards compatibility. That's the opposite of constantly patching the language up.


You can patch things up without breaking backwards compatibility.

But, going on a well-trodden path slower than the pioneers is not a big achievement.


So how do you think they are "patching things up" more than other languages?


Please point to when Go has broken backwards compatibility.


That’s not my point.

Smart men learn from the mistakes of others.


Obviously different perspectives in this thread.

Robert Griesemer, Rob Pike, and Ken Thompson are objectively smart men and pioneers and have learned from lots of mistakes both they and the industry made.

Go embodied a lot of those learnings out of the gate.

If the bar is to be perfect out of the gate that’s impossible and I can’t think of any language that could pretend to be so.

Go was very good out of the gate and has slowly but surely evolved to be great.


That's the embodiment of Go though; they didn't rush to implement generics because they didn't want to repeat Java's mistakes (just have a look at http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypePa...). They took their time with a module / dependency system to avoid the mistakes that NodeJS made. Every decision in Go is deliberate.

Sure, it may not be perfect, or as elegant as other languages, but it's consistent and predictable.


What is java’s mistake, a tutorial file?


Generics made Java’s type system unsound due to an unanticipated edge case. The Go team got a bunch of top-class type theorists (e.g. Phil Wadler) to make sure that Go’s generics implementation doesn’t introduce a similar problem.


AFAIK, it is no longer unsound, and even that was just an academically interesting property - had zero real life impact.


>AFAIK, it is no longer unsound

Reference for this?

>just an academically interesting property - had zero real life impact.

Only because Java runs on the JVM, which preserves enough type information that an exception will be thrown if you try to treat a list of String as a list of Integer. In a language that compiles to native code with full type erasure, that could give rise to serious security problems.

You can just imagine what Go critics would be saying on HN if Go's generics implementation allowed that kind of thing to happen!


https://langdev.stackexchange.com/questions/1444/what-are-th...

No, you need to go way way out of your way to make a real world example for that - otherwise it would have been discovered by a bug, not by academics. It’s similar to Java’s generics being Turing complete - cool, but you can never make use of that/write it accidentally.


I am not as inclined as you to think that accidentally creating an unsound type system is no big deal. Soundness is exactly the property that makes type systems useful. Note the adverb at the beginning of the paper’s abstract:

> Fortunately, parametric polymorphism was not integrated into the Java Virtual Machine (JVM), so these examples do not demonstrate any unsoundness of the JVM.


Java only just got green threads (goroutines), 11 years after Go 1.0 was introduced. CSP had been around for yonks.

Nothing is perfect to begin with, and I think you could probably be a bit kinder to Golang here.



I did mean virtual threads, thanks! :facepalm:




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: