152 Denotation of Conditionals, Negation, Folds, and Cardinality
9. Conditional expression
The expression if A then B else C is not translated as ordinary function application. The
condition and both branches are contextual expressions; a branch may carry path constraints, receiver
variables, and an optional no-result denotation. The conditional introduces one shared output term,
then guards the whole denotation of each producing branch.
branchIf(γ_cond, B, whenTrue, γ_branch, V, O, γ_receiver, P)
{
let G = formula(γ_cond) ∧ (if whenTrue then B else ¬B)
let Ψ = G ∧ formula(γ_branch) ∧ O = V
let F = free(γ_cond) ∪ free(γ_branch)
let Q = vars(F)
if γ_receiver ≠ noFrame
Q = Q \ vars(free(γ_receiver))
Q = Q \ P
Q = Q ∩ FV(Ψ)
let K = decls(F, Q)
return ∃(K) Ψ
}
ℒ(Γ, P, if A then E₁ else E₂)
{
let γ₀ = ℒ(Γ, P, A)
let (a₀,B,T₀) = unary(γ₀)
require T₀ = Boolean
let γ₁ = ℒ(Γ, P, E₁)
let γ₂ = ℒ(Γ, P, E₂)
let r₁ = if result(γ₁) = ∅ then noResult else unary(γ₁)
let r₂ = if result(γ₂) = ∅ then noResult else unary(γ₂)
if r₁ = noResult and r₂ = noResult
return ⟨∅ for ∅ where false⟩
require the producing branch result types have common type T
let a be the producing branch attribute
if all producing branches use the same one,
otherwise _anon_
let O:T be fresh
let γ_receiver = if Γ = [] then noFrame else currentFrame(Γ)
let branches = ∅
if r₁ = (a₁,V₁,T₁)
add branchIf(
γ₀, B, true, γ₁, V₁, O, γ_receiver, P
) to branches
if r₂ = (a₂,V₂,T₂)
add branchIf(
γ₀, B, false, γ₂, V₂, O, γ_receiver, P
) to branches
let Ψ = disjunction(branches)
let F =
{ O:T from a }
∪ {
d ∈ free(γ₀) ∪ free(γ₁) ∪ free(γ₂)
| var(d) ∈ FV(Ψ)
}
return ⟨
{ a = O:T }
for
F
where
Ψ
⟩
}
A branch with no result contributes no possible output values; it does not contribute a marker value.
For example, in if email ≠ none then email else none, the then branch contributes the email values
under the presence guard and the else branch contributes no values.
10. Complement and pointwise negation
ℒ(Γ, P, E!)
{
let γ₀ = currentFrame(Γ)
let γ = ℒ(Γ, P, E)
return pointNot(γ₀, γ)
}
ℒ(Γ, P, E~)
{
if Γ = []
raise error
let γ₀ = currentFrame(Γ)
let γ = ℒ(Γ, P ∪ vars(free(γ₀)), E)
return pathComplement(γ₀, γ, P)
}
11. Fold and cardinality
Folds turn a unary path frame into a set comprehension. Variables in the protected set remain free
in the comprehension term; all other frame variables are bound by the comprehension. The
comprehension helper is sensitive to currentFrame(Γ): if the folded value does not itself bind
the current result, variables from the current frame are retained so the fold is evaluated per
current receiver.
The fresh variables introduced by folds and cardinality are calculated variables. Their from
component is an attribute label such as _anon_; the actual derivation is the defining equation
X = Σop(comp) or N = Σcount(comp). _anon_ is the attribute name for unnamed results in unary
frames.
ℒ(Γ, P, E.Σ)
{
let γ = ℒ(Γ, P, E)
let (comp,K) = comprehension(Γ, P, γ)
let comp have type Set<T>
let X be fresh of type Set<T>
return ⟨
{ _anon_ = X:Set<T> }
for
{ X:Set<T> from _anon_ } ∪ K
where
X = comp
⟩
}
ℒ(Γ, P, E.Σop)
{
let γ = ℒ(Γ, P, E)
let (comp,K) = comprehension(Γ, P, γ)
let T_out = foldtype(op, type(comp))
let X be fresh of type T_out
return ⟨
{ _anon_ = X:T_out }
for
{ X:T_out from _anon_ } ∪ K
where
X = Σop(comp)
⟩
}
Cardinality expressions are boolean scalar folds over the same comprehension:
Cardinality protects the current frame before building the comprehension. This keeps receiver
predicates outside the counted set, so a path such as supplier.[city:+] counts cities per supplier
rather than counting supplier-city pairs globally.
ℒ(Γ, P, E:k)
ℒ(Γ, P, E:k..m)
ℒ(Γ, P, E:?)
ℒ(Γ, P, E:+)
ℒ(Γ, P, E:k+)
{
let γ = ℒ(Γ, P, E)
let Pcard = P ∪ vars(free(currentFrame(Γ)))
let (comp,K) = comprehension(Γ, Pcard, γ)
let N be fresh of type Integer
let Ccard =
N = k for E:k
0 ≤ N ≤ 1 for E:?
N ≥ 1 for E:+
k ≤ N for E:k+
k ≤ N ≤ m for E:k..m
return ⟨
{ _anon_ = Ccard:Boolean }
for
{ N:Integer from _anon_ } ∪ K
where
N = Σcount(comp)
⟩
}