Nanyx

Vending machine

An immutable state machine whose operations return the next state or a typed error.

module vendingMachine

type Item = (price: int, count: int)

type Machine = (
  inventory: map(string, Item)
  coinsDeposited: int
)

export type VendError =
  | #invalidSelection
  | #outOfStock
  | #insufficientFunds(int)   -- how many more coins are needed

export def newMachine = Machine(
  inventory = [
    "Candy Bar" => Item(price = 12, count = 7)
    "Chips" => Item(price = 10, count = 4)
    "Pretzels" => Item(price = 7, count = 11)
  ]
  coinsDeposited = 0
)

export def Machine.deposit: (Machine, int) -> Machine = { machine, coins ->
  machine with coinsDeposited = machine.coinsDeposited + coins
}

-- Vending is a function from the machine before to the machine after
export def Machine.vend: (Machine, string) -> #some(Machine) | #error(VendError) = { machine, name ->
  match machine.inventory(name)
    | #notFound -> #error(#invalidSelection)
    | #some(count = 0, ...) -> #error(#outOfStock)
    | #some(item) ->
      if item.price > machine.coinsDeposited then
        #error(#insufficientFunds(item.price - machine.coinsDeposited))
      else
        #some(machine with (
          coinsDeposited = machine.coinsDeposited - item.price
          inventory = machine.inventory \Map.insert(name, item with count = item.count - 1)
        ))
}

@entry
def main = {
  def result = proc {
    def machine = newMachine \deposit(20)
    def afterChips = try machine \vend("Chips")
    def afterCandy = try afterChips \vend("Candy Bar")
    return afterCandy
  }

  match result
    | #some(machine) -> println("Dispensed; {machine.coinsDeposited} coins left")
    | #error(#invalidSelection) -> println("No such item")
    | #error(#outOfStock) -> println("Out of stock")
    | #error(#insufficientFunds(needed)) -> println("Insert {needed} more coins")
}

This program lives in the repository as examples/vendingMachine.nyx.