Skip to content

Instantly share code, notes, and snippets.

@Savelenko
Created May 26, 2022 09:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Savelenko/9a6bcd378bc1a3cbabcd3fccaa6f2995 to your computer and use it in GitHub Desktop.
Save Savelenko/9a6bcd378bc1a3cbabcd3fccaa6f2995 to your computer and use it in GitHub Desktop.
Error builder
module Facility
/// A facility which admits visitors (or does it?).
type Facility =
| BlackMesa
| DeathStar
| TeaHouse
(*
Similarly to the Visitor module, instead of defining the following error type
type FacilityError =
| FacilityDoesNotExist
we define an error builder instead:
*)
type FacilityErrorBuilder<'e> =
abstract FacilityDoesNotExist : 'e
let directions (errorBuilder : FacilityErrorBuilder<'e>) = function
| BlackMesa -> Error errorBuilder.FacilityDoesNotExist
| DeathStar -> Ok "Enjoy the tractor beam"
| TeaHouse -> Ok "Ask any grandma"
open Facility
open Visitor
(*
In this higher-level module we want to implement the use-case of registering a visitor and providing directions to the
facility if registration is successful. Instead of defining an error DU type which combines individual errors by
"repacking" them like this
type RegistrationError = | FacilityError of FacilityError | VisitorError of VisitorRegistrationError
we define a single "flat" error type as follows:
*)
type RegistrationError =
| VisitorAlreadyRegistered
| InvalidVisitorStatus of string
| FacilityDoesNotExist
(*
... and the implementation of all relevant error builders which "injects" abstract errors of individual builders into
this simple error type:
*)
type private ErrorBuilder () =
interface VisitorErrorBuilder<RegistrationError> with
member _.InvalidVisitorStatus status = InvalidVisitorStatus status
member _.VisitorAlreadyRegistered = VisitorAlreadyRegistered
interface FacilityErrorBuilder<RegistrationError> with
member _.FacilityDoesNotExist = FacilityDoesNotExist
let private eb = ErrorBuilder ()
(*
Unfortunately ErrorBuilder must be a class as an object expression does not seem to work. The resulting use-case
function then uses the error builder on both function which, conceptually, return distinct errors:
*)
let registerVisitorToFacility visitor facility =
match registerVisitor eb visitor, directions eb facility with
| _, Error e
| Error e, _ -> Error e
| Ok _, Ok directions -> Ok (sprintf "%A registered, follow these directions: %s" visitor directions)
[<EntryPoint>]
let main argv =
printfn "%A" (registerVisitorToFacility (Visitor "new") BlackMesa) // Error
printfn "%A" (registerVisitorToFacility (Visitor "accidental") DeathStar) // Ok
printfn "%A" (registerVisitorToFacility (Visitor "") TeaHouse) // Error
0
module Visitor
/// A visitor of a facility.
type Visitor = Visitor of string
(*
Normally we would define the following type for errors:
type VisitorRegistrationError =
| VisitorAlreadyRegistered
| InvalidVisitorStatus of string
Instead, we define an "error builder". Note the similarities in structure: the builder
almost mimics the DU. This is not a coincidence, in a way the interfaces makes it
explicit what the signatures of the DU constructors are.
*)
type VisitorErrorBuilder<'e> =
abstract VisitorAlreadyRegistered : 'e
abstract InvalidVisitorStatus : string -> 'e
/// Registers a visitor taking into account the visitors status.
let registerVisitor (errorBuilder : VisitorErrorBuilder<'e>) = function
| Visitor "registered" -> Error errorBuilder.VisitorAlreadyRegistered
| Visitor ("" as status) -> Error (errorBuilder.InvalidVisitorStatus status)
| _ -> Ok ()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment