Nope, not in languages where you have a stacktrace attached.
> Errors simply passed up the stack are, more often than not, going to leak implementation details.
That's why in a good language you can just wrap them at the right level of abstraction.
Nope, not in languages where you have a stacktrace attached.
> Errors simply passed up the stack are, more often than not, going to leak implementation details.
That's why in a good language you can just wrap them at the right level of abstraction.
Like you say, the stack trace needs to be attached. For that you, at very least, need a "sidecar" handler if not done so in the same execution path, as we discussed earlier. Did you, uh, forget to read the thread?
> That's why in a good language you can just wrap them at the right level of abstraction.
You can move the logic around, but you can't avoid it, as we discussed earlier. Did you, uh, forget to read the thread?
I just read it again, but I'm not sure what you mean. And sorry, I'm not familiar with that terminology (sidecar) but from the perspective of the user/developer, does it matter? As system or library developer, I don't need to do anything - I'll have the stacktrace available when I need it. There is extra code necessary. (one has to be mindful of the performance, but that's it)
> You can move the logic around, but you can't avoid it, as we discussed earlier. Did you, uh, forget to read the thread?
Doesn't have to do anything with moving logic around.
Let's say you are function foo and you call other functions and one of them is bar and it will fail with barError. Then, to avoid breaking your (= foo's) consumers if bar changes its internals, you simple wrap bar's error with your own. That can be as simple as doing `bar.mapError(barError -> fooError(cause = barError))` and that's it.
That is all I wanted to say.
Then, depending on the language, you stil don't have to repeatedly do "`if err != nil {}`" or so. There are enough alternatives, e.g. monadic error handling like in Haskell or macros like in Rust.
You were familiar with it earlier – you couldn't have sensibly replied otherwise. How did you manage to lose it in the meantime?
> That can be as simple as doing `bar.mapError(barError -> fooError(cause = barError))` and that's it.
At the end of the day is that really any different than: `err = errors.Join(MyError{}, err)`?
But you've still just moved logic around (e.g. into mapError/Join). You've not changed what needs to be done.
In the end, everything is machine code. You tell me if that is any different or not.
> But you've still just moved logic around (e.g. into mapError/Join)
To improve backwards compatibility, yeah. Somehow the error needs to be changed.
But: with a stacktrace and a good language, this is a single line of code. No if/else etc. needed, even in the case of multiple different errors in different places in foo.
And my impression was that this is what we were discussing here - ergonomics of error handling.
Code is ultimately written for humans, not machines. If we only cared about the machine you could flip toggle switches and not worry about all these pesky human problems found in understanding code.
> You tell me if that is any different or not.
I don't think there is. But I may have missed your intent. The question was posed to ensure that we are on the same page. If you leave it up to me, we are on the same page, which means your earlier comment really doesn't work. There is no `if err != nil` to be found.
> But: with a stacktrace and a good language, this is a single line of code.
Why can't it be a single line of code in Go? In fact, at one point Go even did include the stack trace in that single line of code in some pre-release work, but real-world usage determined that nobody ever used it (all the information you need is already there without a stack trace!), so it was stricken before final delivery. You can still do it yourself if you want, though. Errors are not magic.
Because Golang has (to my knowledge) no support of any syntax that supports that.
You are a bit hard to discuss with, but I want to show good will, so I'll try to explain and hope you can appreciate that! :-)
Golang (just like most, but not all!) languages has one default way of doing things. Which is: execute each line (or statement / expression) sequentially.
That's why you can write `loadMissiles(); fireMissles()` and it works.
But it could be different. Imagine a language where each of those is, by default, executed in parallel. There are academic languages that actually work like that.
How would you then do something sequentually? By rewriting your code: `var result = loadMissiles(); fireMissles(result)`. This is a semantical enforcement of sequential execution.
Now let's change this a little bit and add a `.then()` method onto every value (even `null` if the language has that). Then we rewrite the code:
`loadMissiles().then(result -> fireMissles(result))`.
Looks familiar? If we add builtin error-handling then we just have re-invented javascript promises and this is not a coincidence.
Now, there is a duality to that - executing code independent of each other, so non-sequential. (whether it is actually run in parallel or not does not matter, as long as the outcome is the same, minus performance implications of course).
How would one do that? By adding a new method, let's call it `all()` that accepts a list of expressions. Unlike methods like .fold or .reduce, there is no way for the elements inside the list of expressions to interact with each other. That means even in a language that is "sequential by default" these expressions can (or could) be executed in parallel without a problem. This is basically Promise.all() in javascript.
Two more final steps.
First step: we have now invented promises (including sequential and non-sequential execution) which describe asynchronous computations. But how about other things? Let's think of results. They are similar - sometimes we need a successful result to continue (sequential) sometimes we can execute logic non-sequential. How about optionality? Well, it's basically like a result where the error has no information, so same thing. How about parsers? Sometimes we can need to parse something and then we decide how to keep parsing based on the result (sequential) - sometimes we can parse multiple things non-sequential. What about resources? Sometimes we need a database connection to open a network connection (sequential). Sometimes we can do both non-sequential.
And so on. See the pattern? Let's call those things "contexts" and then allow developers to define those contexts themselves, because we certainly can't foresee all contexts that exist in the world. Certain things are necessary to allow to do that, including some kind of parametrism (like generics).
Second step:
Now that we have those contexts, we can use them. But it would be nice to write code in the same way as "normal context" code (whatever that means for our language). So we should have some syntax to help with context switches - optimally for both sequentual and non-sequential logic. And optimally generalized and not specialized to single contexts.
Different languages have different strategies for the second step. Golang doesn't have anything like that (well, to my knowledge, I'm not a golang dev). It certainly doesn't have a generalized version though, that is for sure.
Therefore to come back to:
> Why can't it be a single line of code in Go?
The answer is, because it lacks the syntax in the second step and - to my knowledge - the way to define contexts (at least typesafe ones, my unsafe ones are possible) and in particular the syntax to deal with them (without having to call .then() or - worse - if/else).
The single line was already demonstrated...
> That's why you can write `loadMissiles(); fireMissles()` and it works.
Maybe.
func loadMissles() {
go func() {
// Do the things.
}()
}
Maybe not.Get back to us when you gain at least a surface understanding of how computers work.
> See the pattern?
All that just to convert one type/value to another? That is complete and utter insanity.
Did you write this piece before reading the thread and decide to arbitrarily dump it upon us, totally oblivious to what is happening around you, to satisfy your sunk cost fallacy pangs?