139 Frame Construction and Closure

Source environments

Every type T determines a carrier set carrier(T) whose members are the values of that type. The construction of these carrier sets is described for structural types, value types, and entity types.

A source environment σ supplies the source-level bindings used when interpreting a frame. These include program variables, root variables, and contextual bindings such as this. If a program variable x has element type T, its current extension in σ is a subset of carrier(T) satisfying the variable's declared cardinality constraint:

σ(x) ⊆ carrier(T)

For a cardinality-one variable, this extension contains exactly one value, and the source term x denotes that value. Optional and multivalued variables instead have extensions containing the number of values permitted by their cardinality constraints. A contextual binding such as this supplies a single value in the carrier of its type.

A logic-variable assignment ρ is distinct from the source environment. It maps each generated logic variable declared in the free variables F of a frame to a value in the carrier of its declared type:

(V:T from a) ∈ F  ⇒  ρ(V) ∈ carrier(T)

Frame extension and materialisation

A frame is a logical denotation, not its materialised extension. From a frame and a source environment σ, one can derive an extension: the set of results obtained from assignments ρ for which Ψ holds, by evaluating the terms in R:


Extσ(⟨R for F where Ψ⟩) =
    { evalσ(R, ρ) | ρ assigns vars(F) and σ,ρ ⊨ Ψ }

Thus Extσ(⟨R for F where Ψ⟩) is a set of result tuples. Each tuple is keyed by attribute rather than by position. For every binding (a,E,T) in R, the tuple maps a to the single value obtained by evaluating E; that value belongs to the carrier of T.

The extension is derived from the frame; it is not the frame itself. It forgets the formula, free-variable provenance, and the result terms after they have been evaluated. Distinct frames may therefore have the same extension.

Materialising Extσ(γ) is an execution strategy, not part of the denotational definition. Negation, implication, constraints, folds, and normative systems may all be interpreted as formulas without enumerating the full extension.

Domain

For a frame γ, the domain operation removes the result while preserving the condition and free variables:


domain(γ) = ⟨∅ for free(γ) where formula(γ)⟩

This is useful when a scalar expression is evaluated inside a current frame. The current frame's condition still constrains the expression, even if the current frame's result is not retained.

The operation domain(γ) is distinct from the current frame itself. Operations such as this, splat, and attribute navigation need the result term from the current frame and therefore use the frame itself. Operations that only need the current frame's condition and free variables use domain(γ).

Closure

For a frame γ and a set Q of logic variables, close∃(γ,Q) existentially binds the selected variables, while close∀(γ,Q) universally binds them. Both operations have the preconditions:

Q ⊆ vars(free(γ))
Q ⊆ FV(formula(γ)) \ FV(result(γ))

Thus every variable in Q has a typed declaration in the frame, occurs free in the formula, and does not occur in the result. The operations recover the declarations and their types from free(γ):

close∃(γ, Q)
{
    let K = decls(free(γ), Q)
    return ⟨result(γ) for free(γ) \ K where ∃(K) formula(γ)⟩
}

close∀(γ, Q)
{
    let K = decls(free(γ), Q)
    return ⟨result(γ) for free(γ) \ K where ∀(K) formula(γ)⟩
}

Example of closure

Continuing the supplier example from 137 Frames, translating s.supplier.name produces the following frame before closure, with both the supplier variable S and the result variable N free:


γ_name_before_close =
  ⟨
    { name = N:String }
  for
    {
      S:Supplier from supplier,
      N:String from name
    }
  where
    s.supplier(S)
    ∧ S.name(N)
  ⟩

The ordinary composition rule closes the supplier variable that is not needed by the result:


close∃(γ_name_before_close, {S})
  =
  ⟨
    { name = N:String }
  for
    { N:String from name }
  where
    ∃S:Supplier (
      s.supplier(S)
      ∧ S.name(N)
    )
  ⟩

Trying to close N would be an error, because N appears in the result { name = N:String }.