Try.do for recoverable errors in Haskell
source link: https://chrisdone.com/posts/try-do/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Try.do for recoverable errors in Haskell
In Haskell, I sometimes have IO-based actions that may produce failures. The IO aspect is usually incidental; because I need logging or metrics generation.
When not using a free monad or a fancy effect-system–just plain IO–I like to follow a mental model similar to Rust’s definition of error handling, which splits them into recoverable vs unrecoverable errors.
In Rust, when a function can fail with a recoverable error, it returns a Result
, like:
which the caller can pattern-match on. Let’s compare recoverable errors in Haskell.
Have the IO action throw a runtime exception
Our code might look like:
And each of these steps may throw an exception. We leave it up to code above in the call chain to remember to catch them.
The trouble with exceptions is that they’re not mentioned in the type system. They’re also handled not at the call site, when for recoverable errors, that is usually the most straight-forward place to handle them.
It’s simply very easy–and it happens all the time–that you either throw the wrong exception type in the wrong place, or you forget to catch exceptions at the right place.
Verdict: Too dangerous.
Make the IO action return Either
A more direct approach, which is a bit like Rust, is to simply have the function return Either ErrorType OutputType
and then pattern match on the result to find out whether everything went fine.
Now our code looks like this:
This is tedious to write, there’s all that repetition on the Left
case. Reading it, you also can’t immediately tell whether there’s any extra logic going on here.
Verdict: Too much code.
Wrap your IO code up in ExceptT
One way to solve this issue is to wrap up these actions in the ExceptT
monad transformer. We have to make each of the actions now live in the ExceptT
monad, so
becomes
And our code goal is clean again:
The runExceptT
produces an IO (Either ErrorType OutputType)
.
However, ExceptT
cannot be an instance of MonadUnliftIO
– because it necessarily requires multiple exit points. See this discussion which should give you an idea of how hairy and unpredictable this can be.
This is a big deal.
Essentially, if a monad is unliftio-able, then:
- The regular exception system will work as normal. This is important, because you do want regular exceptions to be thrown upwards.
- Concurrent programming works as usual: you can use
async
withUnliftIO.Async
and get predictable results. - Resource clean-up is also straight-forward.
So, ExceptT
has to be thrown out too, sadly.
Verdict: Not compatible.
Qualified do
One thing that struck me was that our earlier Make the IO action return Either
approach produced code that was still perfectly satisfying the unliftio laws. Perhaps, like in Rust, we need a syntactic solution to the problem.
Enter QualifiedDo, which will be available on the 9.0.1 version of GHC. What this would allow us to do is rebind (>>=)
to mean what we’d like:
We put this in a module called Try
and import it with QualifiedDo
enabled.
Now our code becomes:
where each action’s type is SomeThing -> IO (Either ErrorType OtherThing)
.
Full working example:
If you want a final return, you need to wrap it up in Either
, as:
Otherwise it won’t match our type:
People who know a bit of Rust will see this as a familiar pattern; putting Ok(output3)
at the end of your function.
What did we gain? We can have our cake and eat it too. We get a trivial, syntactically-lightweight, way to string possibly-failing actions together, while retaining all the benefits of being an unliftio-able monad.
Verdict: Best of all worlds.
Good things come to those who wait
Unfortunately, it’ll be a while before I’ll be upgrading to this version of GHC, but I look forward to being able to use this time saver.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK