2 people like it.

"Guard" helper function for railway error handling

Defines a "guard" function for railway-style error handing which allows you to concisely verify a condition when handling errors using Choice<'T, 'Error>. It checks the condition and if it is false, returns Choice2Of2 with the specified error value. If the condition is true then it returns Choice1Of2 (). Loosely inspired by Swift 2.0's guard keyword.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
open ExtCore.Control

let succeed = Choice1Of2
let fail = Choice2Of2
let guard cond err = if cond then succeed () else fail err

[<Measure>] type GBP 

type VendingMachineState =
    { Items : Map<string, int * float<GBP>>
      Funds : float<GBP> }

type VendingMachineError =
    | InvalidCashAmount of float<GBP>
    | UnknownItem of string
    | OutOfStock of string
    | InsufficientFunds of float<GBP>

let findItem item vendingMachine =
    match vendingMachine.Items |> Map.tryFind item with
    | Some result -> succeed result
    | None        -> fail (UnknownItem item) 

let payIn cash vendingMachine = choice {
    do! guard (cash > 0.0<GBP>) (InvalidCashAmount cash)
    return { vendingMachine with Funds = vendingMachine.Funds + cash } }

let vend item vendingMachine = choice {
    let! (quantity, price) = findItem item vendingMachine
    do! guard (quantity > 0) (OutOfStock item)
    do! guard (vendingMachine.Funds >= price) (InsufficientFunds <| price - vendingMachine.Funds)
    return (item,
        { vendingMachine with
            Funds = vendingMachine.Funds - price 
            Items = vendingMachine.Items |> Map.add item (quantity - 1, price) }) }

let vendingMachine = 
    { Items =
        [("Candy bar", (4, 1.20<GBP>))
         ("Coke"     , (0, 0.80<GBP>))
         ("Crisps"   , (2, 0.55<GBP>))]
        |> Map.ofList
      Funds = 0.60<GBP> }

// note : vending machine is immutable: each of the transactions starts with the same initial state
// defined above and returns a new vending machine state (or error)
vendingMachine |> payIn 2.0<GBP> |> printfn "%A"
vendingMachine |> vend "Crisps" |> printfn "%A"
vendingMachine |> vend "Coke" |> printfn "%A"
vendingMachine |> vend "Diet coke" |> printfn "%A"
vendingMachine |> vend "Candy bar" |> printfn "%A"
namespace ExtCore
namespace ExtCore.Control
val succeed : arg0:'a -> Choice<'a,'b>

Full name: Script.succeed
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
val fail : arg0:'a -> Choice<'b,'a>

Full name: Script.fail
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
val guard : cond:bool -> err:'a -> Choice<unit,'a>

Full name: Script.guard
val cond : bool
val err : 'a
Multiple items
type MeasureAttribute =
  inherit Attribute
  new : unit -> MeasureAttribute

Full name: Microsoft.FSharp.Core.MeasureAttribute

--------------------
new : unit -> MeasureAttribute
[<Measure>]
type GBP

Full name: Script.GBP
type VendingMachineState =
  {Items: Map<string,(int * float<GBP>)>;
   Funds: float<GBP>;}

Full name: Script.VendingMachineState
VendingMachineState.Items: Map<string,(int * float<GBP>)>
Multiple items
module Map

from ExtCore.Collections

--------------------
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string

--------------------
type string<'Measure> = string

Full name: ExtCore.string<_>
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
VendingMachineState.Funds: float<GBP>
type VendingMachineError =
  | InvalidCashAmount of float<GBP>
  | UnknownItem of string
  | OutOfStock of string
  | InsufficientFunds of float<GBP>

Full name: Script.VendingMachineError
union case VendingMachineError.InvalidCashAmount: float<GBP> -> VendingMachineError
union case VendingMachineError.UnknownItem: string -> VendingMachineError
union case VendingMachineError.OutOfStock: string -> VendingMachineError
union case VendingMachineError.InsufficientFunds: float<GBP> -> VendingMachineError
val findItem : item:string -> vendingMachine:VendingMachineState -> Choice<(int * float<GBP>),VendingMachineError>

Full name: Script.findItem
val item : string
val vendingMachine : VendingMachineState
val tryFind : key:'Key -> table:Map<'Key,'T> -> 'T option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryFind
union case Option.Some: Value: 'T -> Option<'T>
val result : int * float<GBP>
union case Option.None: Option<'T>
val payIn : cash:float<GBP> -> vendingMachine:VendingMachineState -> Choice<VendingMachineState,VendingMachineError>

Full name: Script.payIn
val cash : float<GBP>
val choice : ChoiceBuilder

Full name: ExtCore.Control.WorkflowBuilders.choice
val vend : item:string -> vendingMachine:VendingMachineState -> Choice<(string * VendingMachineState),VendingMachineError>

Full name: Script.vend
val quantity : int
val price : float<GBP>
val add : key:'Key -> value:'T -> table:Map<'Key,'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.add
val vendingMachine : VendingMachineState

Full name: Script.vendingMachine
val ofList : elements:('Key * 'T) list -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofList
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Raw view Test code New version

More information

Link:http://fssnip.net/rn
Posted:9 years ago
Author:Anton Tcholakov
Tags: error handling , choice , extcore