Live data from Hacker News

I Want Off Mr. Golang's Wild Ride

fasterthanli.me

441–450 of 508 posts

Re: I Want Off Mr. Golang's Wild Ride

#441

Earlier quoted context omitted.

Is there a language besides Rust that could be used instead as this example? As in, languages whose standard library was so carefully designed from previous experience that the design of features like Permissions/PermissionsExt and OsString deliberately take into account the design of both Windows and Unix-like internals. The author mentioned part of the reason the filesystem API is so awkward in Go is because Go was…

C++17's std::filesystem is awesome. One of the benefits of C++'s minimal standard library is that when something finally does get added 20 years after it's an established technology the OS primitives have already solidified. (threads and mutexes in C++11 for another example)

Well threads weren't that well done, hence std::jthread.

And lets not start with the whole concurrency soap opera, that might be done by C++23, and the mistakes in std::async design.

Re: I Want Off Mr. Golang's Wild Ride

#442
post #262
post #239

Earlier quoted context omitted.

Erlang is a stable language that has been around for a long time (almost 35 years now). It's used for way more mission-critical code than golang is ever likely to be used for. It's weird syntax and performance tradeoffs are very well known, but you still won't see anywhere near the number of complaints that you see against golang.

Erlang is less and less used in telecoms and it's the only place if was really used, lot of things have switch to C/C++/Java. As for the reason why it has less complains it's pretty simple no one uses Erlang and it's a niche, it's not a generic purpose language. I can't even tell a single known application or library written in Erlang.

Just like Go only matters mostly to Docker and K8s shops.

Re: I Want Off Mr. Golang's Wild Ride

#443
post #190

Earlier quoted context omitted.

And the other one is the tendency for Go's design to say "exceptions are allowed for me but not for thee". When I worked at Google I used other languages by some of the same authors and they showed the same design philosophy. Make things with an enforced simplicity, and where there were more special use cases that the language designer needed, they created escape hatches for themselves but not the language users.

And the other one is the tendency for Go's design to say "exceptions are allowed for me but not for thee". Yes. Exceptions are kind of a pain, but the workarounds for not having them are worse. Passing back "result" types tends to lose the details of the problem before they are handled. Rust is on, what, their third error handling framework? Exceptions have a bad reputation because C++ and Java botched them. You need…

Internal (bug, e.g. divide by zero or missing function definition in a dynamic language) vs external (out-of-your-control corner case, like file not found) is an important axis.

But another axis is "finality":

1. do you just want to never crash (return code)

2. sometimes crash but have the ability to deal with the problem up the callchain (exceptions in most languages)

3. sometimes crash but be able to fix the problem and continue, at the point the error occured -- not up the call stack (restart-case etc. in common lisp)

4. sometimes crash, but have a supervisor hierarchy make an informed decision if and how to restart you and things in your dependency tree (erlang)

5. crash (panic, assert, exit) and maybe have some less sophisticated but probably very complicated mechanism take care of restarting/replacing you (systemd, kubernetes etc.)

This axis may not be completely orthogonal, but probably mostly is. For example resumable conditions are nice in common lisp both to deal with external stuff (no space left on device? ask user to abort or free some up, and just resume download instead of erroring out as webbrowsers do) but also to just fix problems as you run into them and continue your computation during development, including calling a function you did not define – you can just define it and resume the call to it.

Sadly, the choices in most languages for this second axis are much more constrained. Erlang's supervision trees and common lisp's resumable exceptions in particular seem very useful in many scenarios but nothing else has them (well, elixir has everything erlang has, but it's still the same VM/ecosystem).

Re: I Want Off Mr. Golang's Wild Ride

#444

Earlier quoted context omitted.

Even without emoji, mashing up Chinese, Japanese, and Korean to fit in 21k was never going to happen. It’s sort of like asking Danes to stop spelling their names correctly because we can't afford the extra codepoint for “å”. https://en.wikipedia.org/wiki/Han_unification#Rationale_and_...

What I don't understand is how they miscounted so badly. Even ignoring Han unification, Chinese by itself uses more than 65k. Or was there an intent to not encode some of these rarer characters? I haven't been able to find any info.

They didn't think they knew enough about CJKV to make that call, and so instead asked pre-eminent scholars from major Chinese, Japanese and Korean universities, and they replied that 21k was going to be enough.

The reason they thought that they could fit Chinese into so few letters was that at the time, the CPC supported academics who wanted to reform Chinese towards fewer letters. Not long after, views about traditional scholarship changed, and now they want to promote maintaining more of their traditional characters.

The reason they thought Han unification would work was that the time they asked was in a short period of rapprochement in Sino-Japanese relations. At the time, it was good politics for Chinese scholars to co-operate with Japanese ones. Very soon after this changed.

Re: I Want Off Mr. Golang's Wild Ride

#445

Earlier quoted context omitted.

Elixir is a fantastic and small language. Jose Valim kept it reasonable. It just have so much depth with concurrency. The actor model is amazing and easy to think about too. I'm not entirely sure they overlap completely but the concurrency model is superb. I will probably only use Elixir for web application from now on (unless they don't have packages for certain API). Chris have made web development possible and man…

Elixir is Erlang anyway. The only difference between the two is basically syntax and a couple of bonuses in the Elixir STDLib, most of which wraps existing Erlang functions. You're praising Erlang's concurrency model, there's no such thing as Elixir concurrency model.

