F Sharp Programming/Exception Handling
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
editLet'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:
- Occurs when
s
is a null reference.
- Occurs when
s
does not represent a numeric input.
- Occurs when
s
represents number greater than or less thanInt32.MaxValue
orInt32.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
editThe 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
editNormally, 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
editF# 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
editConstruct | 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
|