nustack

Context protocol

How a Ref finds its Fabric inside a Context at evaluation time. The protocol has three dimensions: type, tags, predicates. Every binding and every lookup uses some subset.

Why a protocol, not a dict

A Context that was just name -> value would fix the binding to a name. Real programs need more:

  • one composition runs against several kinds of resources - the Storage binding for Order differs from the one for User;
  • one binding shards by site or path - which shard answers depends on a value computed at lookup time;
  • a child Context extends a parent without polluting siblings - bindings narrow scope, they do not leak back up.

Context resolves these structurally. A lookup tells you not just what but which one under these conditions.

Three dimensions

Type

Primary key. Every binding and every lookup starts with a type - the kind of resource being asked for (Storage, View, Snapshot handle, an API client). The type decides which Fabric family is touched.

Tags

Scope refinements. Additional keys attached to a binding, with set semantics. More tags = more specific.

bind(Storage, rocksdb)
bind(Storage, order_db, tag=Order)

get(Storage)            -> rocksdb
get(Storage, Order)     -> order_db

Subset fallback. A lookup with (Storage, Order, Region) first tries the exact tag set, then every smaller subset ((Storage, Order), (Storage, Region), (Storage,)). The most specific match wins; the unscoped binding is the default.

Tags are opaque values. Any hashable identity - a shape class, a region marker, a lifecycle marker - can serve.

Predicates

Named guards on bindings. A predicate is a function attached to a binding; at lookup, the caller supplies data and the predicate decides whether the binding applies.

bind(View, shard_a, tag=Market, sharding=|site, path| site[0] < 16)
bind(View, shard_b, tag=Market, sharding=|site, path| site[0] >= 16)

get(View, Market, site=(5,))    -> shard_a
get(View, Market, site=(20,))   -> shard_b

Predicates encode conditional resolution at the binding site. Multiple predicates on one entry are ANDed; multiple entries at the same (type, tags) scope are ORed.

Strictness. When guarded entries exist at a (type, tags) scope and the caller supplies data, predicates decide. If none match, the lookup falls back to smaller tag subsets. A guarded miss does not silently fall through to an unguarded entry at the same scope.

Resolution order

get(type, *tags, **data)

  1. exact (type, tags):
       if guarded entries exist and data was supplied:
         return first whose predicates all match
         if none match: skip this scope
       else:
         return direct entry if present
  2. subset fallback: try every smaller subset of tags
       (largest first, down to size 1); apply step 1 at each
  3. empty-scope fallback: try (type, ∅); apply step 1
  4. fail

Specificity wins. Predicates gate. Fallback is structural.

Eager and lazy

Two binding modes, same protocol:

  • eager - the value is held.
  • lazy - a factory registered at bind time; runs once on first lookup and caches.

Lazy bindings defer construction until evaluation reaches that branch. Useful when the resource is expensive or has side effects.

Narrowing via Spans

Because Context is immutable, a Span narrows it for its body without touching siblings: internally the Span extends the Context with its own binding, hands the extended Context to the body, and the outside world keeps its unnarrowed view. The bracket scope and the binding scope are the same scope.

What the protocol does not do

  • no global state. Every binding is on a Context value.
  • no name resolution. The registry is typed, not string-keyed.
  • no effect tracking. The Nu tree records what an Interaction does; Context is where the effect lands, not a log of what was done.

What it buys

  • same tree, different Context, different behavior. Swap backends, shards, snapshots without rewriting the composition.
  • conditional resolution at the binding site. Routing logic lives in bind, not in if branches across the program.
  • specificity without inheritance. Tag subsets give per-shape, per-region overrides without a class hierarchy of Contexts.
  • scoped narrowing for free. A Span binds into a new Context; the bracket scope is the binding scope.

On this page