Exception Handling in F#
Try-catch
Simplest way to handle Exception is to use a try-with pattern. It is similar concept to a try-catch pattern in C#. According to an F# documentation a structure of try-catch block is
try
expression1
with
| pattern1 -> expression2
| pattern2 -> expression3
...
The shortest way we can write it is to give one catch-all pattern for all exception.
Then we can even drop the pipe and match directly after with
clause:
let operationThatFails () = failwith "Error"
let x =
try
operationThatFails ()
with e -> sprintf "Caught exception %A" e
In above case the exception is handled correctly. Variable x evaluates to "Caught exception System.Exception: Error"
string.
Problem with HOFs and partial application
There is one problem that has caught me unexpectedly.
I was working on a function that did a lookup on CsvRow from FSharp.Data package. The first version was:
let tryGetValue (row: CsvRow) (columnName: string) =
try
row.[columnName]
with
| :? KeyNotFoundException -> failwithf "Missing column: %s" columnName
I used it to catch exceptions when there is a missing column name in CSV and then rethrow with better message. Default exception message was too cryptic for my use case.
After couple of minutes I thought that it would be a good idea to refactor this into a higher-order function that will be partially applied.
let tryGetValue (columnName: string) =
try
fun (x: CsvRow) -> x.[columnName]
with
| :? KeyNotFoundException -> failwithf "Missing column: %s" columnName
At first glance it looked OK. The only problem is, that this solution did not work.
In this case exceptions are caught only for the anonymous function creation part. Application of my newly created function happens in another context, which may or may not have its own exception handling.
Similar thing happens with partial application:
let operationThatFails (_: string) (_: string) : string = failwith "Error"
let x =
try
operationThatFails "str1" "str2"
with
| e -> failwithf "Caught exception %A" e
let partiallyApplied : (string -> string) =
try
operationThatFails "str1"
with
| e -> failwithf "Caught exception %A" e
let y = partiallyApplied "str2"
Variable x evaluates to string "Caught exception [...]"
.
Evaluation of y fails with System.Exception: Error
Alternative approach
An interesting (and more functional) way to handle exceptions is to use an
FSharpPlus library.
There is a simple (but very helpful) function protect
for Result
type:
let protect (f: 'T->'U) (x: 'T) : Result<'U,exn> =
try
Ok (f x)
with e -> Error e
Each time we deal with a function/method that throws exceptions we can wrap it
and apply in safe Result
context:
let iWillThrow () : string = failwith "Error"
Result.protect iWillThrow // Wrap
|> fun x -> x () // Apply
|> function // Handle Result
| Ok v -> sprintf "Value was: %s" v
| Error e -> sprintf "There was an error: %A" e
If we don't care about thrown exception we can just use Option.protect
, which will
return None
for all failed cases.
There is a caveat though, that protect
accepts only functions that take a single parameter.
However, this constraint may actually be benefitial, because it saves us from making the same mistake
that I did.
Partially applied tryGetValue
function would then be:
let tryGetValue (columnName: string) =
fun (x: CsvRow) -> x.[columnName]
|> Option.protect
|>> Option.defaultWith (fun () -> failwithf "Missing column: %s" columnName)
where |>>
is just an FSharpPlus operator for Option.map
function.
Disclaimer
In all examples above, tryGetValue
remains a partial function. It will fail for all columnName
values
that are not contained in CsvRow. It is written that way, because in my example I treat missing column name
as an unrecoverable error. I apply Fail-fast principle,
which means that I want to stop further processing immediately.