Errors in F#

open System

// By convention, errors are often represented as Option types in F#,
// where None indicates success and Some error indicates failure.

let f arg =
    if arg = 42 then
        Error "can't work with 42"
    else
        Ok (arg + 3)

// In F#, we can use discriminated unions to represent specific error types
type TeaError = 
    | OutOfTea
    | NoPower of string

let makeTea arg =
    match arg with
    | 2 -> Error OutOfTea
    | 4 -> Error (NoPower "can't boil water")
    | _ -> Ok ()

[<EntryPoint>]
let main argv =
    for i in [7; 42] do
        match f i with
        | Ok result -> printfn "f worked: %d" result
        | Error e -> printfn "f failed: %s" e

    for i in 0..4 do
        match makeTea i with
        | Ok () -> 
            printfn "Tea is ready!"
        | Error err ->
            match err with
            | OutOfTea -> printfn "We should buy new tea!"
            | NoPower msg -> printfn "Now it is dark. %s" msg

    0 // return an integer exit code

In F#, error handling is typically done using the Result type or Option type, which are similar to Go’s approach of returning explicit error values. Here’s an explanation of the key differences and concepts:

  1. Instead of returning a tuple with a value and an error, F# uses the Result type, which can be either Ok for success or Error for failure.

  2. F# doesn’t have a built-in error interface. Instead, we can use discriminated unions to define custom error types, as shown with TeaError.

  3. Pattern matching is used extensively in F# for handling different cases, including error checking. This replaces the if-else chains seen in the Go example.

  4. F# doesn’t have a direct equivalent to Go’s error wrapping with fmt.Errorf. However, you can create nested error types or use a library like FsToolkit.ErrorHandling for more advanced error handling scenarios.

  5. The errors.Is functionality in Go is replaced by pattern matching on the error types in F#.

This F# code demonstrates similar error handling patterns to the Go example, adapted to F#’s functional programming style and type system. It shows how to create and handle custom error types, and how to use pattern matching for control flow based on error conditions.