Begin removing view related code and docs
This commit is contained in:
326
docs/contracts.md
Normal file
326
docs/contracts.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# Contracts
|
||||
|
||||
Contracts are the portable runtime boundary-checking layer for `tricu`. A
|
||||
contract is an ordinary `tricu` function that inspects a value and returns a
|
||||
standard `Result`.
|
||||
|
||||
Contracts are not a type system. Tree Calculus is intensional: every value is a
|
||||
tree and can be inspected by any function. A contract can only observe a value
|
||||
and fail when it does not satisfy the advertised predicate. It cannot hide a
|
||||
value's representation or prove that an opaque function behaves correctly for
|
||||
all inputs.
|
||||
|
||||
Static typing for Tree Calculus is an area of active research. This document
|
||||
describes the dynamic-contract layer that exists today and the guarantees it
|
||||
can honestly claim.
|
||||
|
||||
## 1. The contract type
|
||||
|
||||
A contract is a function:
|
||||
|
||||
```tri
|
||||
contract : Tree -> Tree -> Result Tree Tree
|
||||
```
|
||||
|
||||
The second argument is the conventional `rest` slot. It takes a value and a rest
|
||||
and returns one of the standard `Result` shapes from `lib/base.tri`:
|
||||
|
||||
```tri
|
||||
ok value rest = pair true (pair value rest)
|
||||
err msg rest = pair false (pair msg rest)
|
||||
```
|
||||
|
||||
In contract contexts the `rest` slot is conventionally `t`. Two helpers make
|
||||
this explicit:
|
||||
|
||||
```tri
|
||||
contractOk = (value : (rest : ok value rest))
|
||||
contractErr = (msg : (rest : err msg rest))
|
||||
```
|
||||
|
||||
- On success, a contract returns the checked value. This may be the original
|
||||
value or a transformed/normalized value.
|
||||
- On failure, it returns a reason. The reason is an arbitrary tree, often a
|
||||
string or a structured diagnostic.
|
||||
|
||||
Because a contract is just a tree-valued function, any Tree Calculus
|
||||
implementation can apply it. No special contract object format is required.
|
||||
|
||||
## 2. Core boundary wrappers
|
||||
|
||||
### 2.1 Explicit check
|
||||
|
||||
`checkContract` applies a contract with the conventional `t` rest slot and
|
||||
returns the raw `Result`:
|
||||
|
||||
```tri
|
||||
checkContract = (contract value : contract value t)
|
||||
```
|
||||
|
||||
This is the most flexible form. The caller decides what to do with failure.
|
||||
|
||||
### 2.2 Direct boundary abort
|
||||
|
||||
`withContract` applies a contract with the conventional `t` rest slot and
|
||||
continues on success, or calls a failure continuation on failure:
|
||||
|
||||
```tri
|
||||
withContract = (contract value onOk onFail :
|
||||
matchResult
|
||||
(msg _ : onFail msg)
|
||||
(checked _ : onOk checked)
|
||||
(contract value t))
|
||||
```
|
||||
|
||||
The failure continuation is supplied by the host or by the surrounding program.
|
||||
It may abort, log, return a default, or raise an effect. The core contract
|
||||
standard does not prescribe the failure behavior.
|
||||
|
||||
### 2.3 Example: a simple contract
|
||||
|
||||
```tri
|
||||
isZero? = n :
|
||||
equal? n 0
|
||||
|
||||
nat? = guardC "not a natural number" (n : gte? n 0)
|
||||
|
||||
-- explicit check
|
||||
result = checkContract nat? 5
|
||||
|
||||
-- boundary abort
|
||||
five = withContract nat? 5 (x : x) (msg : 0)
|
||||
```
|
||||
|
||||
Real contract predicates are usually more interesting than `isZero?`; this
|
||||
illustrates only the shape.
|
||||
|
||||
## 3. Contract combinators
|
||||
|
||||
Contracts compose using ordinary `tricu` functions. A few common patterns:
|
||||
|
||||
```tri
|
||||
andC = (c1 c2 value rest :
|
||||
matchResult
|
||||
(msg _ : contractErr msg rest)
|
||||
(v _ : c2 v rest)
|
||||
(c1 value rest))
|
||||
|
||||
mapC = (f c value rest :
|
||||
matchResult
|
||||
(msg _ : contractErr msg rest)
|
||||
(v _ : contractOk (f v) rest)
|
||||
(c value rest))
|
||||
|
||||
listOf = (c xs rest : ...) -- checks spine and element contract
|
||||
pairOf = (c1 c2 p rest : ...)
|
||||
```
|
||||
|
||||
These are library code, not core standard. A contract library can provide
|
||||
`listOf`, `pairOf`, `fnContract`, and similar helpers.
|
||||
|
||||
## 4. Higher-order contracts
|
||||
|
||||
A contract for a function value returns a wrapped proxy. The proxy itself is a
|
||||
contract: it checks arguments on the way in and results on the way out.
|
||||
|
||||
```tri
|
||||
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)
|
||||
```
|
||||
|
||||
This does not prove that `f` is well-behaved internally; it only catches
|
||||
violations at observed calls.
|
||||
|
||||
## 5. Interaction-tree contract effects
|
||||
|
||||
The core contract layer returns `Result`. For code that wants catchable,
|
||||
composable contract failures without threading `Result` through every function,
|
||||
contracts can be lifted into an interaction tree.
|
||||
|
||||
### 5.1 Interaction-tree constructors
|
||||
|
||||
These reuse the same `pure`/`bind` tags already used for `tricu` IO:
|
||||
|
||||
```tri
|
||||
pureE value = pair 0 value
|
||||
bindE action k = pair 1 (pair action k)
|
||||
exceptE tag value k = pair 2 (pair tag (pair value k))
|
||||
```
|
||||
|
||||
`exceptE` is resumable: `k` is the continuation. A handler may resume with
|
||||
`k replacement` or abort by ignoring `k`. Contract failures usually abort; the
|
||||
resumable shape is provided for generality and for richer effect handlers.
|
||||
|
||||
### 5.2 Lifting a contract
|
||||
|
||||
```tri
|
||||
checkM contract value =
|
||||
matchResult
|
||||
(msg _ : exceptE "contract" msg (\_ : pureE t))
|
||||
(checked _ : pureE checked)
|
||||
(contract value t)
|
||||
```
|
||||
|
||||
`pureM` and `bindM` are aliases for `pureE` and `bindE`:
|
||||
|
||||
```tri
|
||||
pureM = pureE
|
||||
bindM = bindE
|
||||
```
|
||||
|
||||
### 5.3 Lifting pure functions
|
||||
|
||||
```tri
|
||||
liftM f = (x : pureE (f x))
|
||||
```
|
||||
|
||||
### 5.4 Example
|
||||
|
||||
```tri
|
||||
halfM n =
|
||||
bindM (checkM even? n)
|
||||
(\n' : pureM (div n' 2))
|
||||
|
||||
use =
|
||||
handleM "contract"
|
||||
(\msg k : pureM 0)
|
||||
(halfM 5)
|
||||
```
|
||||
|
||||
`handleM` is a pure tree-to-tree function that interprets `exceptE` nodes,
|
||||
either resuming with a replacement value or returning a failure tree.
|
||||
|
||||
### 5.5 Running a pure interaction tree
|
||||
|
||||
```tri
|
||||
runM tree =
|
||||
-- interprets pureE, bindE, and exceptE nodes
|
||||
-- returns a Result or a residual effect tree
|
||||
...
|
||||
```
|
||||
|
||||
If the tree contains no IO or other host effects, `runM` can be written
|
||||
entirely in `tricu`.
|
||||
|
||||
## 6. Source syntax
|
||||
|
||||
Source annotations are frontend sugar for inserting contract boundaries. They do
|
||||
not change the runtime semantics of ordinary code; they tell the frontend where
|
||||
to emit contract checks.
|
||||
|
||||
### 6.1 Argument and result assertions
|
||||
|
||||
```tri
|
||||
idNat x@Nat =@Nat x
|
||||
```
|
||||
|
||||
`x@Nat` inserts a `Nat` contract check on the argument. `=@Nat` inserts a check
|
||||
on the result.
|
||||
|
||||
### 6.2 Compound contracts
|
||||
|
||||
```tri
|
||||
sum xs@(List Nat) =@Nat ...
|
||||
useHandler f@(Fn [(NonEmptyList String)] String) =@String ...
|
||||
```
|
||||
|
||||
Compound annotations must be parenthesized when they contain application.
|
||||
|
||||
### 6.3 Phantom arguments
|
||||
|
||||
```tri
|
||||
map @A @B =@(Fn [(Fn [A] B) (List A)] (List B)) ...
|
||||
```
|
||||
|
||||
A phantom argument contributes a contract to the function boundary without
|
||||
introducing a term binder.
|
||||
|
||||
### 6.4 Missing annotations
|
||||
|
||||
Unannotated binders in a contract-bearing head default to `Any`. A missing
|
||||
return annotation defaults to `Any`.
|
||||
|
||||
```tri
|
||||
foo x y@Bool = body -- foo : Fn [Any Bool] Any, y : Bool
|
||||
```
|
||||
|
||||
### 6.5 Export contracts
|
||||
|
||||
A module export may advertise a contract:
|
||||
|
||||
```tri
|
||||
!export factorial : Fn [Nat] Nat
|
||||
```
|
||||
|
||||
The advertised contract travels with the export in the module manifest.
|
||||
|
||||
## 7. Module and content-store integration
|
||||
|
||||
Contracts attach to module exports as ordinary content-addressed tree terms.
|
||||
There is no special contract object kind. The manifest references the contract
|
||||
with the same object kind as any other tree term:
|
||||
|
||||
```text
|
||||
name: "factorial"
|
||||
object:
|
||||
kind: arboricx.tree-term.v1
|
||||
hash: <tree-term hash>
|
||||
contract:
|
||||
kind: arboricx.tree-term.v1
|
||||
hash: <contract term hash>
|
||||
```
|
||||
|
||||
The earlier `arboricx.view-contract.type.v1` object kind is removed. A
|
||||
contract is just a tree term.
|
||||
|
||||
For locally built modules, advertised export contracts may be checked before the
|
||||
manifest is published. For imported modules, the advertised contract is a
|
||||
boundary assumption. The local checker may insert guard wrappers when a
|
||||
contracted import is used.
|
||||
|
||||
See `docs/module-system-design.md` and
|
||||
`docs/content-store-and-module-format.md` for the full store, manifest, and
|
||||
bundle conventions.
|
||||
|
||||
## 8. Guarantees
|
||||
|
||||
The contract layer honestly claims only:
|
||||
|
||||
1. A contract applied to a value returns a standard `Result` shape.
|
||||
2. `withContract` and `checkM` invoke the contract at the represented boundary.
|
||||
3. A failed contract invokes the supplied failure continuation or `exceptE`
|
||||
node.
|
||||
4. Content-addressed references prevent an attached contract from silently
|
||||
drifting to a different stored object.
|
||||
5. Provenance labels record where a contract assertion came from.
|
||||
|
||||
Only the contract function itself observes the runtime value. The rest is
|
||||
metadata plumbing.
|
||||
|
||||
## 9. Limitations
|
||||
|
||||
- Contracts do not establish parametricity or representation independence.
|
||||
- They do not prove that opaque recursive or primitive code satisfies its
|
||||
contract for every input.
|
||||
- They do not remove the need for tests, careful API design, or future static
|
||||
analysis.
|
||||
- Higher-order contract wrapping has the usual costs and proxy-like behavior
|
||||
of dynamic contract systems.
|
||||
|
||||
## 10. Summary
|
||||
|
||||
- A contract is an ordinary `tricu` function: `Tree -> Result Tree Tree`.
|
||||
- `withContract` aborts at a boundary; `checkContract` returns the raw
|
||||
`Result`.
|
||||
- The interaction-tree layer (`checkM`, `bindM`, `handleM`) adds catchable,
|
||||
composable failures on top of the same core contracts.
|
||||
- Contracts attach to module exports as ordinary tree-term objects.
|
||||
- Provenance labels record source and blame, but do not prove truth.
|
||||
Reference in New Issue
Block a user