Composing custom error types in F#
I strongly believe that we should keep code as referential transparent as possible. Unfortunately, F# language does not encourage programmers to use Either monad to deal with errors. The common practice in the community is using common in the rest .NET (imperative) world exception based approach. From my experienced, almost all bugs found in production are caused by unhandled exceptions.
The problem
In our project we've used the Either monad for error handling with great success for about two years. ExtCore is a great library making dealing with Either, Reader, State and other monads and their combinations really easy. Consider a typical error handling code, which make use Choice computation expression from ExtCore:
The code is a bit hairy because of explicit error mapping. We could introduce an operator as a synonym for Choice.mapError, like <!>, after which the code could become a bit cleaner:
(actually it's the approach we use at in our team).
Rust composable errors
I was completely happy until today, when I read Error Handling in Rust article and found out how elegantly errors are composed using From trait. By implementing it for an error type, you enable auto converting lower level errors to be convertable to it by try! macro, which eliminates error mapping completely. I encourage the reader to read that article because it explains good error handling in general, it's totally applicable to F#.
Porting to F#
Unfortunately, there's no static interface implementation neither in F# nor in .NET, so we cannot just introduce IError with a static member From: 'a -> 'this, like we can in Rust. But in F# we can use statically resolved type parameters to get the result we need. The idea is that each "higher level" error type defines a bunch of static methods, each of which converts some lower level error type to one of the error type cases:
Now we can rewrite our processFile function without explicit mapping to concrete error cases:
Great. But it's still not as clean. The remaining bit is to modify Choice computation expression builder so that it can do the same implicit conversion in its Bind method (its ChoiceBuilder from ExtCore as is, but without For and While methods):
The CE now requires all errors to be convertable to its main error type, including the error type itself, so we have to add one more From static method to Error type, and we finally can remove any noise from our processFile function:
Comments
About AsyncChoice CE, I highly recommend to use ExtCore library. It has many CEs, like Maybe, Choice, AsyncChoice, Reader, State and combinations of them, like AsyncReaderChoice or AsyncProtectedState. See https://github.com/jack-pappas/ExtCore/blob/master/ExtCore/Control.fs#L1836-L1896