Chapter 15

Exceptions & Debugging

Main Page

Introduction

Exceptions are erroneous or unexpected conditions that arise during the execution of a program. Common exceptions include memory exhaustion, file permission errors, erroneous mathematical calculations, e.g., divide by zero errors, network timeouts, out-of-bounds indexing, accessing a null object, etc. When these types of problems occur, we can use exception handling to deal with them in a standard, structured manner.  In this chapter, we will look at how to create, propagate and deal with exceptions. We will also discuss some of the diagnostic and defensive programming tools that F# provides.

Exceptions

F# supports two types of exceptions: .NET-based exceptions and F#-based exceptions. The first part of this chapter assumes the code is working with .NET-based exceptions, the most common scenario. In the second part of this chapter, we explore F#-based exceptions.

Handling .Net Exceptions

Since exceptions are the standard way to deal with erroneous and unexpected conditions in .NET, it should come as no surprise that .NET ships with dozens of predefined exception classes that cover all manner of problems. Some of these classes include: ApplicationException, FileNotFoundException, EndOfStreamException, InvalidCastException, etc.

In F#, by default, when an exception occurs, the runtime creates an exception object (an instance of a particular exception class) that describes the error in detail. The runtime then begins the process of walking the call stack looking for an exception handler – a block of code that has declared itself to be a handler for this type of exception. In exception parlance, we say that the runtime has created and then “thrown” an exception. If the runtime finds a handler capable of processing the type of exception thrown, we say the handler “catches” the exception.

It is normally your responsibility to provide a handler to catch an exception, unless you want your program to terminate using the default runtime handler. If the runtime finds the right kind of handler in the call stack of your code, it will pass it the exception object to it. Your handler can then do whatever it likes with the exception, including actions that try to fix the problem, log an error, pass the exception further up the stack, ignore the exception, etc.

The runtime stops walking up the stack when it finds a suitable handler.  A suitable handler is a try…with expression surrounding calling code on the execution stack. If no suitable handler is found, F# will eventually display an error message and halt the program.

try…with

To handle exceptions in F#, we use try…with to wrap an expression where an exception may occur.  try..with has the following general syntax:

try
    expressionA
    expressionB
    expressionC
    ...
with
    | pattern1 -> expression2
    | pattern2 -> expression3
    ...

When we sandwich an expression in try…with, we’re telling F# that we want to know about exceptions that occur within that expression. The try…with sets up an exception frame – a point in the code that’s indicating to F# its interest in exceptions that meet the patterns listed in the with part. When an exception is caught, it is matched against the patterns in the with block, much as if we had a match expression where the exception instance was the argument.

A try…with expression evaluates to the last line of the expression between try and with, or to whatever exception handler expression is executed. This means that the expression between try and with, must be compatible with the expression types in the with block, otherwise, the F# compiler will complain that the over try…with expression is inconsistent.

It’s important to note that try blocks introduce their own scopes, meaning that values declared within try blocks cannot be used in the associated with blocks, since they are out of scope.

The try block applies to any call within its call chain.  When an exception occurs anywhere within the chain, the runtime will jump to the associated with clause and begin matching the thrown exception object against the patterns specified in the with block. The primary expression code (between try and with) will not complete its execution when an exception is thrown during expression evaluation.

In general, we implement the with block by pattern matching upcasted types of the exception’s type via the :? operator. As soon as the matching engine finds a successful pattern match, it executes the expression associated with the pattern (expression2 or expression3 in the above syntax description). If the matching engine cannot find a match, the runtime continues walking up the call stack until it either finds a suitable handler or exhausts the stack.

In the following code, we show a representative example of throwing and catching an exception:

let div x y = x / y

   

try

    let a = div 5 0

    printfn "%A" a

with

    | :? System.DivideByZeroException -> printfn "You cannot divide by zero!"

When implementing functions that may throw exceptions, it is a common style to generate option (Some/None) return values. This makes sense, since the function may not be able to return a meaningful result. Using this style, we would rewrite the div example as follows:

let div x y =

    try

        Some(x / y)

    with

        | :? System.DivideByZeroException -> printfn "Cannot divide by zero!"; None

       

div 10 2

div 19 0

