Basics 12 - Exceptions and Pattern Matching
There are two kinds of exceptions when working in F#:
.NET exceptions, which are driven from System.Exception. Some examples are:
System.DivideByZeroException
System.ArgumentException
System.NullReferenceException
F# exceptions
F# Exceptions
F# exceptions are defined using the exception
keyword.
// An exception type for holding a message
exception ValidationError of string
// An exception type for holding a list of messages
exception ValidationError of string list
// An exception type for holding message and error code
exception BusinessError of message: string * code: int
Raising/Throwing Exceptions
Exceptions (of both types) in F# are raised using raise
function.
// Raise System.Exception
raise (System.Exception())
// Raise System.ArgumentException
raise (System.ArgumentException("I don't like your argument!"))
// Raise ValidationError
raise (ValidationError("I don't like whatever you are supplying."))
// Raise BusinessError
raise (BusinessError("I don't like whatever condition we are in.", 10))
Handling/Catching Exceptions
Exception handling in F# is done with try/with
expression, which is very similar to pattern matching.
let answer =
try
...
with
| ValidationError(message) -> ...
| BusinessError(message, code) -> ...
| :? System.NullReferenceException as ex -> ...
| :? System.DivideByZeroException as ex -> ...
| ex -> ...
try/with
is an expression, which may:Like any expression, result in one value
Raise/throw an exception from
try
blockReraise/rethrow from
with
block (discussed below)
Typically, do whatever is required in
try
blockF# exceptions (defined with
exception
keyword) are handled like discriminated union cases.NET exceptions are handled with
:?
| :? System.NullReferenceException as ex ->
can be written as| :? System.NullReferenceException ->
if you don't need the exception detailsThe last line in the above-given code is wildcard/default case.
ex
is of typeexn
which is an alias forSystem.Exception
.| ex ->
can be written as| _ ->
if you don't need the exception details
Here's another example:
let x = 4
let y = 2
let answer =
try
$"The result of {x}/{y} is {x / y}."
with
| :? System.DivideByZeroException -> $"Sorry! Can't divide by zero."
| ex -> $"Sorry! The operation failed for an unknown reason. {ex}"
printf "%s" answer
The above-given example with pipeline:
let x = 4
let y = 0
try
$"The result of {x}/{y} is {x / y}."
with
| :? System.DivideByZeroException -> $"Sorry! Can't divide by zero."
| ex -> $"Sorry! The operation failed for an unknown reason. {ex}"
|> printf "%s"
Reraising Exceptions
Exceptions are reraised from with
block.
let x = 4
let y = 2
let answer =
try
$"The result of {x}/{y} is {x / y}."
with
| :? System.DivideByZeroException -> $"Sorry! Can't divide by zero."
| _ -> reraise ()
printf "%s" answer
Typically, you reraise an exception from a function when you want the caller to handle the exception. In this example, if we hit | _ ->
wildcard case, we have no idea what to do in this unexpected case. Another case of rethrowing is when you want to handle the exception (say logging), but let the caller also know.
If you have reached so far, congratulations.
Keep reading!