F Sharp Programming/Exception Handling

Previous: Input and Output Index Next: Operator Overloading
F# : Exception Handling

When a program encounters a problem or enters an invalid state, it will often respond by throwing an exception. Left to its own devices, an uncaught exception will crash an application. Programmers write exception handling code to rescue an application from an invalid state.

Try/With edit

Let's look at the following code:

let getNumber msg = printf msg; int32(System.Console.ReadLine())

let x = getNumber("x = ")
let y = getNumber("y = ")
printfn "%i + %i = %i" x y (x + y)

This code is syntactically valid, and it has the correct types. However, it can fail at run time if we give it a bad input:

This program outputs the following:

x = 7
y = monkeys!
------------
FormatException was unhandled. Input string was not in a correct format.

The string monkeys does not represent a number, so the conversion fails with an exception. We can handle this exception using F#'s try... with, a special kind of pattern matching construct:

let getNumber msg =
    printf msg;
    try
        int32(System.Console.ReadLine())
    with
        | :? System.FormatException -> System.Int32.MinValue

let x = getNumber("x = ")
let y = getNumber("y = ")
printfn "%i + %i = %i" x y (x + y)

This program outputs the following:

x = 7
y = monkeys!
7 + -2147483648 = -2147483641

It is, of course, wholly possible to catch multiple types of exceptions in a single with block. For example, according to the MSDN documentation, the System.Int32.Parse(s : string) method will throw three types of exceptions:

ArgumentNullException

Occurs when s is a null reference.

FormatException

Occurs when s does not represent a numeric input.

OverflowException

Occurs when s represents number greater than or less than Int32.MaxValue or Int32.MinValue (i.e. the number cannot be represented with a 32-bit signed integer).

We can catch all of these exceptions by adding additional match cases:

let getNumber msg =
    printf msg;
    try
        int32(System.Console.ReadLine())
    with
        | :? System.FormatException -> -1
        | :? System.OverflowException -> System.Int32.MinValue
        | :? System.ArgumentNullException -> 0

Its not necessary to have an exhaustive list of match cases on exception types, as the uncaught exception will simply move to the next method in the stack trace.

Raising Exceptions edit

The code above demonstrates how to recover from an invalid state. However, when designing F# libraries, its often useful to throw exceptions to notify users that the program encountered some kind of invalid input. There are several standard functions for raising exceptions:

(* General failure *)
val failwith : string -> 'a

(* General failure with formatted message *)
val failwithf : StringFormat<'a, 'b> -> 'a

(* Raise a specific exception *)
val raise : #exn -> 'a

(* Bad input *)
val invalidArg : string -> string -> 'a

For example:

type 'a tree =
    | Node of 'a * 'a tree * 'a tree
    | Empty
    
let rec add x = function
    | Empty -> Node(x, Empty, Empty)
    | Node(y, left, right) ->
        if x > y then Node(y, left, add x right)
        else if x < y then Node(y, add x left, right)
        else failwithf "Item '%A' has already been added to tree" x

Try/Finally edit

Normally, an exception will cause a function to exit immediately. However, a finally block will always execute, even if the code throws an exception:

let tryWithFinallyExample f =
    try
        printfn "tryWithFinallyExample: outer try block"
        try
            printfn "tryWithFinallyExample: inner try block"
            f()
        with
            | exn ->
                printfn "tryWithFinallyExample: inner with block"
                reraise() (* raises the same exception we just caught *)
    finally
        printfn "tryWithFinally: outer finally block"
        
let catchAllExceptions f =
    try
        printfn "-------------"
        printfn "catchAllExceptions: try block"
        tryWithFinallyExample f
    with
        | exn ->
            printfn "catchAllExceptions: with block"
            printfn "Exception message: %s" exn.Message
    
let main() =                
    catchAllExceptions (fun () -> printfn "Function executed successfully")
    catchAllExceptions (fun () -> failwith "Function executed with an error")
    
main()

This program will output the following:

