Define
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.
Raise(Throw)
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.
Try-With/Finally (Catch)
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.
Note:
- 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.”