241 lines
7.1 KiB
Plaintext
241 lines
7.1 KiB
Plaintext
!import "base" !Local
|
|
!import "list" !Local
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Core contract type
|
|
--
|
|
-- A contract is an ordinary tricu function: Tree -> Tree -> Result Tree Tree.
|
|
-- The second argument is the conventional "rest" slot. On success a contract
|
|
-- returns the checked value wrapped in the standard ok shape; on failure it
|
|
-- returns a diagnostic wrapped in the standard err shape.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
contractOk = (value : (rest : ok value rest))
|
|
contractErr = (msg : (rest : err msg rest))
|
|
|
|
-- Apply a contract with the conventional rest slot and return the raw Result.
|
|
checkContract = (contract value : contract value t)
|
|
|
|
-- Apply a contract and continue with either the onOk or onFail branch.
|
|
withContract = (contract value onOk onFail :
|
|
matchResult
|
|
(msg _ : onFail msg)
|
|
(checked _ : onOk checked)
|
|
(contract value t))
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Basic contracts
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- Any value passes.
|
|
anyC = (value : contractOk value)
|
|
|
|
-- Always fails with the supplied message.
|
|
neverC = (msg : (value : contractErr msg))
|
|
|
|
-- Build a contract from a predicate that inspects only the value.
|
|
guardC = (msg predicate value rest :
|
|
lazyBool
|
|
(_ : contractOk value rest)
|
|
(_ : contractErr msg rest)
|
|
(predicate value))
|
|
|
|
-- Natural number contract.
|
|
nat? = guardC "not a natural number" (n : gte? n 0)
|
|
|
|
-- Non-zero number contract.
|
|
nonZero? = guardC "non-zero" (n : not? (isZero? n))
|
|
|
|
-- Boolean contract.
|
|
bool? = guardC "not a boolean" (b : or? (equal? b true) (equal? b false))
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Contract combinators
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
andC = (c1 c2 value rest :
|
|
matchResult
|
|
(msg _ : contractErr msg rest)
|
|
(v _ : c2 v rest)
|
|
(c1 value rest))
|
|
|
|
orC = (c1 c2 value rest :
|
|
matchResult
|
|
(msg _ : c2 value rest)
|
|
(v _ : contractOk v rest)
|
|
(c1 value rest))
|
|
|
|
notC = (c value rest :
|
|
matchResult
|
|
(msg _ : contractOk value rest)
|
|
(_ _ : contractErr "notC: predicate succeeded" rest)
|
|
(c value rest))
|
|
|
|
mapC = (f c value rest :
|
|
matchResult
|
|
(msg _ : contractErr msg rest)
|
|
(v _ : contractOk (f v) rest)
|
|
(c value rest))
|
|
|
|
bindC = (c f value rest :
|
|
matchResult
|
|
(msg _ : contractErr msg rest)
|
|
(v _ : f v value rest)
|
|
(c value rest))
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Collection contracts
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
listOf = (c value rest :
|
|
y (self orig xs :
|
|
matchList
|
|
(contractOk orig rest)
|
|
(h r :
|
|
matchResult
|
|
(msg _ : contractErr msg rest)
|
|
(_ _ : self orig r)
|
|
(c h rest))
|
|
xs) value value)
|
|
|
|
nonEmptyListOf = (c :
|
|
andC (guardC "empty list" (xs : not? (emptyList? xs))) (listOf c))
|
|
|
|
pairOf = (c1 c2 p rest :
|
|
matchPair
|
|
(a b :
|
|
matchResult
|
|
(msg _ : contractErr msg rest)
|
|
(a' _ :
|
|
matchResult
|
|
(msg _ : contractErr msg rest)
|
|
(b' _ : contractOk (pair a' b') rest)
|
|
(c2 b rest))
|
|
(c1 a rest))
|
|
p)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Higher-order function contracts
|
|
--
|
|
-- These return a Result-wrapped proxy. The proxy itself is a contract: it
|
|
-- checks arguments on the way in and results on the way out.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
fnContract = (argC resC f rest :
|
|
contractOk
|
|
(x : (rest1 :
|
|
withContract argC x
|
|
(x' :
|
|
withContract resC (f x')
|
|
(y : contractOk y rest1)
|
|
(msg : contractErr msg rest1))
|
|
(msg : contractErr msg rest1)))
|
|
rest)
|
|
|
|
fn2 = (arg1C arg2C resC f rest :
|
|
contractOk
|
|
(x : (rest1 :
|
|
withContract arg1C x
|
|
(x' :
|
|
contractOk
|
|
(y : (rest2 :
|
|
withContract arg2C y
|
|
(y' :
|
|
withContract resC (f x' y')
|
|
(z : contractOk z rest2)
|
|
(msg : contractErr msg rest2))
|
|
(msg : contractErr msg rest2)))
|
|
rest1)
|
|
(msg : contractErr msg rest1)))
|
|
rest)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Interaction-tree effect layer
|
|
--
|
|
-- These constructors and combinators layer catchable, composable failures on
|
|
-- top of the core Result contracts. They reuse the same tags as tricu IO:
|
|
-- 0 = pureE
|
|
-- 1 = bindE
|
|
-- 2 = exceptE
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
pureE = (value : pair 0 value)
|
|
bindE = (action k : pair 1 (pair action k))
|
|
exceptE = (tag value k : pair 2 (pair tag (pair value k)))
|
|
|
|
pureM = pureE
|
|
bindM = bindE
|
|
|
|
-- Lift a contract failure into an interaction tree.
|
|
checkM = (contract value :
|
|
matchResult
|
|
(msg _ : exceptE "contract" msg (_ : pureE t))
|
|
(checked _ : pureE checked)
|
|
(contract value t))
|
|
|
|
-- Lift a pure function into the interaction tree.
|
|
liftM = (f : (x : pureE (f x)))
|
|
|
|
-- Interpret a pure interaction tree into a Result.
|
|
runM = (tree :
|
|
run tree
|
|
where run =
|
|
y (self tree :
|
|
matchPair
|
|
(op payload :
|
|
matchBool
|
|
-- pureE
|
|
(contractOk (snd tree) t)
|
|
(matchBool
|
|
-- bindE
|
|
(matchPair
|
|
(action k :
|
|
matchResult
|
|
(msg _ : contractErr msg t)
|
|
(v _ : self (k v))
|
|
(self action))
|
|
payload)
|
|
-- exceptE
|
|
(matchPair
|
|
(tag pair :
|
|
matchPair
|
|
(value k :
|
|
contractErr value t)
|
|
pair)
|
|
payload)
|
|
(equal? op 1))
|
|
(equal? op 0))
|
|
tree))
|
|
|
|
-- Handle matching exceptE nodes by applying the handler to the value and the
|
|
-- resumption continuation. Non-matching exceptions are left in place.
|
|
handleM = (tag handler tree :
|
|
handle tree
|
|
where handle =
|
|
y (self tree :
|
|
matchPair
|
|
(op payload :
|
|
matchBool
|
|
-- pureE
|
|
tree
|
|
(matchBool
|
|
-- bindE
|
|
(matchPair
|
|
(action k :
|
|
bindE (self action) (v : self (k v)))
|
|
payload)
|
|
-- exceptE
|
|
(matchPair
|
|
(et pair :
|
|
matchPair
|
|
(value k :
|
|
matchBool
|
|
(self (handler value k))
|
|
tree
|
|
(equal? et tag))
|
|
pair)
|
|
payload)
|
|
(equal? op 1))
|
|
(equal? op 0))
|
|
tree))
|