Exception handling in F#

Posted February 9, 2021 by Rafał Gwoździński ‐ 4 min read

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.