8 ms·
> The unfortunately missing part of exceptions (in mainstream languages) is that they handle this invisibly. Figuring out, at compile time, what sort of excepti
by jammycakes 3y ago
> The unfortunately missing part of exceptions (in mainstream languages) is that they handle this invisibly. Figuring out, at compile time, what sort of exceptions can appear inside a given function is not obvious.
Figuring out, at compile time, what sort of exceptions appear inside a given function is a futile exercise in many contexts, and railway oriented programming does not fix it. Java tried this with checked exceptions and it fell out of favour because it became too unwieldy to manage properly.
In any significantly complex codebase, the number of possible failure modes can be significant, many of them are ones that you do not anticipate, and of those that you can anticipate, many of them are ones that you cannot meaningfully handle there and then on the spot. In these cases, the only thing that you can reasonably do is propagate the error condition up the call stack, performing any cleanup necessary on the way out.
"Handling this invisibly" is also known as "convention over configuration." In languages that use exceptions, everyone understands that this is what is going on and adjusts their assumptions accordingly.
- naasking 3y ago> Java tried this with checked exceptions and it fell out of favour because it became too unwieldy to manage properly. Because they did a half-assed job of it, and required the user to explicitly propagate error signatures. Inference and exception polymorphism are essential.
- Karunamon 3y agoChecked exceptions always seemed to me to be an exercise of self-flagellation and enumerating badness; when most of the time there are a handful of specific errors that require special handling, with everything else logged/return error/possibly crash.
- layer8 3y agoThe problem is that the callee can’t decide for the caller which exceptions will require special handling. And for the caller to be able to make an informed decision about that, the possible exceptions need to be documented. Since this includes exceptions thrown from further down the call stack, checked exceptions are about the only practical way to ensure that all possible failure modes get documented, so that callers are able to properly take them into account in their program logic.
- jammycakes 3y agoIf you want to (and are able to) document all possible failure modes, then checked exceptions will give you that. As far as I can tell, railway oriented approaches can't. Unfortunately, you can only do that when the number of possible failure modes is fairly limited. In a complex codebase with lots of different layers, lots of different third party components, and lots of different abstractions and adapters, it can quickly become pretty unwieldy. And then you end up with someone or other deciding to take the easy way out and declaring their method as "throws Exception" which kind of defeats the purpose.
- catiopatio 3y agoNo; you simply abstract the underlying subsystem’s exceptions in your own types, the same way you do with any other type. And yes, “railway oriented approaches” can absolutely do this.
- jammycakes 3y ago> No; you simply abstract the underlying subsystem’s exceptions in your own types, the same way you do with any other type. That's all very well as long as people actually do that. It doesn't always happen in practice. And even when they do, the abstractions are likely to be leaky ones. > And yes, “railway oriented approaches” can absolutely do this. How? Please provide a code sample to demonstrate how you would do so.
- catiopatio 3y ago> That's all very well as long as people actually do that. It doesn't always happen in practice. And even when they do, the abstractions are likely to be leaky ones. They don’t have a choice under “railway oriented” API in a typesafe language — they must translate the subsystem’s error types to their own error type. If the abstraction is leaky, at least it’s well-specified. How is that worse than having no abstraction at all, and leaving callers with no idea what error cases an API might raise? > How? Please provide a code sample to demonstrate how you would do so. In what language? What data structure? If we assume Haskell and Either, then it can be as trivial as: first mapError someResult Scala’s Either? someResult.left.map(mapError)