Wandler lets you write an ordinary Clojure data pipeline, elaborates it to a term in
a dependently-typed kernel (the CIC kernel that also admits Lean 4 / Mathlib),
optimizes it by rewriting — keeping only rewrites that carry a machine-checked
proof optimized ≡ naive — and lowers it to fast Clojure. This document is the
algebra under that surface: how a malli schema becomes a kernel type, what those
types buy the planner, and the one structure (a sum-product over a semiring) that
unifies relational aggregation and probabilistic inference.
The reading discipline throughout is the trust ledger (see ARCHITECTURE.md):
a > **✓ proven** box marks something a kernel proof object guarantees; a
> **⚠ trusted** box marks a boundary wandler does not prove (a measured statistic,
a floating-point weight, a foreign engine). The exact carriers (counting Nat,
existence Bool) are proven; the probabilistic/measure weights are trusted. Keeping
the two visibly apart is the whole point.
The Tutorial walks the same ground from the outside (runnable REPL output); this doc gives the formal account behind it.
You never write kernel types directly. You write a malli schema and a mapping turns it
into a kernel type. The mapping lives in wandler.surface.malli, in two directions:
- EXPORT —
type-expr->malli: a verified function's CIC type → a malli schema (for runtime contract checking and generative testing of the compiled runtime). - IMPORT —
malli->type-expr: a malli schema → a CIC type, with:mapbecoming a right-nestedProdrecord and refinements becomingSubtypepredicates.
The import direction is the one that drives the planner. The scalar core is exact:
:int → Int, :boolean → Bool, :string → String, :double → Float, and crucially
[:int {:min 0}] → Nat and [:map …] → a record model (malli-record returns
{:keys :index :field-types :rec-type} — the column/type information a query planner
needs). Range constraints become dependent refinements:
;; [:int {:min 18}] ↦ Subtype Nat (fun v => 18 ≤ v) (ksubtype-nat)
;; [:string {:min 1}] ↦ Subtype String (fun s => 1 ≤ s.length)
;; [:set X] ↦ Subtype (List X) (fun l => List.Nodup X l)This is the same m/=> you already see in the tutorial. The §3 rev-by-region schema —
[:=> [:cat [:sequential [:map [:cid [:int {:min 0}]] [:region [:int {:min 0}]]]]
[:sequential [:map [:cid [:int {:min 0}]] [:amount [:int {:min 0}]]]]]
[:sequential :int]]— compiles each [:map …] argument to a record type whose fields are Nat (from
[:int {:min 0}]), and the field keys/positions are what let the optimizer turn the
customers-⋈-orders join into a scatter (:groupby-reduce-join).
⚠ trusted (honesty note). The functor is not total: a CIC type with no malli mapping exports to
:any(with a warning,*warn-unknown?*), and a malli form with no kernel mapping (e.g.[:re …]regex refinements) throws on import. The covered subset — scalars,:map/:tuple/:sequential/:maybe/:set, and:int/:stringrange refinements — is what's proven-out today; everything else is a clean rejection, not a silent approximation. "Total functor" in earlier drafts was aspirational.
A Subtype T P is a value of T carrying a proof of P. The payoff is that the
optimizer can read that proof and delete work. The engine is wandler.surface.refine/prove-const:
given a filter predicate p : T → Bool, it decides whether p is constantly true
(∀x:T, p x = true — drop the filter) or constantly false (∀x:T, p x = false —
the pipeline is empty), and returns a kernel proof term, consumed once at optimization
time. It tries proof candidates cheapest-first:
- definitional (
rfl-proof) —fun x => Eq.refl Bool b, valid whenp xreduces tob(e.g.(>= x 0)overNat). - refinement subtyping (
subtype-property-proof) — when the predicate reads aSubtype.val, its carried.propertydischargesk ≤ vdirectly viaNat.ble_eq_true_of_le(andsubtype-negation-prooffor the always-false case). - omega (
omega-proof) — materialize the refinement as a hypothesis, bridge theBoolcomparison to itsProp, and close with theomegadecision procedure — the systematic F*/Liquid-Haskell-style discharge.
Each candidate is admitted only if it survives strict .check (not the lenient
inferType), so the planner never "verifies" a filter elimination that the later
a/defn admission would reject. prove-monotone likewise proves that an update
function preserves a refinement (∀w, P w → P (f w)), which is what makes a
range-refined field safe to write through (inc/+k keep 18 ≤ age).
✓ proven. Filter elimination, distinct-removal over a
:set(theList.Noduprefinement licensesnodup_eraseDups), and refinement-preserving writes are each backed by a kernel proof term over the value'sSubtype.property. The type literally drives the plan: a constraint in the schema is a theorem the optimizer gets to use.
The tutorial's spine is "no types → inferred → explicit, same body, more guarantees." Formally these are three ways the same elaborated term acquires a typing context, and the typing context is what unlocks rewrites.
-
No types. A plain Clojure
defn. Wandler is not involved — no term, no proof, no plan. The body is just data flowing throughclojure.core. -
Inferred (
wandler.infer/induce-types!). Show a representative sample; malli'smp/providerecovers the structure andfield-profileadds the value-level refinementsmp/provideomits, split into two honest lanes:- the guarded-sound lane — unique-key / FD candidates. A collision-free field on a
sample is only a candidate key (a high-cardinality non-key can look unique), so it
flows to
wandler.adaptive, which certifies the rewrite under the key and guards the key at the runtime boundary, deopting if violated. A wrong guess costs a missed optimization, never a wrong answer. - the advisory-prior lane — numeric ranges, ndv, small domains. These are
one-sidedly-too-tight (a sample never exceeds the true domain), so they feed the cost
model as priors (
cost-priors), fused with measured evidence. A wrong prior costs speed, never correctness, because the kernel still certifies whatever plan is chosen.
The enriched profile round-trips back into a malli schema (
refined-schema) and is registered under the function name, so a subsequenta/defnwith the same body in the same namespace picks up the kernel types with no new machinery. - the guarded-sound lane — unique-key / FD candidates. A collision-free field on a
sample is only a candidate key (a high-cardinality non-key can look unique), so it
flows to
-
Explicit (
:-binders orm/=>). The same elaboration, but now you state the refinements — and the point of writing them by hand is control: declaring[:int {:min 0 :max k}]ranges sharpens selectivity, which steers the physical strategy (pre-aggregated index, grace-hash spill, drive-direction reorder).
wandler.gradual is the front door over the planner that makes the ladder legible:
gradient runs the planner at increasing annotation tiers and reports what each tier
unlocks; coach empirically probes which annotation would unlock which rewrite. The
invariant across the whole ladder: richer annotations change only the plan, never the
result — every tier returns a kernel-certified optimized ≡ original.
✓ proven. At every rung the adopted plan carries
:verified? true. The ladder is a monotone increase in optimization, certified at each step; it never trades correctness for types.
The unifying claim: a query is a sum-product over a semiring S. reduce/+ is the
sum (⊕); map/*/join is the product (⊗). Swap S and the same operators
compute different things. wandler.inference.semiring makes this concrete with
Rel A S := A → S (a relation as a finite map element→weight) and operators
(rel-add, rel-mul, rel-join, rel-sum) generic over S:
| semiring | ⊕ / ⊗ |
computes | carrier |
|---|---|---|---|
counting |
+ / * |
query cardinalities, Z-set weights | Nat/Int (exact) |
existence |
∨ / ∧ |
reachability, set membership | Bool (exact) |
tropical |
min / + |
shortest paths | Float |
probability |
+ / * |
finite distributions (FinDist) |
ℝ≥0 (float) |
provenance |
∪ / pairwise-∪ |
the Boolean formula for exact inference | DNF set-of-sets |
The FAQ engine (faq, factor-join, factor-marginalize, elimination-order) runs a
query as ⊕_{eliminated} ⊗_{factors}, eliminating one variable at a time. The
elimination order is the plan — it determines cost, not the result, for a
commutative semiring — and this is the precise sense in which database query
optimization and probabilistic variable elimination are the same problem.
What is actually proven in the kernel here is the algebra that licenses that claim, for the exact carriers:
Bool.absorptive—∨ a (∧ a b) = a(proved byBool.casesOn). This is the datalog° POPS recursion-safety certificate: an absorptive semiring converges under recursion.fixpointis gated by it (recursion-safe?) — it refuses a non-absorptive semiring (counting:1 + 1·1 = 2 ≠ 1, so a recursive count diverges).Semiring.bool_distrib—a ∧ (b ∨ c) = (a∧b) ∨ (a∧c), the FAQ factorization core.Semiring.bool_or_comm/Semiring.bool_and_comm— the elimination-order independence.
For counting, the optimizer's actual factorization plan is certified by
Map.foldl_join_sum_factor (the pre-aggregated FAQ index) and Map.foldl_join_factor
(push an aggregation through a join) — proved over Nat, and what wandler.optimize.faq
rides to lower a counting query to a verified physical plan. The aggregation monoids these
laws reassociate over are the owned WAddMonoid / WSemiring prelude structures, whose
linearity laws (sum_map_split, foldl-over-append, …) are proved in wandler.core.monoid
by structural induction and normalized by ac_rfl against genuine Std.Associative /
Std.LawfulIdentity instances (wandler.laws.ac).
✓ proven (exact carriers).
Bool.absorptive,Semiring.bool_distrib, the Bool commutativities, and theNatfactorization laws are kernel theorems (each cached and re-checked byinstall!). The counting/existence semirings are exact; the recursion gate is a theorem, not a documentation table (contrast the datahike/Scallop POPS matrix).
⚠ trusted (honesty note). The generic
faq/fixpointengine insemiring.cljis plain Clojure — its soundness forBoolis backed by the proven laws above, but the engine code itself is not kernel-checked the way the optimizer'sNatfactorization plan is. Treat the FAQ engine as a research surface for arbitrary semirings; the certified path is the relational optimizer forNat/Bool.
Read the semiring carrier as weights and the relational algebra becomes a monad
(wandler.inference.dist): fin-return sr a = {a 1̄} and
fin-bind sr m f = ⊕_a m(a) ⊗ (f a). Two instances fall out by picking the semiring:
FinSet over existence (finite nondeterminism / the powerset monad) and FinDist over
probability (the distribution monad). faq is then exactly "do the ⊕-combines in a
cost-chosen order" — variable elimination is marginalization. Inference is not a
separate engine; it is the core sum-product over a probability semiring.
Exact marginals need care: naive (prob, +, ×) double-counts shared variables. So the
implemented path is the datahike PROBLOG split — run the provenance semiring to a
Boolean formula (the certified algebraic part), then weighted-model-count the formula
(wmc) to get the marginal.
⚠ trusted. WMC is
#P-hard; the in-repowmcis exact by enumerating2^|facts|assignments (production swaps in a knowledge-compilation backend — LogicNG/Ganak). It operates on floating-point probabilities. Every probabilistic weight is L2 trusted. What is L0 is the symbolic part — the provenance algebra and the semiring laws; the numbers that come out are a trusted evaluation. The continuous analogue (the Giry monad) is explicitly an oracle boundary, not a kernel object.
It is tempting to present wandler as a verified monoidal category carrying four algebraic structures with machine-checked coherence between them. That framing is the design direction; here is the honest accounting of what each structure actually has:
- Semiring / ring (aggregation, FAQ, joins). Proven for the exact carriers, as above — this is the load-bearing, certified structure.
- Differential / comonad (incremental view maintenance, DBSP). The
∂pass lowers a batch plan to its incremental form; per the streaming layer each stage cites a kernel law (linear filter, bilinear join product rule, sum homomorphism). This is the second structure with kernel proofs — see STREAMING.md for the laws and the linearity boundary (where incremental maintenance stops being exact). - Lens / optic (the backward direction). The first-projection product lens has its
round-trip laws proven —
Lens.fst_PutGetandLens.fst_GetPut, bothEq.refl(wandler.inference.lens). But lens composition (lens-comp) is a runtime combinator with a runtime round-trip witness (round-trips?), not yet a kernel composition law. The projection laws are the L0 anchor; general compositional lawfulness is L2 / directional. - Measure (probabilistic inference). The symbolic part rides structure 1; the numeric part is trusted, as above.
⚠ trusted (honesty note). "Four structures with machine-checked coherence" is aspirational. What is certified today is structure 1 (semiring/relational core) and structure 2 (the DBSP differential), plus the projection laws of structure 3. The compositions between structures — a differential-of-an-aggregation, a lens-into-a- group-by — are where a normal system would hide an untested interaction; keeping all structures over one CIC core makes each composition a candidate for a kernel obligation, but most of those obligations are design goals, not discharged theorems. Do not read this section as a guarantee of cross-structure coherence.
The one slogan that is fully earned: verification licenses the fast representation. Once the algebra of a single structure is proved, the runtime is free to pick the fast form — a one-pass fold, an unboxed array, a parallel fork-join, an incremental view — because the kernel checked it computes the same function as the obvious code.
- TUTORIAL.md — the gradual ladder from the outside, with runnable output.
- ARCHITECTURE.md — elaborate → optimize → lower, and where the trust boundary sits (the full trust ledger).
- OPTIMIZER.md — the semiring/relational laws as optimizer recognizers (fusion, filter pushdown, join factorization, reorder).
- STREAMING.md — the differential
∂structure and the linearity conditions for incremental maintenance. - JIT.md — measure-then-replan: trusted statistics steering a certified plan.
- INFERENCE.md — the semiring view made runnable: variable elimination as the certified factorization, the carriers, WMC, and the FinDist/Giry monad.
- ENGINES.md — datahike/stratum as trusted axioms, and a marginal computed across two databases by the same certified elimination.
- REFERENCE.md — the verb vocabulary and the honest edge cases.
- BENCHMARKS.md — what the verification actually buys, measured.
- README — the 30-second version and the install layers.