Live data from Hacker News

Lies we tell ourselves to keep using Golang (2022)

fasterthanli.me

491–500 of 526 posts

Re: Lies we tell ourselves to keep using Golang (2022)

#491

Earlier quoted context omitted.

I can't agree with you about C++ exceptions being worse than useless. Exceptional C++ is worth it. Safety isn't that hard with RAII and ScopeGuard.. In your map example, just add a scope guard that removes the just-added element using the returned iterator if the rest of the procedure doesn't succeed. It's no different in Java.

Haven't seen ScopeGuard before but it looks like an implementation of defer() in C++? That sort of thing can help yes. But it's still way harder to get exception safety right in a language with manual memory management. In a GCd language you only have to be careful about cleaning up non-GCd resources, whereas in C++ you have to be ready for the expected lifetimes of things to be violated by exception unwinds at many…

A significant number of C++ codebases use exceptions. Google famously doesn't, but Meta, alike in dignity, does. GDB, now C++, does. At least some AI labs do. C++ exceptions are normal and common.

Re: Lies we tell ourselves to keep using Golang (2022)

#492
post #474

Earlier quoted context omitted.

> The pragmatically "right" choice is to have some tuple type built-in. Perhaps, but in practice we end up with these ill-conceived languages that support tuples but end up not embracing them. Consider, for example, this Rust function: fn process_tuples(tuple1: (i32, i32), tuple2: (i32, i32), tuple3: (i32, i32)) If it made the "right" choice a function would only accept a single input value, which could be a tuple: f…

> Go needed to allow multiple outputs because functions can accept multiple inputs. I don't think that's the right conclusion, unless you have any source or insight? It would explain why you can directly cast the multi-return-values into parameters when calling, but... that doesn't seem to fit go at all. What I would think is that "go needed multi-return to be able to return errors along values", and the mentioned fe…

> I don't think that's the right conclusion

It is the "right" conclusion in general. Multiple input arguments without multiple output arguments is frustratingly awkward. I can understand the appeal of your suggestion of only accepting one input and returning one output, where the passed value might be a tuple, but I expect there is good, pragmatic reason why virtually no languages, even those with a proper tuple type, haven't gone down that road. Once a language accepts multiple arguments, though, it needs them in and out. Otherwise you can't even write an identity function. If you can't write an identity function, is it even a function?

> "go needed multi-return to be able to return errors along values"

If there was some specific reason for its addition, beyond the simple fact that it would be crazy not to, it was no doubt for map presence checks. Without that, it is impossible to know if a map contains a given key. Errors can be dealt with in other ways. It wouldn't strictly be needed for that purpose, although it turns out that it also works for that purpose, along with a whole lot of other purposes. Not to mention that we know that the error type was a late-stage addition, so there is strong evidence that errors weren't a priority consideration in the language design.

Re: Lies we tell ourselves to keep using Golang (2022)

#493
post #265

Earlier quoted context omitted.

There is some DX similarity between checked exceptions and Result types. Because the compiler will fail if you don't explicitly mention each possible exception. But checked exceptions are coming out of style: They're unchecked in C#, and frameworks like Spring Boot in Java catch all checked exceptions and rethrow them as Spring Boot flavored unchecked ones. For unchecked exceptions and Result types: The DX is very di…

>Because the compiler will fail if you don't explicitly mention each possible exception. But only the first time. Once you add `throws FooException` to the caller signature, the compiler won't complain about any future callees that also happen to throw FooException, even if you did care about handling their exceptions yourself. With callees that return Result you do get to make that decision for every callee.

That's a fair distinction.

I would say "adding Result to the caller signature" provides a similar cascade, but it's not entirely true: Every call must separately be unwrapped. So consistently wrapping your Result calls with the '?' operator is similar to a gigantic try-catch block or adding 'throws CheckedException' everywhere.

So both the '?' operator and adding 'throws CheckedException' everywhere let you accidentally neglect proper error handling: You have a default that is syntactically almost invisible and frees you from thinking. The checked exceptions give you a little more freedom from thinking than the '?' operator.

Re: Lies we tell ourselves to keep using Golang (2022)

#494
post #484

Earlier quoted context omitted.

But the underlying error stays unmatchable. Doesn't sound like a solution if you have to duplicate every error type, and worse, they don't even map 1:1 but now you have the same underlying error wrapped to god knows how many different errors. For example, the lib produces some an error "bad file descriptor". You'll be wrapping it when you call fileOpen, fileDelete, etc etc 20 times. So you will be wrapping it in "ope…

https://pkg.go.dev/errors#Is and https://pkg.go.dev/fmt#Errorf clearly state that there is a way to match these errors if the package exposes the values, which the stdlib does.

Ah okay, it seems to work because errors are pointers, and errors.Is just checks for equality.

Re: Lies we tell ourselves to keep using Golang (2022)

#495
post #490

Earlier quoted context omitted.

No, I cannot agree that this would be called the consumer. Yes, you have technically moved the interface type away from the implementation, but just for the sake of it, without any other upsides. The consumer is still the package that is using and importing this interface type, just from another package now.

That is the beauty of engineering: There is no universal truth, just different tradeoffs. Meaning that you don't need to agree, nor should you even seek agreement. You can and should forge your own path if different tradeoffs are warranted for your unique needs. But, this is the "idiomatic" approach. The upside is consistency for future readers. Code is for humans to read, after all. Most codebases follow this patter…

Sorry but I haven't really seen this pattern anywhere, care to give some examples?

All libraries that I recall ever using always export a single package, including interfaces. I just took a look, and even io exports a bunch of structs along the interfaces they implement. And `error` is like a basic type of the runtime.

Re: Lies we tell ourselves to keep using Golang (2022)

#496
post #492

Earlier quoted context omitted.

> Go needed to allow multiple outputs because functions can accept multiple inputs. I don't think that's the right conclusion, unless you have any source or insight? It would explain why you can directly cast the multi-return-values into parameters when calling, but... that doesn't seem to fit go at all. What I would think is that "go needed multi-return to be able to return errors along values", and the mentioned fe…

> I don't think that's the right conclusion It is the "right" conclusion in general. Multiple input arguments without multiple output arguments is frustratingly awkward. I can understand the appeal of your suggestion of only accepting one input and returning one output, where the passed value might be a tuple, but I expect there is good, pragmatic reason why virtually no languages, even those with a proper tuple type…

With conclusion I meant you claiming that go returns multiple values because a function also takes multiple values. I did not refer to judging this as worse or better than having tuples as general types instead.

> it was no doubt for map presence checks. Without that, it is impossible to know if a map contains a given key

Are you just making stuff up on the go? A map access is not even a function call. And if you need a presence check in the early language development stage, it's much simpler to add support to check presence and then get the value, since it's not threadsafe anyway.

Re: Lies we tell ourselves to keep using Golang (2022)

#497
post #490

Earlier quoted context omitted.

That is the beauty of engineering: There is no universal truth, just different tradeoffs. Meaning that you don't need to agree, nor should you even seek agreement. You can and should forge your own path if different tradeoffs are warranted for your unique needs. But, this is the "idiomatic" approach. The upside is consistency for future readers. Code is for humans to read, after all. Most codebases follow this patter…

Sorry but I haven't really seen this pattern anywhere, care to give some examples? All libraries that I recall ever using always export a single package, including interfaces. I just took a look, and even io exports a bunch of structs along the interfaces they implement. And `error` is like a basic type of the runtime.

> Sorry but I haven't really seen this pattern anywhere, care to give some examples?

The standard library provides examples, including a "storage" example.

> All libraries that I recall ever using always export a single package

And now there are questions around the language being spoken, so to speak. That's the power of idioms – it avoids the reader needing to ask questions when encountering language that is new to them. But idioms are not the be all, end all. Sometimes they just don't fit. And if that's the case in your situation, no problem. Nobody knows your problems better than you. To listen to someone else tell you how to solve your own problems is foolish.

That is why I earlier wished we had seen some real code. Perhaps then we would understand the nuance that has lead to you choosing these particular tradeoffs. We have no sense of what problems you are actually trying to solve, and questions to try and glean greater insight have been left unanswered. As a rule, though, an overarching interface package with consumptor functions along with multiple implementation packages is preferable because then it avoids a lot of the questions developers are going to start asking.

For example, with the alternative suggested, what if I want to add a new storage adapter that conforms to your interface? Do I need commit rights to your package or should I create my own package that satisfies your exported interface? If I create my own package, why is it the lone implementation in its own package while the others are all rolled up in one package? Is it that you don't want third-party implementations for other services not covered by your package? Why is that? On and on...

If you stick to the idioms, those questions are already answered by convention.

Re: Lies we tell ourselves to keep using Golang (2022)

#498
post #492

Earlier quoted context omitted.

> I don't think that's the right conclusion It is the "right" conclusion in general. Multiple input arguments without multiple output arguments is frustratingly awkward. I can understand the appeal of your suggestion of only accepting one input and returning one output, where the passed value might be a tuple, but I expect there is good, pragmatic reason why virtually no languages, even those with a proper tuple type…

With conclusion I meant you claiming that go returns multiple values because a function also takes multiple values. I did not refer to judging this as worse or better than having tuples as general types instead. > it was no doubt for map presence checks. Without that, it is impossible to know if a map contains a given key Are you just making stuff up on the go? A map access is not even a function call. And if you nee…

> With conclusion I meant you claiming that go returns multiple values because a function also takes multiple values.

That remains the most reasonable explanation. There is no legitimate reason for functions that accept multiple arguments but only return one argument. That defies the very nature of what a function is.

C made a mistake, but that is not a good reason to copy it. It also made a mistake around memory management. You wouldn't copy that in a modern language either, would you?

> A map access is not even a function call.

Theoretically it is, albeit with unique syntax. However, importantly, it establishes the concept of multiple returns in the language. Once the syntax for multiple returns is worked out, there is no reason to not extend that to functions proper.

But the most likely reason it was added was not because of some specific reason, but because it would be strange without. It is not like Go is the only language that has that feature. It was already an accepted paradigm.

Re: Lies we tell ourselves to keep using Golang (2022)

#499
post #484

Earlier quoted context omitted.

But the underlying error stays unmatchable. Doesn't sound like a solution if you have to duplicate every error type, and worse, they don't even map 1:1 but now you have the same underlying error wrapped to god knows how many different errors. For example, the lib produces some an error "bad file descriptor". You'll be wrapping it when you call fileOpen, fileDelete, etc etc 20 times. So you will be wrapping it in "ope…

https://pkg.go.dev/errors#Is and https://pkg.go.dev/fmt#Errorf clearly state that there is a way to match these errors if the package exposes the values, which the stdlib does.

Which only works when the error itself doesn't contain any information.

It's better than nothing, of course.

Re: Lies we tell ourselves to keep using Golang (2022)

#500
The argument that a language is inconsistent if it allows all structure members to be defaulted to zero, but does not default function arguments to be omitted such that they default zero, seems to me like someone climbing out on a limb, out of the window of lunatic asylum.
Post reply on HN