Usually, you do not need to define custom exceptions during programming in F#. If you do scripting in F#, in the most cases you will be happy with standard .NET exception types and F# built-in helper functions. But when you create a custom library or design complex enterprise software, you will need to use power of .NET exceptions. How you can do it from F#:
// C# style exception (possible, but not recommended) type MyCsharpException(msg, id:int) = inherit System.Exception(msg) member this.Id = id // F# style exceptions exception MyFsharpSimpleException exception MyFsharpException of string * int
Note that F# has a special keyword exception for defining “handy” exceptions.
F# provides set of functions (failwith, failwithf, invalidArg, invalidOp, nullArg) that help to raise most common exceptions. They are very convenient especially for F# scripts.
let rnd = System.Random() let rnd = System.Random() let raiseException() = match rnd.Next(8) with | 0 -> failwith "throws a generic System.Exception" | 1 -> failwithf "throws a generic Exception with formatted message (%d)" 1 | 2 -> invalidArg "_" "throws an ArgumentException" | 3 -> invalidOp "throws an InvalidOperationException" | 4 -> nullArg "throws a NullArgumentException" | 5 -> raise <| MyFsharpException("throws a MyFsharpException", 5) | 6 -> raise <| MyCsharpException("throws a MyCsharpException", 6) | 7 -> assert (2>1) | _ -> raise <| System.Exception("Impossible case")
The assert expression is syntactic sugar for System.Diagnostics.Debug.Assert. The assertion is triggered only if the DEBUG compilation symbol is defined.
The last step is to catch exceptions and handle them in a proper way.
let catchException() = try raiseException() with | Failure(msg) // 'Failure' active pattern catches only Exception objects -> printfn "%s" msg | MyFsharpException(msg, id) -> printfn "Catch F# exceptions using pattern matching" | : ? System.ArgumentException as ex -> printfn "Invalid argument '%s'" ex.ParamName | : ? MyCsharpException | : ? System.InvalidOperationException -> printfn "You can handle multiple exceptions at a time" | _ as ex -> printfn "Log: exception '%s'" (ex.GetType().Name) reraise() // re-raise exception let finaly() = try catchException() finally printfn "Now, I am ready for exceptions!"
‘: ?‘ is a two-symbol operator without space inside.
- Failure active pattern catches only System.Exception objects. It is useful to handle exceptions raised by failwith & failwithf functions.
- Exceptions defined using exception keyword could be handled automatically using pattern matching.
- F# provides reraise function that helps to raise a current exception one more time. This function can be used only from pattern matching rules of try-with expressions.
If you want to learn more about exceptions, read an amazing The “Expressions and syntax” series from Scott Wlaschin.
One thought on “F# Kung Fu #3: Exceptions recap.”