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.