2 people like it.

Retail with units of measure

Sample retail domain model, using F# types (records and discriminated unions) with JSON sample data using F# Data type provider.

 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: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
81: 
82: 
83: 
84: 
85: 
86: 
87: 
88: 
89: 
90: 
namespace Retail

// ----------------------------------------------
// Domain
// ----------------------------------------------
type [<Measure>] GBP
type [<Measure>] Q

type UnitPrice = decimal<GBP/Q>
type Amount = decimal<GBP>
type Name = string
type Code = string
type Quantity = decimal<Q>

type Product = Code * Name * UnitPrice

type Tender = 
  | Cash 
  | Card of string 
  | Voucher of Code 

type LineItem =
  | SaleItem of Product * Quantity
  | TenderItem of Tender * Amount
  | CancelItem of int

type Basket = list<LineItem>

// ----------------------------------------------
// Data
// ----------------------------------------------
module Data =

  let products =
    [ "A1", "Border oat crumbles", 0.69M<GBP/Q>
      "B1", "Tea", 1.49M<GBP/Q>
      "C1", "Phil's phone", 299.9M<GBP/Q>
      "D1", "Phil's mac", 1200.0M<GBP/Q> ]

  let lookup (search:Code) : Product option = 
    products
    |> List.tryFind (fun (code, _, _) -> code = search)

// ----------------------------------------------
// Calculations
// ----------------------------------------------
module Calculations = 
  open System

  let total (basket:Basket) =
    basket |> List.sumBy (fun item ->
      match item with
      | SaleItem((_, _, price), q) -> 
          price * q
      | TenderItem(_, value) -> 0.0M<GBP>
      | CancelItem(index) ->
          let cancelled = List.nth basket index
          match cancelled with
          | SaleItem((_, _, price), q) ->
              -1.0M * price * q
          | CancelItem _ | TenderItem _ -> 
              invalidOp "You can only cancel SaleItems!" )
        
// ----------------------------------------------
// "User interface"
// ----------------------------------------------

module Main = 
  open System
  open Data
  open Calculations

  let rec purchase (basket:Basket) =
    let code = Console.ReadLine()
    match code, lookup code with
    | "q", _ ->
        printfn "Finished"
        basket
    | _, None -> 
        printfn "Not found: %s" code
        purchase basket
    | _, Some prod ->
        printfn "Adding: %A" prod
        let item = SaleItem(prod, 1.0M<Q>)
        purchase (item::basket)


  let basket = purchase []
  printfn "Purchase: %A" basket
  printfn "Total: %A" (total basket)
Multiple items
type MeasureAttribute =
  inherit Attribute
  new : unit -> MeasureAttribute

Full name: Microsoft.FSharp.Core.MeasureAttribute

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

Full name: Retail.Q
type UnitPrice = decimal<GBP/Q>

Full name: Retail.UnitPrice
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

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

--------------------
type decimal = System.Decimal

Full name: Microsoft.FSharp.Core.decimal

--------------------
type decimal<'Measure> = decimal

Full name: Microsoft.FSharp.Core.decimal<_>
[<Measure>]
type GBP

Full name: Retail.GBP
type Amount = decimal<GBP>

Full name: Retail.Amount
type Name = string

Full name: Retail.Name
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 Code = string

Full name: Retail.Code
type Quantity = decimal<Q>

Full name: Retail.Quantity
type Product = Code * Name * UnitPrice

Full name: Retail.Product
type Tender =
  | Cash
  | Card of string
  | Voucher of Code

Full name: Retail.Tender
union case Tender.Cash: Tender
union case Tender.Card: string -> Tender
union case Tender.Voucher: Code -> Tender
type LineItem =
  | SaleItem of Product * Quantity
  | TenderItem of Tender * Amount
  | CancelItem of int

Full name: Retail.LineItem
union case LineItem.SaleItem: Product * Quantity -> LineItem
union case LineItem.TenderItem: Tender * Amount -> LineItem
union case LineItem.CancelItem: int -> LineItem
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<_>
type Basket = LineItem list

Full name: Retail.Basket
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
namespace Microsoft.FSharp.Data
val products : (string * string * decimal<GBP/Q>) list

Full name: Retail.Data.products
val lookup : search:Code -> Product option

Full name: Retail.Data.lookup
val search : Code
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val tryFind : predicate:('T -> bool) -> list:'T list -> 'T option

Full name: Microsoft.FSharp.Collections.List.tryFind
val code : string
namespace System
val total : basket:Basket -> decimal<GBP>

Full name: Retail.Calculations.total
val basket : Basket
val sumBy : projection:('T -> 'U) -> list:'T list -> 'U (requires member ( + ) and member get_Zero)

Full name: Microsoft.FSharp.Collections.List.sumBy
val item : LineItem
val price : UnitPrice
val q : Quantity
val value : Amount
val index : int
val cancelled : LineItem
val nth : list:'T list -> index:int -> 'T

Full name: Microsoft.FSharp.Collections.List.nth
val invalidOp : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.invalidOp
module Main

from Retail
Multiple items
namespace System.Data

--------------------
module Data

from Retail

--------------------
namespace Microsoft.FSharp.Data
module Calculations

from Retail
val purchase : basket:Basket -> Basket

Full name: Retail.Main.purchase
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...

Full name: System.Console
Console.ReadLine() : string
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
val prod : Product
val basket : Basket

Full name: Retail.Main.basket

More information

Link:http://fssnip.net/kW
Posted:10 years ago
Author:Tomas Petricek & Phil Trelford
Tags: retail , json , type providers