Custom Errors in F#

open System

// A custom error type usually has the suffix "Error".
type ArgError(arg: int, message: string) =
    inherit Exception(sprintf "%d - %s" arg message)
    member this.Arg = arg
    member this.Message = message

// Function that might return an error
let f arg =
    if arg = 42 then
        Error (ArgError(arg, "can't work with it"))
    else
        Ok (arg + 3)

// Main function
[<EntryPoint>]
let main _ =
    // Pattern matching is used to handle the Result type
    match f 42 with
    | Ok result -> 
        printfn "Result: %d" result
    | Error err when err :? ArgError ->
        let argError = err :?> ArgError
        printfn "%d" argError.Arg
        printfn "%s" argError.Message
    | Error _ ->
        printfn "err doesn't match ArgError"
    0

In F#, we can implement custom errors by creating a new type that inherits from the Exception class. This is similar to implementing the Error() method in the original example.

The f function returns a Result type, which is F#’s way of handling operations that might fail. It’s similar to returning a tuple of (int, error) in the original code.

In the main function, we use pattern matching to handle the Result type. This is analogous to using errors.As in the original code. The when keyword in the pattern match allows us to check if the error is of type ArgError.

To run this program:

$ dotnet fsi custom-errors.fsx
42
can't work with it

In F#, we typically use the dotnet fsi command to run F# scripts directly, without needing to compile them first. This is similar to using go run in Go.

F# provides a robust type system and pattern matching capabilities that make handling custom errors straightforward and type-safe.