As with all pattern matching, F# will execute the first match that it finds, ignoring all the others. This makes the ordering of exception handlers important, especially since we’re comparing against class type. All “handle-able” exceptions eventually derived from System.Exception. So, if you match to System.Exception, that should be the last exception type listed in the handlers. The rule of thumb is to order your handler from most-specific to least-specific, i.e., specific to general.  For example, if we were to write the previous example like this…

let div x y =

    try

        Some(x / y)

    with

        | :? System.Exception -> printfn "Always handle exceptions here!"; None

        | :? System.DivideByZeroException -> printfn "Cannot divide by zero!"; None

       

…we’d have a problem, since the DivideByZeroException would never execute. It would always be superceded by the previous pattern System.Exception.

Note in this example the use of the semicolon used to separate the printfn and the return value (None). We can use a semicolon to put several expressions on the same line.

try…finally

Sometimes when an exception occurs, our application may have resources that it needs to release or half-completed work that it needs to undo.  In these cases, using try…with can lead to resources leaks and inconsistent state, since all expressions following the offending one are skipped. This makes cleanup difficult. So, if you have cleanup to do, whether or not an exception occurs, use try…finally.  The syntax is as follows:

try
    expression1..n
finally
    expression2..m

The code in the finally block (expression2..m above) will always execute, regardless of whether executing expression1..n generates an exception. If the code in the try block generates an exception, F# transfers control to the finally block, then to the next matching exception handler up the call stack, if one exists. The following code demonstrates the use of try…finally to clean up a graphics resource:

open System.Drawing

let brush = new SolidBrush(Color.Blue)

try

    printfn "About to 'paint' and throw an exception..."

    let result = 10 / 0

    printfn "'Paint' complete"  // should never be executed

finally

    printfn "Cleaning up brush"

    brush.Dispose()

 

With try…finally blocks, the last expression of the try block is considered to be the expression’s return type. The finally expression does not contribute to the return type of the expression.

Combining try...with and try…finally

Often in real-world programs we want to combine the following: executing potentially exceptional code, catching exceptions that occur, and performing come cleanup. F# does not have a try…with…finally construct, but instead favors nesting try…with blocks inside try…finally blocks, producing a similar effect. In the following example, we see how to use try…with combined with try…finally:

open System

open System.IO

 

let mutable f: FileStream = null    // so that all blocks can see it,

                                    // we define it outside all trys

try

    printfn "Opening file"

    f <- new FileStream(@"c:\temp\badfile.bin", FileMode.Open)

    try   

        Some(f.ReadByte())

    with

        | :? FileNotFoundException -> printfn "Cannot find input file"; None

        | :? Exception -> printfn "Unspecified exception"; None

finally

    printfn "Closing file"

    if f <> null then f.Close()

Creating F# Exceptions

In F#, you are not limited to using predefined .NET-based exceptions. You can also define your own F#-based exceptions using the following syntax:

exception exception-type of argument-type

The keyword exception introduces a new exception type. The exception-type is the name of the exception “class”, and argument-type is a tuple of field types making up the contents of the exception. type. Let’s take a look at several examples to help illustrate the idea:

exception LightweightException of string

exception AccountOverdrawnException of string * decimal

 

Given the definition of these two F# exceptions, let’s see how we can use them in code:

try

    // some bad statement

    ()

with

    | LightweightException(arg) -> printfn "LightweightException handled"

    | AccountOverdrawnException(s, d) -> printfn "Account %s is overdrawn by %f" s d

 

When catching F# exceptions, we need to use different matching patterns than when catching .NET exceptions. With F# exceptions, we match on the exception type and its arguments, as shown above. Even though we need to use different patterns for catching .NET and F# exceptions, we can combine them in the same with block, as illustrated in the example below (the first two patterns match F# exceptions, the last two match .NET exceptions):

exception LightweightException of string

exception AccountOverdrawnException of string * decimal

 

try

    // some bad statement

    ()

with

    | LightweightException(arg) -> printfn "LightweightException handled"

    | AccountOverdrawnException(s, d) -> printfn "Account %s is overdrawn by %f" s d

    | :? System.IO.FileNotFoundException -> printfn "File not found"

    | :? System.Exception -> printfn "Exception of last resort"

Throwing Exceptions

Not only can we create and handle .NET and F# exceptions, we can also throw or “raise” them explicitly from our code. We may want to do this to inform a caller that something went wrong, e.g., the caller passed in an invalid parameter, we could not perform a calculation, etc. There are two ways to generate exceptions: using raise and using failwith.

raise

