F#: Revisiting the Vending Machine Kata

I decided to revisit the Vending Machine code kata.
I flushed out the logic for this program via TDD.
I spent more time than I expected on the ‘return change’ feature of the vending machine.

Overall, it was a refreshing exercise that enabled me to think functionally again.


Here are the types that I flushed out via TDD:

(*Types*)
type Coin =    Quarter | Dime | Nickel
type Product = Chips   | Soda | Gum

type Purchase = { Product:Product ; Balance:Coin list }

type SelectionResult =
    | Purchased of Purchase
    | Requires  of decimal

Here are the functions that I flushed out via TDD:

(*Functions*)
let valueOf = function
    | Quarter -> 0.25m
    | Dime    -> 0.10m
    | Nickel  -> 0.05m

let costOf = function
    | Chips -> 0.25m
    | Soda  -> 0.50m
    | Gum   -> 0.10m

let balanceOf coins =
    
    (0.0m , coins) 
    ||> List.fold (fun balance coin -> balance + (valueOf coin))

let remaining balance product = (costOf product) - balance

let rec getChange balance change =
    
    let rec get remaining change =

        if remaining >= valueOf Quarter
        then get (remaining - valueOf Quarter) (Quarter::change)

        elif remaining >= valueOf Dime
        then get (remaining - valueOf Dime) (Dime::change)

        elif remaining >= valueOf Nickel
        then get (remaining - valueOf Nickel) (Nickel::change)

        else change

    get balance []


let attemptPurchase product deposited =

    if balanceOf deposited >= costOf product 
    then Purchased { Product=product
                     Balance= getChange (balanceOf deposited - costOf product) [] }
    else Requires (product |> remaining (balanceOf deposited))


let select product balance = 
    balance |> attemptPurchase product

Here are the tests:

[<Test>]
let ``depositing quarter results in $.25 balance``() =
    
    [Quarter] |> balanceOf
              |> should equal 0.25

[<Test>]
let ``depositing dime results in $.10 balance``() =
    
    [Dime] |> balanceOf
           |> should equal 0.10

[<Test>]
let ``depositing nickel results in $.05 balance``() =
    
    [Nickel] |> balanceOf
             |> should equal 0.05

[<Test>]
let ``depositing quarter can purchase chips``() =
    
    [Quarter]
    |> select Chips
    |> should equal ((Purchased {Product=Chips ; Balance=[] }))

[<Test>]
let ``depositing two quarters can purchase Soda``() =
    
    [Quarter ; Quarter] 
     |> select Soda
     |> should equal ((Purchased {Product=Soda ; Balance=[] }))

[<Test>]
let ``depositing 2 quarters equals balance of 50 cents`` () =
    [Quarter ; Quarter]
    |> balanceOf
    |> should equal 0.50

[<Test>]
let ``depositing dime can purchase Gum``() =
    
    [Dime]
    |> select Gum
    |> should equal ((Purchased {Product=Gum ; Balance=[] }))

[<Test>]
let ``depositing 1 quarter and attempting to buy soda requires 1 more .25 more`` () =
    [Quarter]
    |> select Soda
    |> should equal (Requires 0.25m)

[<Test>]
let ``zero balance and attempting to buy soda requires .50 more`` () =
    []
    |> select Soda
    |> should equal (Requires 0.50m)

[<Test>]
let ``0.75 balance and attempting to buy soda requires returns 0.25`` () =
    [Quarter ; Quarter ; Quarter]
    |> select Soda
    |> should equal (Purchased {Product=Soda ; Balance=[Quarter] })

[<Test>]
let ``0.85 balance and attempting to buy soda requires returns 0.35`` () =
    [Quarter ; Quarter ; Quarter ; Dime]
    |> select Soda
    |> should equal (Purchased {Product=Soda ; Balance=[Dime ; Quarter] })

The entire source code is below:

module VendingMachine

open NUnit.Framework
open Xunit
open FsUnit

(*Types*)
type Coin =    Quarter | Dime | Nickel
type Product = Chips   | Soda | Gum

type Purchase = { Product:Product ; Balance:Coin list }

type SelectionResult =
    | Purchased of Purchase
    | Requires  of decimal

(*Functions*)
let valueOf = function
    | Quarter -> 0.25m
    | Dime    -> 0.10m
    | Nickel  -> 0.05m

let costOf = function
    | Chips -> 0.25m
    | Soda  -> 0.50m
    | Gum   -> 0.10m

let balanceOf coins =
    
    (0.0m , coins) 
    ||> List.fold (fun balance coin -> balance + (valueOf coin))

let remaining balance product = (costOf product) - balance

let rec getChange balance change =
    
    let rec get remaining change =

        if remaining >= valueOf Quarter
        then get (remaining - valueOf Quarter) (Quarter::change)

        elif remaining >= valueOf Dime
        then get (remaining - valueOf Dime) (Dime::change)

        elif remaining >= valueOf Nickel
        then get (remaining - valueOf Nickel) (Nickel::change)

        else change

    get balance []


let attemptPurchase product deposited =

    if balanceOf deposited >= costOf product 
    then Purchased { Product=product
                     Balance= getChange (balanceOf deposited - costOf product) [] }
    else Requires (product |> remaining (balanceOf deposited))


let select product balance = 
    balance |> attemptPurchase product



[<Test>]
let ``depositing quarter results in $.25 balance``() =
    
    [Quarter] |> balanceOf
              |> should equal 0.25

[<Test>]
let ``depositing dime results in $.10 balance``() =
    
    [Dime] |> balanceOf
           |> should equal 0.10

[<Test>]
let ``depositing nickel results in $.05 balance``() =
    
    [Nickel] |> balanceOf
             |> should equal 0.05

[<Test>]
let ``depositing quarter can purchase chips``() =
    
    [Quarter]
    |> select Chips
    |> should equal ((Purchased {Product=Chips ; Balance=[] }))

[<Test>]
let ``depositing two quarters can purchase Soda``() =
    
    [Quarter ; Quarter] 
     |> select Soda
     |> should equal ((Purchased {Product=Soda ; Balance=[] }))

[<Test>]
let ``depositing 2 quarters equals balance of 50 cents`` () =
    [Quarter ; Quarter]
    |> balanceOf
    |> should equal 0.50

[<Test>]
let ``depositing dime can purchase Gum``() =
    
    [Dime]
    |> select Gum
    |> should equal ((Purchased {Product=Gum ; Balance=[] }))

[<Test>]
let ``depositing 1 quarter and attempting to buy soda requires 1 more .25 more`` () =
    [Quarter]
    |> select Soda
    |> should equal (Requires 0.25m)

[<Test>]
let ``zero balance and attempting to buy soda requires .50 more`` () =
    []
    |> select Soda
    |> should equal (Requires 0.50m)

[<Test>]
let ``0.75 balance and attempting to buy soda returns 0.25`` () =
    [Quarter ; Quarter ; Quarter]
    |> select Soda
    |> should equal (Purchased {Product=Soda ; Balance=[Quarter] })

[<Test>]
let ``0.85 balance and attempting to buy soda returns 0.35`` () =
    [Quarter ; Quarter ; Quarter ; Dime]
    |> select Soda
    |> should equal (Purchased {Product=Soda ; Balance=[Dime ; Quarter] })
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: