cells

Handler state: a var declared in the arm table, and the block below the handler reading what the arms left there.

运行

CI 运行的命令,以及它逐字节核对的输出。

sh

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
}