We use the keyword raise to generate both .NET and F# exceptions. Using raise is quite simple, as demonstrated by the general syntax:

raise exception

Using raise causes the runtime to generate the specified exception and to start the standard stack unwinding and exception handling process. Here are a few examples:

exception AccountOverdrawnException of string * decimal

 

try

    let t, f = true, false

    if t then raise(AccountOverdrawnException("123", 4567.0m))

    if not f then raise(System.Exception("bad stuff happened"))

with

    | AccountOverdrawnException(acct, amt) -> printfn "Account %s overdrawn by %f" acct amt

    | :? System.Exception as err -> printfn "%s" err.Message

rethrow

As of this writing, F# supports the rethrow function, which enables us to re-raise an exception from within an exception handler. This enables our handler to process (or ignore) an exception and then “pass it up” the call stack so that other handlers can process it as well. You can use rethrow only from within an existing exception hander. The following code demonstrates how to use it:

try

    let x = 10

    Some(x)

with

    | :? System.Exception as e -> printfn "%s" e.Message; rethrow()

Note that rethrow does not take any parameters. It re-raises the exception present in the current context.


failwith

To generate an F#-specific exception, you call failwith via the following syntax:

failwith error-string

In response, F# generates an exception of type Microsoft.FSharp.Core.FailureException, which is mapped to the name F# name Failure. You can use the Failure pattern in with blocks as to find an appropriate handler. In the following example[1], we see how to generate and handle an exception generated via failwith:

let divide_failwith x y =

  if (y = 0) then failwith "Divisor cannot be zero."

  else x / y

 

let test_divide_failwith x y =

  try

     divide_failwith x y

  with

     | Failure(msg) -> printfn "%s" msg; 0

 

let result1 = test_divide_failwith 100 0


invalidArg

F# supports another keyword, invalidArg, that we can use to generate exceptions. We use invalidArg to indicate that a caller has passed to our function, method, etc. in unexpected or invalid argument. invalidArg uses the following syntax:

invalidArg parameter-name error-message-string

Calling invalidArg generates a .NET System.ArgumentException where the error-message-string becomes the Message property of the exception object. In the following example, we see how to generate and handle this type of exception:

let kids = [| "Jessica", "Kimberly", "Melissa" |]

let getKid n =

    if (n >=0 && n < kids.Length) then kids.[n]

    else

        invalidArg "n" "Please pass a number from 0-2"

       

try

    let k1 = getKid 1

    let k2 = getKid 100

    Some(k1)

with

    | :? System.ArgumentException as e -> printfn "%s" e.Message; None

A Note on sprintf

Note the call in invalidArg above. It takes 2 parameters: the name of the invalid argument (“n”) and a diagnostic message. The second parameter, the diagnostic message, becomes the thrown exception’s Message property.

As written, the invalidArg call uses a hard-coded string for the message, ending in 0-2. This is a rather brittle way to handle the error, since at some point in the future, we may want to extend the array or otherwise change its length. What we’d really like to do is format the string based on the calculated length of the array. Unfortunately, trying to directly embed a  printf, printfn or tuple-formatted string does not work – the compiler rejects the construct.

In order to format strings to be used where printf cannot be used - or anywhere, really - you can use the sprintf function. Using sprintf, we can rewrite the invalidArg call as follows:

invalidArg "Invalid parameter n" (sprintf "…number from 0-%d" kids.Length)

sprintf is a handy function for formatting strings in-situ. Of course, being in the .NET environment, you have all of its string formatting facilities at your disposal, e.g., System.String.Format as well.

Additional Matching Options

The following list summarizes the different patterns we can use to match exceptions with exception handlers.

·         :? exception-type

o   Matches the specified .NET exception type.

·         :? exception-type as identifier

o   Matches the specified .NET exception type and gives the exception a name. This allows us to interrogate the fields, etc. defined on the exception class.

·         exception-type(args)

o   Matches an F# exception type and binds the arguments.

·         identifier

o   Matches any exception and binds the name to the exception object. This is equivalent to :? System.Exception as identifier.

·         identifier when condition

o   Match any exception if the condition is true.

Remember that .NET-based exceptions are distinct from F#-based exceptions, because .NET exceptions are class instances, where F# exceptions are typically not. We use different pattern matching constructs to match .NET exceptions.

Some Notes on Using Exceptions

