Created
May 26, 2022 09:35
-
-
Save Savelenko/9a6bcd378bc1a3cbabcd3fccaa6f2995 to your computer and use it in GitHub Desktop.
Error builder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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