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:
- Detecting exceptions.
- Logging and reporting information.
- Generating events that can be monitored externally to assist system operation."
- Detecting exceptions.
- 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
|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||Guarantees execution of specified final block.|
|failwith string||Throws a Failure exception with the specified string.|
F#'s predifined library exceptions
|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.|
Exceptions are caught/filtered using pattern matching and a Failure exception can be matched like a discriminated union type:
try failwith "Too many errors"Outputs: "Too many errors"
with | Failure s -> printf "%s" s
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
Reading the Message property value of a custom F# exception
try raise (AppException "Bang!") with | e -> printf "%s" e.Message
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!"))Outputs: "Vanilla!"
| Failure s -> printf "%s" s
| 😕 VanillaException as e -> printf "%s" e.Message
| e -> e.Message
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.
Further reading check out: Expert F# Chapter 4 Introducing Imperative Programming.