I could not find an object choice
in the standard libraries, that allows me to write
let safeDiv (numer : Choice<Exception, int>) (denom : Choice<Exception, int>) =
choice {
let! n = numer
let! d = denom
return! if d = 0
then Choice1Of2 (new DivideByZeroException())
else Choice2Of2 (n / d)
}
like in Haskell. Did I miss anything, or is there a third-party library for writing this kind of things, or do I have to re-invent this wheel?
There is no built-in computation expression for the Choice<'a,'b>
type. In general, F# does not have a built-in computation expression for the commonly used Monads, but it does offer a fairly simple way to create them yourself: Computation Builders. This series is a good tutorial on how to implement them yourself. The F# library does often have a bind
function defined that can be used as the basis of a Computation Builder, but it doesn't have one for the Choice
type (I suspect because there are many variations of Choice
).
Based on the example you provided, I suspect the F# Result<'a, 'error>
type would actually be a better fit for your scenario. There's a code-review from a few months ago where a user posted an Either
Computation Builder, and the accepted answer has a fairly complete implementation if you'd like to leverage it.
It is worth noting that, unlike in Haskell, using exceptions is a perfectly acceptable way to handle exceptional situations in F#. The language and the runtime both have a first-class support for exceptions and there is nothing wrong about using them.
I understand that your safeDiv
function is for illustration, rather than being a real-world problem, so there is no reason for showing how to write that using exceptions.
In more realistic scenarios:
If the exception happens only when something actually goes wrong (network failure, etc.) then I would just let the system throw an exception and handle that using try ... with
at the point where you need to restart the work or notify the user.
If the exception represents something expected (e.g. invalid user input) then you'll probably get more readable code if you define a custom data type to represent the wrong states (rather than using Choice<'a, exn>
which has no semantic meaning).
It is also worth noting that computation expressions are only useful if you need to mix your special behaviour (exception propagation) with ordinary computation. I think it's often desirable to avoid that as much as possible (because it interleaves effects with pure computations).
For example, if you were doing input validation, you could define something like:
let result = validateAll [ condition1; condition2; condition3 ]
I would prefer that over a computation expression:
let result = validate {
do! condition1
do! condition2
do! condition3 }
That said, if you are absolutely certain that custom computation builder for error propagation is what you need, then Aaron's answer has all the information you need.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With