Hacker News new | ask | show | jobs
by Scubabear68 846 days ago
It has always been surprising to me how primitive Go really is, for no really good reason.

I understand the evolution of C, it made perfect sense back when it was invented. And the limitations were necessary due to the wide array of architectures and extremely limited computers of the time in every dimension (CPU speed, IO speed, RAM size, disk size, etc).

Many of those dimensions have been improved by several orders of magnitude, and both compilers and runtimes can afford to be comprehensive. Yet we get this ham-strung language out of the gate.

Very disappointing.

4 comments

Simplicity is a feature. Sure, how complicated can effective enums really be, but Go's general philosophy is to think hard (& sometimes for a long time) before adding every bell and whistle.

I have a far easier time delving in to previously unknown Go code for the first time compared to something like Scala (or even Java). Go is a solid language for those who value that and want to enable the experience for others.

Simplicity can be taken too far. Take this to its logical close cousin and you would have Forth or Basic.

Also, we have been doing computer language design for quite awhile now. This isn’t a new frontier. The deficiencies in Go aren’t in areas of “oh, we never thought of that!”, but are in very well known areas with known solutions.

I find Go code is obscured with house keeping code that isn’t necessary in better languages.

That simplicity can lead to more complex and hard to read/support code.

For example, to encode a JSON structure with a dynamic top-level key you need to write a custom marshaller OR marshal twice. That's... awful. Like bonkers level insane.

You're right.

Go is not simple, it is idiotically designed to deliberately exclude common sense features that ironically makes it less simple and more error prone to code in and read Go.

Other languages are objectively better than Go for every imaginable use case. Rust is better for embedded. Kotlin is better for back end. I could go on.

The creator of Go is very open and candid that he thinks his target audience, Google Engineers, are too stupid to use "advanced" features like oh I don't know, sane error handling? and any number of basic things other languages have.

I know how cringe it is to start flame wars about programming languages, but srsly, Go, PHP, Perl, JS and a few others really are objectively worse (for every context and use case) than widely used alternatives.

> sane error handling?

Golang _has_ sane error handling. It just considers errors a normal and expected situation.

When you perform a http request, and the result is successful you expect the result to be assigned a variable, right? Then why would you expect non-successful outcome to be returned in a different way? Why is it different? Why do you unwind the stack? Something terrible happened? Definitely not, it's as real life as 200 OK.

For unrecoverable things golang has panics, and if you don't like the idiomatic way of handling errors, you can just throw them like exceptions.

100% agree, and Go gets oh so close

But the correct and only sane way to do this is Either<Error, Success> that you can then pass on, map over both or either of the two, flatMap to chain with other Eithers, fold into a single thing etc etc. Not endless sprinkling of

if err != nil { log.Fatal(err) }

everywhere (and no, those operations are not obscure, esoteric or difficult to learn or understand - they're the same for other types like Option, List etc and are trivial to learn in a day for people who aren't familiar with them)

+ not making the compiler distinguish between null and non-nully values (as eg Kotlin, Rust and Haskell does) in itself as well is inexcusable for a modern language

Btw, tried to implement Result[T] flatmaps etc, it looks uglier than err != nil

func myfunc(url string) Result[string] {

  tup := FromTuplePtr(http.Get(url))

  return FlatMap(tup, func(r http.Response) Result[string] {

    return Map(FromTuple(io.ReadAll(r.Body)), func(b []byte) string {

      return string(b)

    })

  })

}
I agree that the type system should be better. And for some reasons, golang didn't even implement proper tuple types. However, now with generics, you can actually do Result[T] with all functions you described.

> in itself as well is inexcusable for a modern language

In Go, you can assign nils only to pointers

What language typically returns the `Either<Error,Success>` you refer to here? I get (and love) the idea but have never seen it in official documentation (sure I could go off the beaten path and implement in my language of choice).

Also, did you come up with this on your own, or were you exposed to it?

not as many languages as you'd hope unfortunately, but plenty do (see eg other reply you got, there are more still including F# etc etc)

+ other languages get close, eg Kotlin has nullable types (which is a poor substitute) and Result (which is also poor because it's not a true Either)

that said lots of languages these days have libraries that do it (Arrow, Vavr and countless others)

IMO the killer simple language that Go tries and fails to be would be something like a Kotlin+Arrow with heavily reduced syntax and features, eg

no exceptions (use Either or a correct Result type)

no loops (use map, fold etc)

no nulls (use a correct Option/Maybe type)

etc etc

= in such a language, we learn that methods return things, those things will be what they say they are (guaranteed by the compiler), they will tell you what you can do with them, and if a program compiles, you can be pretty damn sure it works as intended

insert "all the languages are broken, I should create a new language" meme here...

C# gets pretty close with NRTs, pattern matching, terse record declaration and task-based async syntax, lambdas and Result libraries if you like those. Also nicely builds to self-contained binaries, both JIT and AOT.
> Why do you unwind the stack? Something terrible happened? Definitely not,

Definitely do.

In Go we just have to emulate it, badly, by manually writing code to forward the error up the stack so you can finally top-level print “error bad thing happen” or maybe some unholy stringification of wrapped errors possibly collected along the way.

Please explain how errors are fundamentally different so they require drastically different way of returning.
I still can't understand how anyone designing a language can defend self-rolled stack traces as a good thing.
You can in theory unwrap them but that seems to rarely be something people use in the real world
It is basically Limbo with updated syntax, and AOT instead of a JIT.
Very disappointing.

Disappointing it maybe but productive it is not considering people can easily move to far better languages.