Please keep in mind the following rule of thumb: exceptions should be used only to handle truly “exceptional” conditions that arise during program execution. They should not be used to control normal program flow. If they are used to control normal program flow, there are several downsides. First, the semantics of the exception is confused, making room for downstream misunderstandings and subsequent coding errors. Second, exceptions impose a runtime penalty, since an exception frame needs to be created and managed.

Many non-trivial software systems tend to use a hierarchy of domain-specific exceptions. This is often a good idea, since it makes clear the domain semantics of a given error, and allows handlers to be clear and precise about their intended function. For example, in an email system, you could imagine exceptions dealing with message fidelity, network connectivity, message sizes, attachments, etc. Domain-centric exceptions provide clarity and consistency throughout the code base, and make clear both the sender’s and receiver’s jobs.

There is a lot more we can say about exceptions and how best to use them; however, a full discussion is beyond the scope of this book. Let’s move on and talk about trying to prevent exceptions in the first place.

Assertions

While bugs in software are inevitable, there are techniques that have proven useful in limiting their existence and lifespan. One of the techniques, defensive programming, has many facets, one of which is to use assertions. We use assertion to test conditions before or after we execute a block of code. We refer to these as pre-condition and post-condition checks respectively.

The .NET library ships with the System.Diagnostics namespace and the System.Diagnostics.Debug class. The Debug class has a fair number of methods for helping us write robust code, and F#, being a .NET language, has access to all these facilities.

F# directly exposes Debug.Assert from .NET through its the keyword assert. Using assert is simple, as demonstrated by the following syntax:

assert condition

condition is a Boolean expression. If it evaluates to true, the application proceeds normally, executing whatever follows the assert clause. Otherwise, assert generates a system error dialog box. Due to their “interactive” nature, e.g., they display a dialog box, asserts are most useful during the interactive testing of your software. They are generally used to test pre- and post conditions, e.g., they ensure the values passed to a function are correct, that return values fall within an expected range, etc.

Failures generated by assert cannot be caught by using F# exception handling, and do not affect the F# Interactive Console. This means that they are really only useful during testing, since there’s no way to deal with them other than seeing their output (the system dialog box) when they fail.

Assertion checking is enabled only when you compile in Debug mode. You compile in Debug mode by defining the constant DEBUG as part of the definitions passed to the compiler (see fsc debug and –-define). In the Visual Studio project system, by default, the DEBUG constant is defined in the Debug configuration but not in the Release configuration. This means that assertions have no impact or effect in Release builds by default – they are compiled away in Release builds.

In the following code, we use assert to ensure that the given input string is not empty.

let Lookup(name: string) =

    assert(name.Length > 0)

    // lookup here

To use the other Debug facilities, you can access the Debug class directly. Of course, you also have available to you the full power debuggers, e.g., the Visual Studio debugger, and performance tools that you have with any other .NET language. What I wanted to cover here is the F#-specific debugging facilities, which really just amounts to the assert keyword.

What You Need to Know

·         Exceptions typically result from unanticipated or problematic conditions that arise during the execution of a program.

·         From an F# perspective, exceptions come in two flavors: .NET exceptions and F# exceptions.

·         When an exception occurs, F# stops evaluation of the current expression and begins to unwind the call stack looking for an appropriate exception handler. If F# finds one, it executes the handler. If not, it displays an error message and terminates the process.

·         You can catch .NET exceptions by wrapping expressions in try…with and pattern matching using :? (or one of its kin). Some code in expressions being evaluated when an exception occurs may not complete execution.

·         You can catch F# exceptions using try…with and matching the exception value in the pattern matching clauses of the with block.

·         You can catch both .NET and F# exceptions using the same with block using the appropriate pattern matching constructs.

·         To ensure that certain code is executed, even if an exception occurs, wrap the expression with try…finally.

·         F# does not support a combined try…with…finally construct. You can, however, simply embed try…with blocks inside try…finally blocks to accomplish the purpose.

·         You can define your own F# exceptions using the exception keyword.

·         You can throw exceptions using built-in F# keywords: raise, rethrow, failwith and invalidArg.

·         F# supports the assert keyword to aid in defensive programming, but does not generate an exception that can be caught with a try…with wrapper.

 



[1] Taken from the MSDN documentation.

 

Feedback

We welcome your feedback. If you have comments or questions about this chapter, please feel free to e-mail us at

Keep Reading

Next Chapter...