I tend to avoid exceptions for the following reasons:
- .NET exceptions are slow
- Exceptions change control flows of programs in an unexpected way, which makes it much harder to reason about
- Exceptions often arise in critical situations while you can fail-safe by using options.
In your case, I will follow F# core library conventions (e.g. List.tryFind
and List.find
, etc.) and create both versions:
let tryMult m1 m2 =
let sizeOK = validateDims m1 m2
if not sizeOK then
None
else
Some <| doWork m1 m2
let mult m1 m2 =
let sizeOK = validateDims m1 m2
if not sizeOK then
raise <| InvalidOperationException("bad dimensions!")
else
doWork m1 m2
This example isn't exceptional enough to use exceptions. The mult
function is included for C# compatibility. Someone using your library in C# doesn't have pattern matching to decompose options easily.
One drawback with options is that they don't give the reason why the function didn't produce a value. It's overkill here; generally Choice (or Either monad in Haskell term) is more suitable for error handling:
let tryMult m1 m2 =
// Assume that you need to validate input
if not (validateInput m1) || not (validateInput m2) then
Choice2Of2 <| ArgumentException("bad argument!")
elif not <| validateDims m1 m2 then
Choice2Of2 <| InvalidOperationException("bad dimensions!")
else
Choice1Of2 <| doWork m1 m2
It's a pity that F# Core lacks high-order functions to manipulate Choice. You can find those functions in FSharpX or ExtCore library.