frei (free) + eigen (self) — a Lean library whose programs are effectful free-monadic
computations (Free, the source of truth), given meaning by Lean-side interpreters and
reflected into a dumb, typed imperative AST that denotes into interaction trees, with the
round-trip proven sound by weak bisimulation (≈, ITree.Eutt).
A program is a Free Op SOp α with two orthogonal extension slots:
Op : Type → Type → Type 1— first-order effects, opaque instructions (e.g.CircOp.assert).SOp : Type → Type— scoped constructs: each operation carries an in-monad block (e.g.HintS, the out-of-circuithint).NoScoperecovers a plain first-order DSL.
Free is the source of truth: run it in Lean, or reflect% it for compilation. A scoped op's block
is a positive recursive occurrence in the monad (the hop constructor) — an ordinary inductive, so
there is no self-reference and no function values inside ops.
Six top-level concerns:
- Free monads —
Freigen/Free.lean— the monadFree Op SOp(pure/op/hop) +Monadinstance, and one generic interpreterrun: fold a program into any monadM, given a handler for the first-order ops and one for the scoped ops (which receives the interpreted block, so it may run it, erase it, …). Plus the trivial scoped signatureNoScope. How a scoped construct is interpreted is the handler's business — supplied by each example. - Semantics —
Freigen/ITree/— the coinductive denotation domainComp Op(Effectfunctor +ret/tau/vis/fail),bind+ monad/computation laws, weak bisimulation≈(Eutt) as a lawfulSetoidwith its congruence algebra, and the general-recursion combinatormrec(withinterpand the call-extended signatureCallOp). - AST —
Freigen/Ast/—Ast.Tp(the object-type universeTpand the reified primitive ops: totalUn/Bin— arithmetic is a typed op node, not an opaque closure — and partialPOpwithOption-valued denotation:vget/vset/aget/aset/arrToVec/natToFin) andAst.Basic(the dumb typed ASTCode/Prog:ret/lit/un/bin/pop/vec/arr/fold/vgen/op/ite/call/scope, top-leveldef_and recursiverec_, PHOAS over a function familyFand valuesV;denoteProguniformly intoComp— a function is aComp-Kleisli subroutine, arec_is tied bymrec;ofFree) andAst.Sexp(the printer: a uniform, machine-parseable S-expression serialization of a closedProg— fully parenthesized, prefix, every binder type-annotated; the grammar is pinned in its module docstring, and it is the format#compileemits, the examples pin, and the Rust SDK parses). The singlepopnode carries every proof-erased partial primitive (a bareNatindex, no in-bounds proof), so its denotation is partial — a failingPOp.denote(an out-of-range access, a size mismatch) isfail. AProgadmitsrec_, so it has no total map into a finite monad — the AST does not assume termination.fold(Fin.foldl/Fin.foldlM) andvgen(Vector.ofFn) are first-class bounded loops (a static trip count, aFin-indexed body block) — loops are kept as control flow, never unrolled, and their denotations are total andtau-free (so a pure loop is equal, not merely≈, to its source). - Reflection —
Freigen/Reflect/—reflect%compiles aFreeprogram into aProgwith its≈-soundness againstofFree.Reflect.Basicreifies Lean types intoTp, A-normalises pure computation intoun/bin/lit(and a sourcecoll[i]/coll.set i xinto proof-erasedpopnodes), and spills each called helper —Free-valued or pure — into adef_(definitions stay folded, never inlined; helper bodies may call other helpers, spilled in dependency order; a reifiable pureletkeeps its sharing) (a two-pass discovery/build, monomorphised on the helper's(name, argument-types, result-type));mainmay be a function of the program's inputs.Reflect.Recursionadds the recursion arm (a structural recursionNat → A₁ → … → Free Op SOp ρon the first argument, extraTp-typed state threading through the tupledrec_state → arec_node) with itsmrecadequacy (parameterised by the measureμ = ·.1). The reflector emits nosimp— every proof it outputs is a compositional/structural term (simpis confined to the fixed library lemmas likeadeqBody). Value-arm soundness: the same walk, re-run in proof mode, walks the source at the concrete representation (V := Tp.denote) and, at every node, applies that node's congruence lemma (Reflect.Sound'ssc_op/sc_bind/sc_call/…) to the sub-terms' equations — a proof term mirroring the source. A reflected get/set inserts exactly the source's own in-bounds proof (sc_vget … h=dif_pos h), so the erasedfailbranch is closed at that node — no decidability, no global rewrite, sound for a symbolic index at any depth (buried under effects, branches, or a helper call), including dynamically-sizedArrayreads. A non-reifiable argument (an in-bounds proofj < n) is erased from the program inputs and instead quantifies the soundness statement. - Examples —
Freigen/Examples/— theCircuit/folder (CircOp+ scopedhint,runCirc/conCirc):Circuit.Basicexercises the features one by one (hints, helpers, inputs, monomorphisation, collections, casts, pure and effectful loops);Storage(hint-lessStoreOp, operationalrunStore); andRecursion(countdown/sm, statefulsumAcc/countAsserts). Every example proves its≈-soundness and pins its statement/result/AST with#guard_msgs. A real circuit — the Poseidon hash over BN254Fr(reference constants, static round schedule), reflected with kept loops and folded definitions and pinned against the circomlib test vectors — lives in the downstream client (examples/client/), doubling as the#compilegolden test. - Compilation —
Freigen/Compile.lean— turns the reflect-and-serialize capability into a usable tool: aDSLtype-class carrying each signature's op/scope naming (sorender/serializeare the argument-freepp/sexp), and a#compile foo => "path"command recording which reflected program to emit where. A Lakelibrary_facet prog(inlakefile.lean) does the writing:lake build <lib>:progserializes every#compile'd program of<lib>and writes the.progfiles (the uniform S-expression format ofAst.Sexp). It works on any downstream library thatrequires Freigen — seeexamples/client/. Since#compilepoints at areflect%result, an artifact only exists if its≈-soundness proof type-checks: every emitted file is certified against its source. - Rust SDK —
rust/— the consumer side of the.progformat: a dependency-light crate (freigen) with an S-expression reader, a typed Rust AST mirroringProg/Codeone-to-one (ready for a client compiler to walk), and a canonical interpreter mirroringdenoteProg— the DSL's two extension slots stay client-injectable through aHandlertrait (a custom op is a named callback; a scoped block arrives as a runnable closure, run inline by default). Its test suite parses and executes the goldens project's artifacts E2E on CI —myProgram's witness generation and the Poseidon circuit against the circomlib known-answer vectors.
Effects are first-order and opaque; scoped constructs carry an in-monad block and live in hop.
reflect% compiles a Free program into a typed imperative Prog — with reified arithmetic,
first-class functions (def_), and recursion (rec_) — and everything denotes into the same
interaction-tree domain Comp, sound by the uniform ≈: denoteProg (reflect% foo) ≈ ofFree foo.