F# Custom Exceptions

This post tries to explore exception handling in F# with custom exception types.

Some background on .Net exceptions

  • Exceptions happen: "Programs must be able to uniformly handle errors that occur during execution. The common language runtime greatly assists the design of fault-tolerant software by providing a model for notifying programs of errors in a uniform way. All .NET Framework operations indicate failure by throwing exceptions." - .NET Framework Developer's Guide: Handling and Throwing Exceptions.
  • Deal with them: "To build successful and flexible applications that can be maintained and supported easily, you must adopt an appropriate strategy for exception management. You must design your system to ensure that it is capable of the following: 
  • But: "Do not rely on exceptions in your code. Exceptions can cause performance to suffer significantly, so you should avoid using them as a way to control normal program flow. If it is possible to detect in code a condition that would cause an exception, do so rather than catching the exception itself and handling the condition."  - Microsoft TechNet: Developing High-Performance ASP.NET Applications.
  • And: "In most cases, use the predefined exceptions types. Define new exception types only for programmatic scenarios. Introduce a new exception class to enable a programmer to take a different action in code based on the exception class." - .Net Framework Developers Guide - Best Practices for Handling Exceptions.

Quick guide to F# exception handling keywords

F# C# equivalent Description
raise exception throw exception; Throws the specified exception.
rethrow () throw; Rethrows the current exception unchanged from a catch block.
try ... with try-catch Filters exceptions.
try ... finally

try-finally

Guarantees execution of specified final block.
failwith string   Throws a Failure exception with the specified string.

F#'s predifined library exceptions

Exception Usage Description
Failure of string failwith "Bang!" General failure.
InvalidArgument of string invalid_arg "arg1" Invalid argument exception.
exn raise (new exn("Bang!")) System.Exception type abbreviation.

Filtering Failures

Exceptions are caught/filtered using pattern matching and a Failure exception can be matched like a discriminated union type:

     try failwith "Too many errors"
    with | Failure s -> printf "%s" s

Outputs: "Too many errors"

Some reusable System.Exception based classes

Frequently it is possible to simply reuse existing exception classes in your code:

Exception class

Thrown when...

ApplicationException

a non-fatal application error occurs.

ArgumentNullException

a null reference is passed to a method that does not accept it.

ArgumentOutOfRangeException

the value of an argument is outside the allowable range of values.

FormatException

the format of an argument does not meet the parameter specifications.

InvalidOperationException

a method call is invalid for the object's current state.

NotImplementedException

a requested method or operation is not implemented.

Custom F# exceptions

In some cases we have a pragmatic reason to introduce our own custom exceptions, for example if we define our own data layer we may want to have an exception type for this layer. In the same way the .Net framework defines the SqlException class  for SQL specific exceptions. F# Custom exceptions provide an extremely concise form definition like discriminated unions which makes them simarly easy to pattern match against:

  • Defining a custom F# exception

     exception AppException of string
    
  • Raising a custom F# exception 

     raise (AppException "Bang!")
    
  • Catching a custom F# exception

     try raise (AppException "Bang!") with | AppException s -> printf "%s" s
    

    Outputs: "Bang!"

  • Reading the Message property value of a custom F# exception

     try raise (AppException "Bang!") with | e -> printf "%s" e.Message
    

    Outputs: "Exn+AppException[System.String]..."

So this all seems to make F# custom exceptions great for F# projects which are self-contained or which have F# projects as final consumers. However F# custom exceptions current concise construction syntax does not allow us to pass the values for the Message and InnerException properties to the underlying System.Exception. It is possible to override the System.Exception.Message property but then again we lose a lot of our original conciseness. So I think this means then that F# custom exceptions are probably not suitable if you are going to be throwing an exception out of your F# project to say a C# project or to a vanilla exception logging mechanism.

An Alternative - custom exceptions using plain old inheritance

You will see below custom exceptions using inheritance are also pretty concise for definition and for pattern matching and also allow you to easily initialize the System.Exception's Message and InnerException properties. Here we make good use of F#'s constructed classes, optional parameters and inheritance facilities:

  • Defining an F# custom exception using inheritance

     type VanillaException (message:string, ?innerException:exn) =
        inherit ApplicationException (message, 
            match innerException with | Some(ex) -> ex | _ -> null)            
    
  • Pattern matching exception types in F#  

     try raise (new VanillaException("Vanilla!")) 
    with 
    | Failure s -> printf "%s" s
    | :? VanillaException as e -> printf "%s" e.Message
    | e -> e.Message
    

    Outputs: "Vanilla!"

So it appears then that this approach is more suited to F# originated frameworks and libraries that expose exceptions to other languages and/or logging frameworks. Finally I would recommend that in this case that you catch any F# custom exceptions at the application boundary including Failure exceptions and rethrow them as vanilla .Net exceptions.

Phil Trelford

Further reading check out: Expert F# Chapter 4 Introducing Imperative Programming.