I tell people Elixir is just pretty Erlang.

Re: I Want Off Mr. Golang's Wild Ride

#446
post #35

Earlier quoted context omitted.

I surprises me that most people here aren't up in arms in agreement with this point. Code that is silently incorrect is an absolute disaster on an enterprise level. I spend a lot of time writing seemingly redundant double and triple error checking into my code, only to have the designers of the LANGUAGE say, "yeah, most filepaths are utf-8 so seems good enough to me".

option types are spreading pretty well these days. Zig, for instance, has them, even as a low level C replacement language.

Even C itself has unions since forever. Optionals, enums, bools, err-or-result constructs are (highly ergonomic) sugars atop of unions.

Re: I Want Off Mr. Golang's Wild Ride

#447

Classic: “There are only two kinds of languages: the ones people complain about and the ones nobody uses.”[1] The thing that bugs me is the comparison to Rust. I mean, the author did caveat that he chose it because Rust provided the best available counter examples to his specific gripes. But my issue is that comparison seems to make a false conclusion: Rust is better. My intuition says if the author used Rust (or any…

No language is perfect but a couple of years ago I tried both. Learning Go was a constant stream of internalizing design failures and working around them (error handling, packages, etc.). Rust had some points which were harder to get started with but showed clear benefits for having done so — the difference between teaching you better habits versus not learning from C’s second greatest failure and viewing error handling as optional.

Re: I Want Off Mr. Golang's Wild Ride

#448
post #103

Earlier quoted context omitted.

> Well you should handle the error in the first place. That's like saying you should just write bug-free code in the first place.

Go goes out of its way to ensure you handle the error. You have to do something with that err return, otherwise it's a compile error. If you're just throwing it away without checking, we've gone from the mere mistakes everyday developers make to irresponsibility. There's a reason most go code is littered with "if err != nil" on nearly all function calls.

I’ve never looked at a Go codebase where someone has handled every single error – it’s just too easy to assign it but not check the value.

For a language which refuses to compile if you have an unused import, it seems like an odd gap not to have the compiler force you to access the error before it’s reassigned.

Re: I Want Off Mr. Golang's Wild Ride

#449
post #98

Earlier quoted context omitted.

Result types are genericizable in a way that checked exceptions aren't (IIRC), which is huge for ergonomics.

Can you give an example of how you think this helps?

Basically, exceptions have a "happy path" which is very simple but deviating from that path is often quite inconvenient and painful. A well-built result type makes it easy to opt into the happy path of exceptions, and also quite easy to use different schemes and deviate from that path, all the while being much safer than exceptions because you're not relying on runtime type informations and assumptions.

Furthermore, results make it much less likely to "overscope" error handlers (there a try block catches unrelated exceptions from 3 different calls) as the overhead is relatively low and there's necessarily a 1:1 correspondance between calls and results; and it's also less likely to "miscatch" exceptions (e.g. have too broad or too narrow catch clauses) because you should know exactly what the call can fail with at runtime. It's still possible to make mistakes, don't get me wrong, but I think it's easier to get things right.

"Path unification" is a big one in my experience: by design exceptions completely split the path of "success" and "failure" (the biggest split being when you do nothing at all where they immediately return from the enclosing function).

This is by far the most common thing you want so in a way it makes sense as a default, but it's problematic when you don't want the default because then things get way worse e.g. if you have two functions which return a value and can fail and you need to call them both, now you need some sort of sentinel garbage for the result you don't get, and you need a bunch of shenanigans to get all the crap you need out

    int a;
    SomeException e_a = null;
    try {
        a = something();
    } except (SomeException e) {
        a = -1;
        e_a = e;
    }
    int b;
    SomeException e_b = null;
    try {
        b = something();
    } except (SomeException e) {
        b = -1;
        e_b = e;
    }
    if (e_a != null or e_b != null) { // don't mess that up because both a and b are "valid" here
        …
    }
or you duplicate the path in both the rest of the body and the except clause (possibly creating a function to hold that), etc…

By comparison, results are a reification so splitting the path is an explicit operation, but at the same time they still don't allow accessing the success in case of failure, or the failure in case of success.

    let result_a = something();
    let result_b = something();
    if let Err(_) = result_a.and(result_b) { // or pattern matching or something else
        …
    }
Having a reified object also allows building abstractions on top of it much more easily e.g. if you call a library and you want to convert its exceptions into yours you need to remember to

    try {
        externalCall()
    } except (LibraryException e} {
        throw MyException.from(e); // because that might want to dispatch between various sub-types
    }
and if you don't remember to put this everywhere the inner exception will leak out (that's assuming you don't have checked exceptions because Java's are terrible and nobody else has them).

Meanwhile with results the Result from `externalCall` is not compatible with yours so this:

    return externalCall();
will fail to compile with a type mismatch, and then you can add convenience utilities to make it easy to convert between the errors of the external library and your own, and further make it easy to opt into an exception-style pattern. e.g. Rust's `?`

    externalCall()?
is roughly:

    match externalCall() {
        Ok(value) => value,
        Err(error) => { return Err(From::from(error))); }
    }
(there's actually more that's involved into it these days an a second intermediate trait but you get the point, in case of success it just returns the success value and in case of failure it converts the failure value into whatever the enclosing function expects then directly returns from said enclosing function).
Post reply on HN