This post tries to explore exception handling in F# with custom exception types.
Some background on .Net exceptions
Quick guide to F# exception handling keywords
F#'s predifined library exceptions
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
Some reusable System.Exception based classes
Frequently it is possible to simply reuse existing exception classes in your code:
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:
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
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:
type VanillaException (message:string, ?innerException:exn) =
inherit ApplicationException (message,
match innerException with | Some(ex) -> ex | _ -> null)
try raise (new VanillaException("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.