Running it
What CI runs and the output it checks, byte for byte.
dawn run examples/effects/cells.dawn
budget 20: ["bread", "cheese", "apples"] left=1 ["-3 bread", "-12 cheese", "-4 apples"]
budget 10: ["bread", "apples"] left=3 ["-3 bread", "skip cheese (12)", "-4 apples"]
examples/effects/cells.dawn
# Handler state: a `var` declared in the arm table, and the block below the
# handler reading what the arms left there.
#
# `shop` below spends from a budget it cannot see. It has no running total, no
# accumulator parameter and no return value carrying one; its signature says
# `!Spend` and that is all it knows. The budget is a cell belonging to one
# installation of the handler, the arms read and write it, and the block after
# `with handle` reads it afterwards. That block is the only way a stateful
# handler hands anything back: there is no `return` arm (spec §6.5).
#
# Three things a cell is not. It is not visible to the code that raises the
# operation. It is not shared with another installation, not even a nested one
# of the same handler. And it cannot leave: a closure that is not one of this
# installation's arms may not capture it, so no second name reaches it, and it
# has no type a program can write down.
#
# Cells must be declared before the arms, and their type annotation is not
# optional -- inside the arm table there is no context to infer it from.
#
# Run: dawn run examples/effects/cells.dawn
use std/io
effect Spend {
## Take `n` from the budget for `item`. `false` when there was not enough
## left, in which case nothing was taken.
fn spend(item: String, n: Int) -> Bool
}
type Order = { item: String, cost: Int }
## Buys what it can afford, in order, and answers what it got. Nothing in here
## could tell you the budget, or what is left of it.
fn shop(orders: List[Order]) -> List[String] !Spend = {
var bought: List[String] = []
for o in orders {
if spend(o.item, o.cost) {
bought = bought ++ [o.item]
}
}
bought
}
type Trip = { bought: List[String], left: Int, ledger: List[String] }
## One installation, with two cells: the money and the paper trail. `shop`
## runs under it, and the three lines after `with handle` are where the cells
## are read.
fn trip(budget: Int, orders: List[Order]) -> Trip = {
with handle Spend {
var left: Int = budget
var ledger: List[String] = []
spend(item, n) =>
if n <= left {
left = left - n
ledger = ledger ++ ["-${n} ${item}"]
true
} else {
ledger = ledger ++ ["skip ${item} (${n})"]
false
}
}
let bought = shop(orders)
Trip { bought: bought, left: left, ledger: ledger }
}
fn basket() -> List[Order] = [
Order { item: "bread", cost: 3 },
Order { item: "cheese", cost: 12 },
Order { item: "apples", cost: 4 },
]
fn show(t: Trip) -> String = "${t.bought} left=${t.left} ${t.ledger}"
pub fn main() -> Unit !io = {
io.println("budget 20: ${trip(20, basket()).show()}")
io.println("budget 10: ${trip(10, basket()).show()}")
}
test "the arms keep the tally and the block below reads it" {
let t = trip(20, basket())
assert t.bought == ["bread", "cheese", "apples"]
assert t.left == 1
assert t.ledger == ["-3 bread", "-12 cheese", "-4 apples"]
}
test "a refusal is an answer, not a failure: shopping goes on" {
let t = trip(10, basket())
assert t.bought == ["bread", "apples"]
assert t.left == 3
assert t.ledger == ["-3 bread", "skip cheese (12)", "-4 apples"]
}
test "each installation starts from its own budget" {
assert trip(20, basket()).left == 1
assert trip(20, basket()).left == 1
}
test "a nested installation spends its own money" {
with handle Spend {
var left: Int = 10
spend(_item, n) =>
if n <= left {
left = left - n
true
} else {
false
}
}
let outer = shop([Order { item: "bread", cost: 3 }])
let inner = trip(100, [Order { item: "cheese", cost: 50 }])
assert outer == ["bread"]
assert inner.bought == ["cheese"]
assert inner.left == 50
# The nested handler answered its own operations; this cell never saw them.
assert left == 7
}