517 lines
17 KiB
Markdown
517 lines
17 KiB
Markdown
# View Contracts and View Trees
|
|
|
|
## 1. Purpose
|
|
|
|
View Contracts are the portable checking layer for Tree Calculus programs.
|
|
|
|
The checker does not consume detached metadata about a separate executable. Its
|
|
canonical input is a typed, checkable tree artifact: ordinary tree data that
|
|
contains both the executable program payloads and the view/contract structure
|
|
needed to validate and transform them.
|
|
|
|
The checker consumes this artifact and returns either:
|
|
|
|
```text
|
|
checked-execution artifact
|
|
```
|
|
|
|
or:
|
|
|
|
```text
|
|
structured diagnostic
|
|
```
|
|
|
|
A checked-execution artifact is interpreted by `runChecked`. Unguarded programs
|
|
are represented as `checkedPure rootPayload`; guarded programs contain explicit
|
|
checked guard/bind nodes.
|
|
|
|
This keeps checking independent of any particular host implementation. A typed
|
|
artifact may be produced by any frontend, compiler, hand-written generator, or
|
|
future self-hosted `tricu` toolchain.
|
|
|
|
## 2. Design Principle
|
|
|
|
The model follows the same discipline as interaction trees.
|
|
|
|
Interaction trees use tagged structural envelopes with explicit executable
|
|
payloads:
|
|
|
|
```tri
|
|
io action = pair "tricuIO" (pair version action)
|
|
pure x = pair 0 x
|
|
bind action k = pair 1 (pair action k)
|
|
```
|
|
|
|
The interpreter understands the outer structure, but it does not recursively
|
|
mistake every subtree for interpreter metadata. A continuation `k` is an opaque
|
|
executable tree until the interpreter reaches the `bind` step that applies it.
|
|
|
|
View trees use the same rule:
|
|
|
|
```text
|
|
structure says how to check;
|
|
opaque executable fields are only executed/applied by the checker at the
|
|
appropriate step.
|
|
```
|
|
|
|
This is the key distinction that allows Views to carry guards without confusing
|
|
ordinary program trees with View metadata.
|
|
|
|
## 3. Views
|
|
|
|
A View is an extrinsic contract over an ordinary Tree Calculus value. Tree
|
|
Calculus values do not carry native runtime types; a View describes how a value
|
|
may be treated by the checker or by a checked boundary.
|
|
|
|
Core View forms:
|
|
|
|
```text
|
|
Any
|
|
Ref ref
|
|
Fn [argView...] resultView
|
|
List elemView
|
|
Maybe elemView
|
|
Pair leftView rightView
|
|
Result errView okView
|
|
Guarded baseView guard
|
|
```
|
|
|
|
`Ref` supports both generated/numeric and symbolic references. Symbolic refs are
|
|
preferred for user-authored views:
|
|
|
|
```tri
|
|
UserId = viewRef "UserId"
|
|
```
|
|
|
|
A guarded view refines a base view with an executable guard:
|
|
|
|
```tri
|
|
UserId = viewGuarded (viewRef "UserId") userIdGuard
|
|
```
|
|
|
|
The guard is ordinary program code. The View validator checks that the guarded
|
|
view envelope is well-formed, and recursively validates the `baseView`, but it
|
|
must treat the guard payload/reference as opaque executable data, not as another
|
|
View.
|
|
|
|
## 4. Polymorphic and Abstract Views
|
|
|
|
View Contracts support portable polymorphism over Views. The View language is
|
|
interpreted by the same portable checker model implemented in `tricu` terms.
|
|
|
|
Source syntax may use underscore-prefixed names as View variables inside
|
|
annotations:
|
|
|
|
```tri
|
|
id x@_a =@_a x
|
|
const x@_a y@_b =@_a x
|
|
compose f@(Fn [_b] _c) g@(Fn [_a] _b) x@_a =@_c f (g x)
|
|
```
|
|
|
|
In the portable artifact, these lower to scoped View binders rather than
|
|
unscoped source-name conventions. This fits the existing View encoding style:
|
|
Views are tagged records with numeric tags and tagged fields. Polymorphic forms
|
|
are View records such as:
|
|
|
|
```text
|
|
Var localId
|
|
Forall binders body
|
|
Exists binders body
|
|
```
|
|
|
|
The current durable encoding uses stable local binder IDs. For example,
|
|
`id x@_a =@_a x` exports a shape equivalent to:
|
|
|
|
```text
|
|
Forall [0] (Fn [Var 0] (Var 0))
|
|
```
|
|
|
|
Source names like `_a` are for authoring; the artifact carries binder scope and
|
|
local IDs rather than relying on source-name identity.
|
|
|
|
`Forall` supports generic contracts:
|
|
|
|
```tri
|
|
map f@(Fn [_a] _b) xs@(List _a) =@(List _b) ...
|
|
head xs@(NonEmptyList _a) =@_a ...
|
|
```
|
|
|
|
At each checked use, the checker instantiates quantified variables into
|
|
use-local internal variables and solves View compatibility constraints. The
|
|
portable checker uses structural use-local IDs rather than expensive numeric
|
|
freshening, and treats unconstrained variable-variable matches as constraints
|
|
that do not create substitution cycles. Concrete observations still bind these
|
|
variables when enough information is available. This is what lets explicitly
|
|
annotated higher-order boundaries accept polymorphic values, for example
|
|
`compose id id "x"`, and lets quantified values satisfy concrete requirements
|
|
such as `Fn [String] String`. It gives useful polymorphic contracts for
|
|
explicitly declared/imported View facts.
|
|
|
|
`Exists` supports checked abstraction boundaries. A module can expose a value as
|
|
"some representation `_repr` plus capabilities over `_repr`":
|
|
|
|
```text
|
|
Exists _repr.
|
|
Pair
|
|
(Fn [String] _repr) -- constructor
|
|
(Fn [_repr] String) -- renderer / eliminator
|
|
```
|
|
|
|
This does not make raw Tree Calculus inspection impossible. Unchecked code can
|
|
always inspect trees. It means checked clients cannot justify
|
|
representation-specific operations through the View system unless the package
|
|
exports an appropriate capability or eliminator.
|
|
|
|
This leads to an important distinction for future checked subsets:
|
|
|
|
```text
|
|
controlled observation: Bool/List/Maybe/Result/etc. eliminators with Views
|
|
raw observation: direct tree-shape inspection through triage-like power
|
|
```
|
|
|
|
Useful application code can live mostly in the controlled fragment and receive
|
|
explicit View validation over lambdas, application, let, and typed eliminators.
|
|
Low-level library code may still use raw intensionality, but should expose
|
|
disciplined Views and capabilities above it. Scott-encoded constructors and
|
|
eliminators are a natural tricu-native representation for these APIs.
|
|
|
|
Tree Calculus terms do not carry intrinsic principal Views, and raw intensional
|
|
code can invalidate parametric claims. View Contracts are an explicit evidence
|
|
and contract layer over tricu programs; limited polymorphic Views are supported
|
|
when they are declared or imported as facts with provenance.
|
|
|
|
The first stdlib annotation island starts with parametric functions that do not
|
|
inspect representation:
|
|
|
|
```tri
|
|
id x@_a =@_a x
|
|
const x@_a y@_b =@_a x
|
|
compose f@(Fn [_b] _c) g@(Fn [_a] _b) x@_a =@_c f (g x)
|
|
```
|
|
|
|
Re-export-only modules preserve imported View metadata, so these contracts flow
|
|
through `prelude` rather than only through direct `base` imports.
|
|
|
|
Functions built on raw `t`/`triage` should enter the checked world through
|
|
trusted, controlled eliminator contracts rather than by treating arbitrary raw
|
|
inspection as parametric.
|
|
|
|
|
|
## 5. Guards
|
|
|
|
Guards are ordinary `tricu` values/functions grouped with the Views they refine.
|
|
|
|
Example:
|
|
|
|
```tri
|
|
userIdGuard = value :
|
|
-- ordinary program that validates value
|
|
|
|
UserId = viewGuarded (viewRef "UserId") userIdGuard
|
|
|
|
loadUser id@UserId = ...
|
|
```
|
|
|
|
Guards return the standard checked-runtime protocol:
|
|
|
|
```tri
|
|
guardOk value
|
|
guardFail
|
|
```
|
|
|
|
Guards do not author diagnostics. The checked-exec runner owns guard failure and
|
|
malformed-guard diagnostics using boundary context from the checked artifact.
|
|
|
|
Guards are injected by the checker. They are not discovered by the runtime as a
|
|
separate metadata layer. The checking process transforms a view tree into an
|
|
executable tree with the necessary guard applications inserted.
|
|
|
|
## 6. View Tree Artifact
|
|
|
|
The primary checker-facing artifact is a view executable term graph.
|
|
|
|
Conceptually:
|
|
|
|
```text
|
|
ViewTree
|
|
version
|
|
root node id
|
|
nodes
|
|
```
|
|
|
|
Each node is tagged tree data. Nodes combine executable payloads, view claims,
|
|
and structural relationships in one graph.
|
|
|
|
Representative node forms:
|
|
|
|
```text
|
|
Value node view executableTree
|
|
Apply node calleeNode argNode expectedOrInferredView
|
|
Require node requiredView sourceNode
|
|
External node name view
|
|
```
|
|
|
|
This is not a mandatory final encoding; it is the semantic target. The important
|
|
property is that executable trees and checking structure are carried together in
|
|
a single portable artifact.
|
|
|
|
A node may contain opaque executable fields. Those fields are tree terms, but
|
|
they are not recursively decoded as view-tree nodes or Views unless the node's
|
|
semantics explicitly says so.
|
|
|
|
View facts may also carry explicit per-fact trust provenance:
|
|
|
|
```text
|
|
Checked -- derived by checked lowering / checker validation
|
|
Trusted -- asserted by a trusted boundary, e.g. a primitive eliminator API
|
|
Unchecked -- raw or assumed; no parametricity/abstraction guarantee
|
|
```
|
|
|
|
In the portable view-tree envelope this provenance is represented as an optional
|
|
field on `typedValue` / `typedRequire` facts. In module manifests the same
|
|
provenance is carried beside the exported View Contract object reference so that
|
|
imports and re-exports preserve it without relying on module-level convention.
|
|
Absent provenance is interpreted conservatively as `Unchecked` at use sites.
|
|
|
|
For parametric checked definitions, the frontend now performs a conservative
|
|
raw-intensionality dependency pass over local definitions. If a definition with
|
|
scoped View variables depends directly or indirectly on raw `triage` / raw `t`
|
|
construction, or on an imported `Unchecked` fact, lowering fails and asks the
|
|
author to route observation through a trusted eliminator boundary. This is
|
|
intentionally provenance/dependency based; it is not an attempt to decide
|
|
whether arbitrary Tree Calculus reduction will ever reach rule 3.
|
|
|
|
View facts can be authored as ordinary value-level Tree Calculus metadata under
|
|
one conventional top-level name:
|
|
|
|
```text
|
|
viewFacts = [fact ...]
|
|
fact = pair exportName (pair provenance view)
|
|
```
|
|
|
|
where `exportName` is a string naming a value exported by the module,
|
|
`provenance` is `0 = Checked`, `1 = Trusted`, or `2 = Unchecked`, and `view` is
|
|
the same portable View record used by `view-tree` artifacts. The host evaluates
|
|
this value and decodes the data schema; it does not infer trust from source
|
|
syntax, AST shape, module name, or a Haskell-side catalog.
|
|
|
|
The initial trusted eliminator facts are authored this way in clearly separated
|
|
stdlib `viewFacts` sections:
|
|
|
|
```text
|
|
matchBool : forall r. r -> r -> Bool -> r
|
|
matchMaybe : forall a r. r -> (a -> r) -> Maybe a -> r
|
|
matchList : forall a r. r -> (a -> List a -> r) -> List a -> r
|
|
```
|
|
|
|
The `base` module provides small `facts*` authoring helpers for this advanced
|
|
metadata, e.g. `factsFact`, `factsChecked`, `factsTrusted`, `factsUnchecked`,
|
|
`factsForall`, `factsFn`, `factsVar`, `factsBool`, `factsString`, `factsByte`,
|
|
`factsUnit`, `factsMaybe`, and `factsList`. These helpers construct ordinary
|
|
Tree data; authority comes from the exported `viewFacts` value and its explicit
|
|
provenance tags. Loader validation rejects duplicate fact names and facts for
|
|
names the module does not export.
|
|
|
|
Initial derived stdlib annotations using this trusted kernel include:
|
|
|
|
```text
|
|
maybeMap : forall a b. (a -> b) -> Maybe a -> Maybe b
|
|
maybeBind : forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
|
|
maybeOr : forall a. a -> Maybe a -> a
|
|
```
|
|
|
|
Recursive list combinators are currently published as explicit `Trusted`
|
|
value-level facts rather than `Checked` source annotations, because their bodies
|
|
pass through raw fixed-point machinery that the conservative parametric taint
|
|
pass intentionally does not prove safe. This is the stabilized boundary: raw
|
|
stdlib kernels establish conventions with explicit authority; ordinary checked
|
|
clients consume those facts rather than re-proving the internals.
|
|
|
|
```text
|
|
headMaybe / lastMaybe / nthMaybe
|
|
append / map / filter / foldl / foldr
|
|
length / reverse / snoc / count / all? / any? / intersect
|
|
take / drop / splitAt / concatMap / find / partition / zipWith
|
|
string/list-byte helpers such as strLength, startsWith?, lines, words
|
|
```
|
|
|
|
## 7. Checker Semantics
|
|
|
|
The checker is an interpreter over the view tree.
|
|
|
|
For each node it may:
|
|
|
|
1. validate the node envelope;
|
|
2. validate Views referenced by the node;
|
|
3. check compatibility between expected and actual Views;
|
|
4. recursively check child nodes;
|
|
5. inject guards required by guarded Views;
|
|
6. produce the executable tree for that node;
|
|
7. memoize node results by node id.
|
|
|
|
The root node result is a checked-execution program.
|
|
|
|
In abstract form:
|
|
|
|
```text
|
|
checkViewTree : ViewTree -> Result CheckedExec Diagnostic
|
|
```
|
|
|
|
or, in self-hosted terms:
|
|
|
|
```tri
|
|
checkViewTree viewTree = ... -- ok checkedExec / err diagnostic
|
|
```
|
|
|
|
## 8. Compatibility and Guard Injection
|
|
|
|
Structural compatibility is about Views. Guard injection is about producing the
|
|
checked-execution tree.
|
|
|
|
For example, if a node is required to satisfy:
|
|
|
|
```tri
|
|
viewGuarded (viewRef "UserId") userIdGuard
|
|
```
|
|
|
|
then the checker verifies the underlying View relationship and emits executable
|
|
code that applies `userIdGuard` at the appropriate checked boundary.
|
|
|
|
The checker, not the runtime metadata system, owns this transformation.
|
|
|
|
## 9. Source Annotations
|
|
|
|
Source annotations are one frontend syntax for producing view-tree nodes.
|
|
|
|
Examples:
|
|
|
|
```tri
|
|
Nat = viewRef "Nat"
|
|
Box a = viewPair (viewRef "Box") a
|
|
|
|
idNat x@Nat =@Nat x
|
|
idBox x@(Box String) =@(Box String) x
|
|
```
|
|
|
|
Annotations are value-level View expressions. Names such as `Nat` and `Box` are
|
|
ordinary program values/functions that evaluate to Views.
|
|
|
|
A frontend that supports this syntax should lower the source into a view tree
|
|
that contains the relevant executable terms, views, and checking structure. The
|
|
artifact must not depend on source names or on the frontend implementation that
|
|
produced it.
|
|
|
|
## 10. Contract Expressions
|
|
|
|
Contract-expression helpers remain useful as authoring/building tools, but they
|
|
are not the fundamental artifact model.
|
|
|
|
Preferred style for expression-oriented authoring is pipeline-first:
|
|
|
|
```tri
|
|
mapBoolStringUse = cFn <|
|
|
[(viewFn [(viewBool)] viewString) (viewList viewBool)] (viewList viewString)
|
|
|> cApply (cFn [(viewBool)] viewString)
|
|
|> cApply (cValue (viewList viewBool))
|
|
|> cRequire (viewList viewString)
|
|
```
|
|
|
|
These helpers should be understood as convenient ways to build typed/checkable
|
|
structure, not as a permanent replacement for view-tree artifacts.
|
|
|
|
## 11. Artifact Direction
|
|
|
|
The target direction is to make the view tree the canonical checked-program
|
|
artifact.
|
|
|
|
Older split concepts remain useful internally or during development:
|
|
|
|
```text
|
|
tree term
|
|
view value
|
|
typed-program node
|
|
module/export manifest
|
|
```
|
|
|
|
But the durable design should avoid treating contracts as detached facts about a
|
|
separate program. The portable checker input is the checkable program itself.
|
|
|
|
In short:
|
|
|
|
```text
|
|
Do not store code over here and contracts over there.
|
|
Store a view tree: executable code plus the structure needed to check and guard it.
|
|
```
|
|
|
|
## 12. IO Interaction Trees
|
|
|
|
`tricu` IO is represented as ordinary interaction-tree data:
|
|
|
|
```tri
|
|
io action = pair "tricuIO" (pair version action)
|
|
pure value = pair 0 value
|
|
bind action k = pair 1 (pair action k)
|
|
```
|
|
|
|
View Contracts do not change that representation. A checked program may produce
|
|
an ordinary IO interaction tree, and the existing IO driver can execute it
|
|
unchanged.
|
|
|
|
For source evaluation with contracts enabled, `tricu eval --io` performs an
|
|
additional frontend instrumentation pass over visible IO continuations. When a
|
|
continuation returns a `pure (...)` value that mentions source-annotated
|
|
functions, the frontend lowers that pure expression into the existing portable
|
|
checked-exec protocol before returning the next IO action.
|
|
|
|
This means source sugar works for practical checked IO paths such as:
|
|
|
|
```tri
|
|
acceptNames xs@(NonEmptyList String) =@String "accepted"
|
|
|
|
main = io (bind (pure []) (xs : pure (acceptNames xs)))
|
|
```
|
|
|
|
and for explicit higher-order boundaries:
|
|
|
|
```tri
|
|
useHandler handler@(Fn [(NonEmptyList String)] String) xs@(List String) =@String
|
|
handler xs
|
|
|
|
main = io (bind (pure []) (xs : pure (useHandler acceptNames xs)))
|
|
```
|
|
|
|
The IO runtime does not perform View inference or guard injection at every step.
|
|
The source/frontend pass constructs checked-exec boundaries once; the runtime
|
|
only evaluates the resulting interaction tree.
|
|
|
|
Current limitations:
|
|
|
|
- This is source-visible instrumentation, not whole-program function-flow
|
|
tracking.
|
|
- Higher-order guarantees require explicit annotated boundaries.
|
|
- Raw prebuilt interaction trees, imported executable artifacts, and content-store
|
|
terms are not automatically re-instrumented unless they pass through this
|
|
source-lowering path.
|
|
- The IO action shape itself is only shallowly checkable unless users provide
|
|
guarded Views for the relevant boundaries.
|
|
- Continuation result Views are not inferred from external effects; dynamic IO
|
|
values should cross annotated/guarded boundaries when runtime enforcement is
|
|
required.
|
|
|
|
Making IO checking more complete is future work. In particular, a future design
|
|
may validate every continuation-produced action structurally, carry checked
|
|
wrappers with higher-order function values, or define a portable checked-IO
|
|
artifact instead of relying on Haskell/frontend source instrumentation.
|
|
|
|
## 13. Host Independence
|
|
|
|
No part of the core View Tree design is specific to Haskell or to the current implementation.
|
|
|
|
Any producer may emit a view-tree artifact if it follows the portable tree-data
|
|
encoding. Any checker implementation may consume it if it implements the typed
|
|
node semantics.
|
|
|
|
The current implementation can produce and consume these artifacts, but it is
|
|
not the semantic authority. The artifact format and the self-hosted checker
|
|
semantics are the authority.
|