9.8 KiB
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:
checked-execution artifact
or:
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:
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:
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:
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:
UserId = viewRef "UserId"
A guarded view refines a base view with an executable guard:
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. Guards
Guards are ordinary tricu values/functions grouped with the Views they refine.
Example:
userIdGuard = value :
-- ordinary program that validates value
UserId = viewGuarded (viewRef "UserId") userIdGuard
loadUser id@UserId = ...
Guards return the standard checked-runtime protocol:
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.
5. View Tree Artifact
The primary checker-facing artifact is a view executable term graph.
Conceptually:
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:
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.
6. Checker Semantics
The checker is an interpreter over the view tree.
For each node it may:
- validate the node envelope;
- validate Views referenced by the node;
- check compatibility between expected and actual Views;
- recursively check child nodes;
- inject guards required by guarded Views;
- produce the executable tree for that node;
- memoize node results by node id.
The root node result is a checked-execution program.
In abstract form:
checkViewTree : ViewTree -> Result CheckedExec Diagnostic
or, in self-hosted terms:
checkViewTree viewTree = ... -- ok checkedExec / err diagnostic
7. 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:
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.
8. Source Annotations
Source annotations are one frontend syntax for producing view-tree nodes.
Examples:
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.
9. 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:
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.
10. 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:
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:
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.
11. IO Interaction Trees
tricu IO is represented as ordinary interaction-tree data:
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:
acceptNames xs@(NonEmptyList String) =@String "accepted"
main = io (bind (pure []) (xs : pure (acceptNames xs)))
and for explicit higher-order boundaries:
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.
12. 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.