-------------
catchAllExceptions: try block
tryWithFinallyExample: outer try block
tryWithFinallyExample: inner try block
Function executed successfully
tryWithFinally: outer finally block
-------------
catchAllExceptions: try block
tryWithFinallyExample: outer try block
tryWithFinallyExample: inner try block
tryWithFinallyExample: inner with block
tryWithFinally: outer finally block
catchAllExceptions: with block
Exception message: Function executed with an error

Notice that our finally block executed in spite of the exception. Finally blocks are used most commonly to clean up resources, such as closing an open file handle or closing a database connection (even in the event of an exception, we do not want to leave file handles or database connections open):

open System.Data.SqlClient
let executeScalar connectionString sql =
    let conn = new SqlConnection(connectionString)
    try
        conn.Open() (* this line can throw an exception *)
        let comm = new SqlCommand(sql, conn)
        comm.ExecuteScalar() (* this line can throw an exception *)
    finally
        (* finally block guarantees our SqlConnection is closed, even if our sql statement fails *)
        conn.Close()

use Statement edit

Many objects in the .NET framework implement the System.IDisposable interface, which means the objects have a special method called Dispose to guarantee deterministic cleanup of unmanaged resources. It's considered a best practice to call Dispose on these types of objects as soon as they are no longer needed.

Traditionally, we'd use a try/finally block in this fashion:

let writeToFile fileName =
    let sw = new System.IO.StreamWriter(fileName : string)
    try
        sw.Write("Hello ")
        sw.Write("World!")
    finally
        sw.Dispose()

However, this can be occasionally bulky and cumbersome, especially when dealing with many objects which implement the IDisposable interface. F# provides the keyword use as syntactic sugar for the pattern above. An equivalent version of the code above can be written as follows:

let writeToFile fileName =
    use sw = new System.IO.StreamWriter(fileName : string)
    sw.Write("Hello ")
    sw.Write("World!")

The scope of a use statement is identical to the scope of a let statement. F# will automatically call Dispose() on an object when the identifier goes out of scope.

Defining New Exceptions edit

F# allows us to easily define new types of exceptions using the exception declaration. Here's an example using fsi:

> exception ReindeerNotFoundException of string

let reindeer =
    ["Dasher"; "Dancer"; "Prancer"; "Vixen"; "Comet"; "Cupid"; "Donner"; "Blitzen"]
    
let getReindeerPosition name =
    match List.tryFindIndex (fun x -> x = name) reindeer with
    | Some(index) -> index
    | None -> raise (ReindeerNotFoundException(name));;

exception ReindeerNotFoundException of string
val reindeer : string list
val getReindeerPosition : string -> int

> getReindeerPosition "Comet";;
val it : int = 4

> getReindeerPosition "Donner";;
val it : int = 6

> getReindeerPosition "Rudolf";;
FSI_0033+ReindeerNotFoundExceptionException: Rudolf
   at FSI_0033.getReindeerPosition(String name)
   at <StartupCode$FSI_0036>.$FSI_0036._main()
stopped due to error

We can pattern match on our new existing exception type just as easily as any other exception:

> let tryGetReindeerPosition name =
    try
        getReindeerPosition name
    with
        | ReindeerNotFoundException(s) ->
            printfn "Got ReindeerNotFoundException: %s" s
            -1;;

val tryGetReindeerPosition : string -> int

> tryGetReindeerPosition "Comet";;
val it : int = 4

> tryGetReindeerPosition "Rudolf";;
Got ReindeerNotFoundException: Rudolf
val it : int = -1

Exception Handling Constructs edit

Construct Kind Description
raise expr F# library function Raises the given exception
failwith expr F# library function Raises the System.Exception exception
try expr with rules F# expression Catches expressions matching the pattern rules
try expr finally expr F# expression Execution the finally expression both when the computation is successful and when an exception is raised
| :? ArgumentException F# pattern rule A rule matching the given .NET exception type
| :? ArgumentException as e F# pattern rule A rule matching the given .NET exception type, binding the name e to the exception object value
| Failure(msg) -> expr F# pattern rule A rule matching the given data-carrying F# exception
| exn -> expr F# pattern rule A rule matching any exception, binding the name exn to the exception object value
| exn when expr -> expr F# pattern rule A rule matching the exception under the given condition, binding the name exn to the exception object value
Previous: Input and Output Index Next: Operator Overloading