What a great resource.

I’ve been in the errors-as-values (like Rust and Haskell) camp for over a decade. But I think you need to go a bit beyond that philosophy (i.e. not just as a slogan) in order to truly unlock what treating errors as values can be like.

Errors as values are just values. Not any more complex than other values. But except in cases like `None | Error` (no regular value or an error) you at least double the work that you need to do. The thing about the “happy path” is that it’s just one channel of information. With errors you layer on one more channel.

I think that has lead me to be careful about treating errors in the same way I treat other kinds of values. Because simplifying errors down to just one value, like a string, simplifies the whole error channel. But how is error-as-string useful if you are using the code as a library and not just erroring out and letting the end-user/operator read it with their primate brain? Well that lead me to add variants, sum types for all the different error possibilities. Along with associated data like how this int was larger than some exected bound.

Already here I run into a sort of sub-error problem: only advertising what variants of an error that I can return from a function. It seems that this isn’t supported directly in Rust, which is what I was using at that time. So if I just advertise that I can return all the error variants (which is a soft lie) then I might have to deal with them downstream.

More expressive error values seem to both (1) burden the implementation (sub-errors) and (2) the clients who have to consume them.

So do I use less expressive errors so that they become easier to handle for client code?

Well it seems here that I’m still stuck treating errors as special values. Because I still seem hesitant to treat errors in their full generality as values. Think of a value:

1. It’s not just a single “atom”/scalar like an integer

2. It can be in a list or a dictionary/map or on a stack or some other data structure

3. It can be transformed with a map or reduce operation since you either want to transform the error or leave out some details

Maybe the library should provide a list of errors. But the client only cares about the last one. So it takes the head of the list. Maybe the library should provide a detailed error containing associated data about the bounds, expected and actual. But the client just cares about the error type and elides the rest.

Maybe this doesn’t seem useful for errors. But take the `Hurdle` type in the article. These are effectively a mandatory (product type) list of warnings. Maybe the client code only cares about five out of eight of them so he filters out the rest. Or he doesn’t care about them at all so he just ignores them.

And this seems to lead into iterators and lazy evaluation. In high level languages at least we’ve come to a point where we value pretty functional, declarative, and coarse transformation of data. “Coarse” in the sense that we use a few datastructures and operations to process the data without worrying about special datastructures and micromanaging control flow. But crucially these constructs don’t have to allocate N intermediary collections. They can be lazy. So maybe errors (and warnings (hurdles)) should be as well. Maybe you can have a very rich error/warning declaration while only generating them when the client code wants it. And what happens when you make a nod (at least) that all the error computations can be elided if only, say, you just kill the application on the first sight of any problems (like in a script)? People are incentivized to treat errors as any other value without fearing that it is wasted effort (computation).

Now on the other hand I do program in Java. And personally, philosophically, I am fine with exceptions for the type of application advocated in the article: report errors way up the stack. But there I’ve been running into the same problem: treating exceptions as not-quite-values. For any other custom class I would create however many fields and methods I need. But for custom exceptions I just give them a name. And maybe pass a string to the constructor. Which I could do for a built-in or third-party error. (So effectively I just give them a new name.)

But why? There must have been a block in my mind. Because Java exceptions are just classes. And they can have fields. And ultimately we log errors that bubble all the way up. So why not record all the associated data as fields on the exception and use the error handler (with `instanceof`) to gather up all the data into a structured log? That should be a ton better than just creating format strings everywhere and sticking the associated data into them. And of course you can do whatever else with the exception objects in the error handler; once it’s structured you can use the structure directly, without any parsing or indirect access.