Bank Account
Model a simple bank account using OCaml's type system. Practice variant types, immutable record updates, and list folds to track a full transaction history.
Support deposits, withdrawals, and balance queries. The account should keep a complete history of all transactions, and balances should be computed from history rather than stored directly.
- 01Define a
transactionvariant type withDeposit of floatandWithdrawal of floatconstructors. - 02Define an
accountrecord with fieldsowner : stringandhistory : transaction list. - 03Write
depositandwithdraw. Each must return a new account value (immutable update via{ acct with … }). - 04Write
balanceto compute the current balance by folding overhistory. - 05Write
statementto return a(string * float) listwith one entry per transaction, oldest first.
Starter code
type transaction = | Deposit of float | Withdrawal of float type account = { owner : string; history : transaction list; } let create owner = { owner; history = [] } let deposit amount acct = (* TODO *) let withdraw amount acct = (* TODO *) let balance acct = (* TODO: fold over acct.history *) 0.0 let statement acct = (* TODO *) []
Hints
For deposit and withdraw, use the record update syntax { acct with history = … }. Prepend new transactions to the front of the list. This is O(1).
In balance, List.fold_left is the right tool. Start from 0.0 and adjust the accumulator on each transaction. Pattern match on the variant to know whether to add or subtract.
In statement, call List.rev acct.history first to get chronological order, then List.map each transaction to a label–amount tuple.
The current balance is never stored. It is always derived from history. This makes past balances recomputable, which matters for auditing.
If your match is missing a case, OCaml will warn you. Use that as a safety net, since warnings here are likely bugs.
type transaction = | Deposit of float | Withdrawal of float type account = { owner : string; history : transaction list; } let create owner = { owner; history = [] } let deposit amount acct = { acct with history = Deposit amount :: acct.history } let withdraw amount acct = { acct with history = Withdrawal amount :: acct.history } let balance acct = List.fold_left (fun acc tx -> match tx with | Deposit a -> acc +. a | Withdrawal a -> acc -. a) 0.0 acct.history let statement acct = acct.history |> List.rev |> List.map (fun tx -> match tx with | Deposit a -> ("Deposit", a) | Withdrawal a -> ("Withdrawal", a)) (* Usage *) let () = let acct = create "Alice" |> deposit 500.0 |> deposit 250.0 |> withdraw 100.0 in Printf.printf "Balance: %.2f\n" (balance acct) (* Balance: 650.00 *)
Walkthrough
Immutability. Each call to deposit or withdraw returns a new account record with a transaction prepended to the history. The original is untouched. We use the pipe operator to chain updates cleanly.
fold_left. We start at 0.0 and walk the history list. For each entry, we add the amount on Deposit and subtract it on Withdrawal. Pattern matching on the variant is exhaustive and the compiler verifies this.
statement. Since we prepend transactions (newest first), List.rev puts them in chronological order. Then List.map converts each to a printable tuple.
Notice deposit and withdraw are structurally identical: both prepend a constructor to history. An advanced refactor could unify them into a single apply : transaction -> account -> account.
Going further
Once the basic model works:
- Guard
withdrawagainst overdrafts by returningOk accountorError "Insufficient funds"using theresulttype. - Write
balance_at : int -> account -> floatthat returns the balance after the first n transactions. - Add a
Transfer of float * accountvariant that atomically moves funds between two accounts.
(* Safe withdrawal *) let withdraw_safe amount acct = if balance acct < amount then Error "Insufficient funds" else Ok { acct with history = Withdrawal amount :: acct.history } (* Balance after n transactions *) let balance_at n acct = acct.history |> List.rev |> List.filteri (fun i _ -> i < n) |> List.fold_left (fun acc tx -> match tx with | Deposit a -> acc +. a | Withdrawal a -> acc -. a) 0.0