🎓 A1Learner
🔍
0 of 54 lessons completed

Haskell Project: Budget

A budget project stores priced items and works out what they cost. This project keeps every price in cents and formats the answers as dollars.

Formatted lines for each item

formatItem builds one line per item from its name and price. The four lines print in list order.

data Item = Item { itemName :: String, itemPrice :: Int } deriving (Show, Eq)
formatPrice :: Int -> String
formatPrice cents = "$" ++ show (cents `div` 100) ++ "." ++ centsText
  where
    remainder = cents `mod` 100
    centsText = if remainder < 10 then "0" ++ show remainder else show remainder
formatItem :: Item -> String
formatItem item = itemName item ++ ": " ++ formatPrice (itemPrice item)
items = [Item "Notebook" 1250, Item "Pen" 400, Item "Mug" 900, Item "Bag" 2400]
main :: IO ()
main = putStr (unlines (map formatItem items))

The total cost

map pulls out the prices and foldl adds them up. formatPrice turns the total of 4950 cents into dollars.

data Item = Item { itemName :: String, itemPrice :: Int } deriving (Show, Eq)
formatPrice :: Int -> String
formatPrice cents = "$" ++ show (cents `div` 100) ++ "." ++ centsText
  where
    remainder = cents `mod` 100
    centsText = if remainder < 10 then "0" ++ show remainder else show remainder
formatItem :: Item -> String
formatItem item = itemName item ++ ": " ++ formatPrice (itemPrice item)
items = [Item "Notebook" 1250, Item "Pen" 400, Item "Mug" 900, Item "Bag" 2400]
main :: IO ()
main = putStrLn (formatPrice (foldl (+) 0 (map itemPrice items)))

A discount on every item

The map takes 10 percent off each price in cents. Each sale price then prints in dollars.

data Item = Item { itemName :: String, itemPrice :: Int } deriving (Show, Eq)
formatPrice :: Int -> String
formatPrice cents = "$" ++ show (cents `div` 100) ++ "." ++ centsText
  where
    remainder = cents `mod` 100
    centsText = if remainder < 10 then "0" ++ show remainder else show remainder
formatItem :: Item -> String
formatItem item = itemName item ++ ": " ++ formatPrice (itemPrice item)
items = [Item "Notebook" 1250, Item "Pen" 400, Item "Mug" 900, Item "Bag" 2400]
main :: IO ()
main = do
  let salePrices = map (\item -> itemPrice item * 90 `div` 100) items
  putStr (unlines (map formatPrice salePrices))

Example

data Item = Item { itemName :: String, itemPrice :: Int } deriving (Show, Eq)
formatPrice :: Int -> String
formatPrice cents = "$" ++ show (cents `div` 100) ++ "." ++ centsText
  where
    remainder = cents `mod` 100
    centsText = if remainder < 10 then "0" ++ show remainder else show remainder
formatItem :: Item -> String
formatItem item = itemName item ++ ": " ++ formatPrice (itemPrice item)
items = [Item "Notebook" 1250, Item "Pen" 400, Item "Mug" 900, Item "Bag" 2400]
checkBudget :: Int -> Int -> Either String String
checkBudget budget total = if total <= budget then Right "Within budget" else Left "Over budget"
main :: IO ()
main = do
  let total = foldl (+) 0 (map itemPrice items)
  let saleTotal = foldl (+) 0 (map (\item -> itemPrice item * 90 `div` 100) items)
  let dearest = foldl (\best item -> if itemPrice item > itemPrice best then item else best) (head items) items
  putStrLn ("Total: " ++ formatPrice total)
  putStrLn ("Sale total: " ++ formatPrice saleTotal)
  putStrLn (formatItem dearest)
  case checkBudget 5000 saleTotal of
    Right message -> putStrLn message
    Left message -> putStrLn message

Key points

  • Item record: an Item value holds a name and a price in cents.
  • Cents as Int: whole cents keep prices exact, with no decimal rounding slips.
  • div and mod: div gives the whole dollars in a price and mod gives the leftover cents.
  • foldl total: folding the item prices with + gives the full cost.
  • map discount: mapping a price change over the items gives the sale prices.
  • Either result: a budget check gives Right with good news or Left with a warning.

In this example, foldl adds the item prices to a total of $49.50. A map takes 10 percent off each price, and foldl adds the sale prices to $44.55. A second foldl keeps the dearest item it has seen, which prints as the Bag line, and the case match prints the Right message from the budget check against 5000 cents.

Note

the record values are never printed as records here, since formatted lines read better.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Haskell Playground

Exercise

What does formatPrice give for a price of 1250 cents?