Live data from Hacker News

I want off Mr. Golang’s Wild Ride (2020)

fasterthanli.me

441–450 of 477 posts

Re: I want off Mr. Golang’s Wild Ride (2020)

#441

Earlier quoted context omitted.

I don't think "screen real estate" is the right argument here. The problem is just that every line creates cognitive load and there's a tradeoff between concision and descriptiveness. A language with piles of syntactic sugar and magic gets it wrong with too much concision and can read like line noise when it gets overused. Go goes the other way though and makes it way too verbose and just makes it difficult to read t…

Cognitive load is unrelated to SLoC. This expression let a = x.iter().filter(...).apply(...).map(...); is equally or even potentially _more_ cognitively complex than this expression for _, v := range x { if !filter(v) { continue } vv := apply(v, ...) vm := map(vv, ...) ... }

focusing on a pedantic detail that i clearly didn't intend and which doesn't change my point.

consider it from a blocks-of-code metric, or some better slightly more abstract metric, that isn't affect by simple things like whitespace transformations, and try assuming that we all understand that we should write code that isn't monstrous to begin with.

Re: I want off Mr. Golang’s Wild Ride (2020)

#442

Earlier quoted context omitted.

For small companies, variance can be good. For large companies, avoiding surprises is everything. So they cram a bunch of processes to make everyone a B player. That’s great if you’re naturally a C player, and can be less work if you’re a B+ player who wants to coast. But it’s hell hell if you’re an A player who is now working with handcuffs, and getting paid the same as the C player.

Can you consider yourself an A player if you think yourself too good to work with B / C players in a language that, as you say, handcuffs you? I mean it frees you up mentally to think about the bigger problems, beyond the bit of code you're working on right now. In the end, the person writing the code is just a cog in the machine. You can try and pretend you're an Important cog by being Very Clever and using very sma…

go has null and has modifiable function parameters.

The first thing is a reintroduction of billion dollar mistake [1] which is not acceptable in 21-st century. It was acceptable when Hoare did that because noone knew better then. It is not acceptable now.

[1] https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retra...

The second point means that some predicate on locally constructed and held data that is true before calling a function with that data as parameters, may not be true after return from a call.

These two "features" of a language make language hard to use. Instead of thinking about bigger things, you think about whether you may have NULL or not in this variable after that call to that function.

In my not so humble opinion, taking OCaml and bolting on it type constructors (monads for hand-made channels, software transactional memory, etc, and even more than monads) could been better choice than reimplementing Simula on Plan 9, poorly. And I do not like OCaml at all.

As per "unimportant cog" argument, I can only say that some companies recognize(d) talents in different areas. AFAIR, IBM used to have parallel ladders for officing-oriented and engineering people, with comparable salaries on comparable levels. I believe it is a good thing.

Re: I want off Mr. Golang’s Wild Ride (2020)

#443

Earlier quoted context omitted.

> It is tedious. > to reduce the variance between the best programmer on a project and the worst. In my experience the only way this can be done as with trying to level anything is to bring down the level of the best. I find this awful.

> In my experience the only way this can be done as with trying to level anything is to bring down the level of the best. > I find this awful. It mitigates risk; if "the best" is at a level above the other developers, to the point where a lot of things hinge on this individual, it's a huge company risk. You never want to rely on "the best"; see also https://en.wikipedia.org/wiki/Bus_factor Go is a language that tells…

Errors are introduced at constant ratio per line of code that is not syntactically checked by compiler. The ratio is specific to developer and language used.

E.g., you can't make a mistake on a line that contains only curly bracket, but you can make (one-off) mistake on line "i += 10;"

The more verbose code, the more lines of code that are not syntactically checked. The more errors you introduce during development.

Why I talk about syntax and not types? Because type system of go and some other languages like C (and C#, C++ and some other including OCaml, for that matter) are not rich enough to express long dependencies as is done in Rust with its borrow checker. Rust has its own failing, of course. In some cases of type systems [1] it is possible to express environments with long dependencies without resorting to hacking of a compiler. But most languages do not have that luxury, the only dependence checking are balancing brackets and missing variable declarations.

[1] http://blog.sigfpe.com/2009/02/beyond-monads.html

The axiom of (not only) software engineering says that cost to fix a defect is directly propotional to the time between introduction and discovery of said defect. The funnel of defect filtering (personal review, colleague code review, CI, QA, beta, etc) is not perfect, it still allows for some defects to fall through. The more defects are on wide part of the funnel, the more defects are on the narrow part of it. And for these defects that pass through, the time between introduction and discovery is bigger than for all other defects.

The more verbose code in some language, the more defects developer will generate using that language. The more defects will pass through defect detection funnel. The biger will be the cost to fix them and cost of software user to, well, use the software.

Google can afford that, because they can afford extremely long funnel with prolonged code reviews, big QA teams and long lasting betas. They can afford that because of money they got.

Practically everyone else cannot afford that.

Ego is not even a problem when you factor all other problems you get with simple or good enough verbose solutions.

Re: I want off Mr. Golang’s Wild Ride (2020)

#444

Earlier quoted context omitted.

What I wonder, is if by designing a language to enable happy corporates, rather than happy programers, is Go not shooting itself in the foot, as the now less happy programmers (because corp mandated Go) would move to other corps, creating more cost to the corporate than if they had use a more joy-inducing language. IOW, Go is probably great if you're quite big. But like most Google created dev tools, the questions we…

So the purpose of a programming language is to spark joy in developers? I mean, I get it, but it's a very selfish position to take and it's not in the interest of a business, in the end. I mean, Java makes me feel dead inside, but I appreciate it for being ubiquitous and reliable, and that a business will be able to find half-decent Java developers any day of the week.

The time to peek up local problem domains are independent of language. The time to peek up language is, well, independent of language. I had an experience when former hardware engineer picked up Haskell in a month.

Re: I want off Mr. Golang’s Wild Ride (2020)

#445
post #424

Earlier quoted context omitted.

> I am referring to TFA/post which sounds similar to many experiences with Go. Lots of people have bad experiences with Java, C++, Rust, Python, or JavaScript. I've had bad experiences with all of those languages, as well as bad experiences with Go. I don't think you've managed to articulate what makes Go different. I'm not saying you should use Go. I will say this: it sounds like you have opinions about Go, and I ca…

This is an extreme intellectualisation of Go's error handling, which is literally just "errors are nullable strings returned by functions, and you have to check for them"[0]. I can't see where Go has learned from anything in PL research or practice, and indeed it's famous for not having learned: errors can't be matched; any hacky matching solution is unhelped by the type system; nor does the type system enforce check…

> literally just "errors are nullable strings returned by functions, and you have to check for them"

That’s definitely not how errors work in Go, either in theory or practice.

In Go, I would characterize errors as an open union type. That’s how I think about them. They turn into strings when you log them.

There is the type of error you get from errors.New. This is not the same as a string, because it is compared using pointer equality, not string equality. For example,

    n, err := f.Read(buf)
    if err == io.EOF {
        // ...
    }
This is not a string comparison.

> …errors can't be matched…

You use ==, type assertion, typeswitch, or errors.Is/As/Unwrap is how you match errors in Go. Occasionally you will see a helper function (e.g. os.IsNotExist) but I think these functions are only there because they predate the errors.Is/As/Unwrap interface.

> nor does the type system enforce checking for an error before accessing a value

This sounds like some kind of dogmatic analysis not founded in PL theory. PL theory does not say “it is better for the type system to enforce checking”, because PL theory is not prescriptive.

> adding context to errors is an absolute clusterfuck […] "failed: op failed: encountered error: service could not load foo: error: database error: EAGAIN".

The nice thing about that is that you can then just do this:

    if errors.Is(err, syscall.EAGAIN) {
        // handle EAGAIN
    }
Admittedly, producing good errors requires the programmer to care, and most programmers simply don’t (an observation). The error you wrote is not what errors in the applications I work with look like. I wrap errors with context when the context is relevant, unwrap context from errors if the context is implicitly understood.

Equally, you will find that programmers don’t care in other languages, like Java, Haskell, or Ruby. When a programmer doesn’t care about errors in Java, you get unreadable stack traces, devoid of context. When a programmer doesn’t care about errors in Haskell, you get partial functions, which throw exceptions (which must be caught in the IO monad) even though the functions are declared to be pure.

Go’s outcomes here seem pretty good to me.

> You say Go has learned some lesson from Java's checked exceptions, because ... errors are return values. What does that have to do with checked exceptions?

The default location to handle exceptions is at a distance. The default location to handle error return values is where they are returned. Checked exceptions are an attempt to bring exception handling closer to the location of the error, but they had ergonomic problems. The ergonomic problems with Java checked exceptions have been discussed elsewhere.

> …I fail to see how picking one approach - of the two only approaches that any language can pick, both of which countless languages have used - is evidence of some kind of deep thought and originality.

“Originality” is what you want to see in research languages like Haskell. That’s the frontier where new ideas are tested. However, speaking as a longtime Haskell programmer, it can often be damn hard to get work done in Haskell. Same thing with Rust—lots of good ideas, can be hard to get work done.

Go is a more conservative approach. “Conservative” does not mean “better” and nor does it mean “worse”, it’s just another niche for programming languages.

Re: I want off Mr. Golang’s Wild Ride (2020)

#446

Earlier quoted context omitted.

Null pointer bugs are prevented by idiomatic use of Option in Rust, so that’s at least one case where Rust’s focus on correctness prevents memory-safety bugs.

Yet many Rust code bases are littered with .unwrap(), which undoes that benefit.

A great deal of the benefit of Option comes from where it isn't used - you know most things can't be null and simply don't have to think about the possibility.

unwrap() bugs are annoying, yes, but at the end of the day they're just a fancy assert(), helpfully spelled out in the code and confined to bits of it where the value is Option. This is a much better situation than a language where nulls can in principle turn up anywhere.

Re: I want off Mr. Golang’s Wild Ride (2020)

#447
post #424

Earlier quoted context omitted.

This is an extreme intellectualisation of Go's error handling, which is literally just "errors are nullable strings returned by functions, and you have to check for them"[0]. I can't see where Go has learned from anything in PL research or practice, and indeed it's famous for not having learned: errors can't be matched; any hacky matching solution is unhelped by the type system; nor does the type system enforce check…

> literally just "errors are nullable strings returned by functions, and you have to check for them" That’s definitely not how errors work in Go, either in theory or practice. In Go, I would characterize errors as an open union type. That’s how I think about them. They turn into strings when you log them. There is the type of error you get from errors.New. This is not the same as a string, because it is compared usin…

> That’s definitely not how errors work in Go, either in theory or practice.

Sorry, I originally had a footnote after that, which I mistakenly removed: something to the effect that "yes, I realise errors are actually nullable structs implementing an interface which can return a string". Not quite as detailed as your comment, and perhaps incorrect in some respects, but in short: yes I understand errors aren't literally strings in Go, but I wanted to focus on the essential aspect, not the distinction between 'is a string' and 'is an interface type whose only common denominator is the string it returns'.

(FWIW, I wrote Go professionally for several (painful, stagnant) years, so you can safely assume I have at least an intermediate familiarity with the language, and that any simplifications are just that.)

And yes, I'm aware you can do type assertions on errors. That's why I specified "any hacky matching solution [i.e. type assertions] is unhelped by the type system": in other words, you can type-assert on some random types if you know that's what the function can return, but the type system neither (a) tells you what errors the function can return (like Java does with checked exceptions, or Rust - among many others - does with the errors-as-values approach) nor (b) enforces that you have handled all possible error types.

In practice, this is a moot point because most Go code (that I've seen) doesn't really use the type system to structure its error handling anyway. If you look through any file at random in the Kubernetes source - probably the most vaunted example of 'well written Go code in a widely used application' – I can't find a single example of that being done, and very few examples of any error handling tout court besides "if err != nil { return [_,] err }".

> The nice thing about that is that you can then just do this[: "if errors.Is(..."]

That only works on errors created using the `errors` package. That also does runtime reflection on nullable pointers.

> The error you wrote is not what errors in the applications I work with look like.

Yeah, I'm used to the "it's ok if you ensure all the code you run is meticulously handwritten" attitude, from when I worked with Go. Unfortunately I have to use libraries, at which point that argument rather falls apart.

That's why I prefer my programming language to enforce or encourage good habits, rather than simply saying "well it's your fault for being rubbish!" if my, or a dependency's, code is problematic for me.

> “Originality” is what you want to see in research languages like Haskell.

I'm not criticising it for being unoriginal. I'm saying that your reverential comment that "the Go language designers have taken some fairly sophisticated lessons from history" is rather over the top.

I couldn't care less whether it's original or not, but you claimed it was, and I responded to that because it seemed a very odd claim (not because it's terribly important if true).

---

Look, I appreciate the arguments for Go. It's a simple language that avoids all that clever, computer-sciencey, type-theory-y argle-bargle, for simple salt-of-the-earth craftsmen who just want to write Good Honest Application Code in a language where no one cares about that garbage collection stuff because we don't write garbage (and anyway what's a few milliseconds here or there!? what's determinism? http go brr!). I tried to convince myself of that for several years. The truth is that there are plenty of simple languages which aren't designed with obstinate inadequacies of the kind amply illustrated by the OP (though I'd throw in the horrific 'zero value' semantics, along with the bug-inducing, heap-smashing 'nullable pointer as option type' monkey patch that's semi-standard among Go programmers).

Re: I want off Mr. Golang’s Wild Ride (2020)

#448
post #247

Earlier quoted context omitted.

> no idea why people get so worked up about character counts Think of reading code as mining ore. If the ore is rich, you don't have to mine and process nearly as much of it to get the material you need. If the ore is poor, you have to invest extra effort to mine more ore to get the same amount of refined material. You might think Go is easy to read because lines are individually very easy to read, but Go code is so…

Cognitive complexity is not related to character count. The expression let x = f.a()?.b()?; is exactly as "easy" to parse as the code block of y, err := f.a() if err != nil { return fmt.Errorf("a: %w", err) } x, err := y.b() if err != nil { return fmt.Errorf("b: %w", err) } They are effectively equivalent in terms of cognitive load.

I disagree. These two bits of code cater to different ways of reading. The first caters to a happy-path reading, where the reader has the choice to yadda-yadda the error handling or mentally expand it. The second foregrounds the error handling on an equal footing with the other logic.

I like your example, because this is exactly what happened in the application code I had to work with. In an application with complicated business logic, it isn't just one line of code turning into ten like you have here. It's ten lines of business logic turning into forty or fifty, where each operation is separated from the next by multiple lines of error-handling boilerplate.

The trade-off is that in Go code you can see every error path. This is a good trade-off for systems where absolute reliability and rigorous error handling are critical.

In an exception-oriented language the error paths are often invisible. This is a good trade-off for complicated business logic where error handling usually means aborting with an appropriate exception. Think about processing a record or a request where you have to validate the request, look up a few related objects in a datastore, check some business rules, do an authorization check for the requesting user, calculate the result of the request, store the result in a datastore, and produce a response. Each step can be written in a couple of lines of code that are hopefully pretty understandable if you have good names, like this:

    validate_request(request)
    user = fetch_user(request.for_user_id)
    authorize_user(user, Privileges.CanFoo)
    dingles = fetch_dingles_by_dongle_group(request.dongle_group_id)
    unfooable_dingles = dingles.filter(not_fooable)
    if (!unfooable_dingles.is_empty()) {
        throw BadRequestException("This dongle group contains unfooable dingles")
    }
    fooment = calculate_total_fooment(dingles)
    fooment_store.save(fooment)
    return DongleGroupFooed(request.dongle_group_id)
From one point of view, this code is nice. You can read these lines of code quickly and see what the basic request handling logic is. It reads like a story.

From another point of view, this code is terrible. A lot of different things can go wrong here, and only one of them is visible. What happens if the user can't be found? What happens if the user isn't authorized? What happens if the dongle group id can't be found? If the wrong exception is thrown, the wrong result will be reported for this request. You have to navigate to other functions to check that. If that makes it bad code for you, then you'd probably rather be writing Go. In Go, these eleven lines would turn into thirty to fifty lines of code. The handling of each error would be visible, at the cost of the happy path being harder to follow.

Re: I want off Mr. Golang’s Wild Ride (2020)

#449
post #264
post #252

Earlier quoted context omitted.

Can you see the value in being able to create software more quickly, at the expense of the software's stability?

Yes, but that is definitely not achieved by checking for errors every second line.

The value here is the few seconds of human attention given to the error at hand. The thought is irreplaceable. Like others mention, 90% of the, end up if err != nil { return nil, err } but thinking about it will, in my experience, make the system more robust to failure. At the very Least for your process you will decide on a goal of carrying on in the face of failures and repairing when things work again vs. just bailing out and letting something else worry about it.

Re: I want off Mr. Golang’s Wild Ride (2020)

#450

Earlier quoted context omitted.

I think checking for errors every line makes my go programs easier and faster to produce at a higher level of quality.

Sure, but is writing the error check by hand what creates the benefit? Or is it having the error check?

It is thinking about what to do for each operation that can fail. This thought is what separates the applications that recover when the network glitch is over vs. the applications that need to be restarted.
Post reply on HN