This section applies the principles from module 1 through semantic types, proof-bearing values, phantom tags, and abstract models. Each pattern states which distinctions or properties its types enforce, and which claims require additional proofs.
Pattern: Every semantic distinction claimed to be enforced by Lean MUST be represented in the types or constructors used by the interface. State whether the representation restricts the possible values or makes a particular cross-use ill-typed. Meaning that exists only in strings or other shared primitive values is not a type-level distinction.
Rationale: When two semantic domains use the same String type, Lean accepts either value wherever a String is expected. A decoder can distinguish them by returning a domain-specific type. An enumeration serves a different purpose: its constructors restrict the vocabulary, but remain values of the same type.
Implementation Strategy:
Identify the distinctions the interface claims to enforce.
Use an enumeration for a closed vocabulary; use distinct types, indices, or refinements when the interface must reject particular cross-use. The enumeration alone does not establish that an operation is authorized.
Use smart constructors where validation is needed.
Decode raw strings into the semantic type at admission, and require that type downstream. Treat the decoder as the place where you establish validation. A result type alone does not specify how the decoder interprets, rejects, or normalizes input; claimed behavior needs a contract about that decoder (§3.7).
Example - Entity Roles:
importMathlib.Data.List.BasicimportAudit.DocPreludeopenGlossary/-- Closed vocabulary for the permission policy below. -/inductiveEntityRole/-- Submits proposals. -/|proposer/-- Reviews proposals. -/|reviewer/-- Observes without acting. -/|observer/-- Administers the system. -/|administratorderivingRepr,DecidableEq/-- Permissions are derived from roles, not string parsing. -/defEntityRole.canPropose:EntityRole→Bool|.proposer=>true|.administrator=>true|_=>false/-- Whether the role permits reviewing. -/defEntityRole.canReview:EntityRole→Bool|.reviewer=>true|.proposer=>true|.administrator=>true|.observer=>false/-- Type-safe role assignment with no string parsing
(`Id`: wrap identifiers in a phantom-typed key per §2.3). -/structureEntitywhere/-- The entity's tagged identifier. -/id:Glossary.IdGlossary.EntityTag/-- The assigned roles; use `Finset EntityRole` (from `Mathlib.Data.Finset.Basic`,
with its richer API) once duplicates matter. -/roles:ListEntityRole/-- Compute whether any assigned role permits proposing. -/defEntity.canPropose(e:Entity):Bool:=e.roles.any(funr↦r.canPropose)
All four constructors have type EntityRole. The type restricts role values to that vocabulary; the functions compute permission decisions. An operation requiring permission must either enforce that decision or require corresponding proof evidence. The enumeration alone does not prevent an unauthorized operation.
Anti-Pattern to Avoid when the interface claims role distinctions are type-enforced (this implementation elaborates, but its behavior depends on an unconstrained String rather than the closed EntityRole constructors, so the claimed type boundary does not exist):
importInit.Data.String.Search/-- An entity whose role is an unconstrained string. -/structureBadEntitywhere/-- An untyped identifier. -/id:String/-- The role as free text, such as `"proposer"` or `"reviewer"`. -/roleString:String/-- Whether the role text contains the substring `"proposer"`. -/defBadEntity.canPropose(e:BadEntity):Bool:=e.roleString.contains"proposer"-- substring search over an unconstrained value
Pattern: Use dependent types, including Subtype (written {x : T // P x}), to bundle values with proofs. A subtype adds the predicate P to the underlying type T. Every value supplies evidence of that predicate, and the interface should state when later use depends on that evidence.
Rationale: Every value of {p : ℝ // 0 ≤ p ∧ p ≤ 1} supplies a real number and a proof of its bounds. Further properties may follow from those bounds, but reachability through a particular process or consistency with other quantities requires evidence of the corresponding relation. Documentation explains the predicate’s intended meaning and limits; it does not replace the proof (module 0: non-vacuity).
Implementation Strategy:
Identify unary properties that must hold for every admitted value.
Use Lean's subtype notation {x : T // P x} to create refined types.
For complex invariants, use structures with proof fields.
Provide smart constructors that bundle values with their proofs.
Ensure admission and every update return refined types. Justified raw internal representations follow the verified-boundary alternative in §1.1. State and prove transition or history claims separately. Include initialization, preservation, and composition obligations where the claim requires them. A predicate about one admitted state alone does not establish those relations.
Example - Core Refinement Patterns (shared Time type: import Audit.DocPrelude, defined in module 4 §4.1):
importMathlib.Topology.UnitIntervalimportMathlib.Tactic.Linarith-- `linarith` in `TimeInterval.duration`importAudit.DocPreludeopenGlossary/-- A probability reuses Mathlib's canonical closed unit interval. -/abbrevProbability:Type:=unitInterval/-- Smart constructor for probabilities. -/defmkProbability(p:ℝ)(h:0≤p∧p≤1):Probability:=⟨p,h⟩/-- Non-vacuity: at least one probability exists. This proves inhabitance,
nothing stronger. -/example:NonemptyProbability:=⟨mkProbability0⟨le_rfl,zero_le_one⟩⟩/-- Mathlib's interval symmetry proves complement closure once. -/defProbability.complement(p:Probability):Probability:=unitInterval.symmp/-- Valid identifier: non-empty, alphanumeric with underscores. -/defValidIdentifier:={s:String//0<s.length∧s.all(func↦c.isAlphanum||c='_')}/-- Time intervals with proven ordering (`Time`: `import Audit.DocPrelude`). -/structureTimeIntervalwhere/-- The start time. -/start:Time/-- The finish time. -/finish:Time/-- Proof field: the interval is ordered. -/valid:start≤finish/-- Duration of an interval is provably non-negative. -/defTimeInterval.duration(ti:TimeInterval):{d:ℝ//0≤d}:=⟨ti.finish.val-ti.start.val,ti:TimeInterval⊢ 0≤↑ti.finish.val-↑ti.start.valti:TimeIntervalhv:↑ti.start.val≤↑ti.finish.val⊢ 0≤↑ti.finish.val-↑ti.start.valAll goals completed! 🐙⟩/-- Bounded collections with size guarantees. -/defBoundedList(α:Type)(n:ℕ):={l:Listα//l.length≤n}/-- Adding requires proof we won't exceed bound. -/defBoundedList.cons{α:Type}{n:ℕ}(x:α)(bl:BoundedListαn)(h:bl.val.length<n):BoundedListαn:=⟨x::bl.val,α:Typen:ℕx:αbl:BoundedListαnh:(↑bl).length<n⊢ (x::↑bl).length≤nα:Typen:ℕx:αbl:BoundedListαnh:(↑bl).length<n⊢ (↑bl).length+1≤nAll goals completed! 🐙⟩
The shared glossary deliberately makes Time nominal while ResourceAmount reuses NNReal. Although both expose non-negative-real values, direct cross-use is rejected:
✗ Rejected by Lean, as intendedimportAudit.DocPrelude/-- Consume a resource amount. -/defconsume(_amount:Glossary.ResourceAmount):Unit:=()/-- Attempt to consume a time as a resource amount. -/defwrongDomain(t:Glossary.Time):Unit:=consumeApplication type mismatch: The argumentthas typeGlossary.Timebut is expected to have typeGlossary.ResourceAmountin the applicationconsumett
Lean's error at line 7, the checked evidence:
Application type mismatch: The argument
t
has type
Glossary.Time
but is expected to have type
Glossary.ResourceAmount
in the application
consume t
Pattern: Parameterize a wrapper by a type tag that does not occur in its stored fields. Different tags distinguish otherwise similar types during type checking. The tag carries no runtime value; this does not by itself specify the wrapper’s compiled representation.
Rationale: A function requiring TaggedId UserTag rejects a TaggedId ProposalTag argument. This prevents direct cross-use of those types. Public payload access and constructors still permit explicit rewrapping under another tag. It does not prove that the wrapped string identifies an existing user or proposal.
Implementation Strategy:
Define a generic wrapper type parameterized by a phantom tag.
Create tag types that are not definitionally equal; different names for the same type do not suffice. Empty inductive types suffice because no tag value is stored.
Define type aliases using the wrapper with specific tags.
Verify representative wrong-tag applications fail by type mismatch.
Example - Type-Safe Identifiers:
importMathlib.Basic.Real.BasicnamespacePhantomIds/-- An identifier tagged with the phantom type `entity`, which it does not store. -/structureTaggedId(entity:Type)where/-- The identifier text. -/value:StringendPhantomIdsopenPhantomIds/-- Phantom tag for users: an empty type, used only as a type index. -/inductiveUserTag/-- Phantom tag for proposals. -/inductiveProposalTag/-- Phantom tag for entities. -/inductiveEntityTag/-- Phantom tag for content. -/inductiveContentTag/-- User identifiers. -/defUserId:=TaggedIdUserTag/-- Proposal identifiers. -/defProposalId:=TaggedIdProposalTag/-- Entity identifiers. -/defEntityId:=TaggedIdEntityTag/-- Content identifiers. -/defContentId:=TaggedIdContentTag/-- A user record. -/structureUserwhere/-- The user's name. -/name:String/-- These function parameters specify argument types only; this example makes
no claim about lookup results or proposal submission behavior. -/example(getUser:UserId→OptionUser)(submitProposal:UserId→ProposalId→Bool)(uid:UserId)(pid:ProposalId):Unit:=let_:=getUseruidlet_:=submitProposaluidpid()/-- A real quantity tagged with its phantom unit of measure. -/structureQuantity(unit:Type)where/-- The magnitude, in the tagged unit. -/value:ℝ/-- Phantom unit tag: metres. -/inductiveMeters/-- Phantom unit tag: seconds. -/inductiveSeconds/-- Phantom unit tag: metres per second. -/inductiveMetersPerSecond/-- A distance in metres. -/defDistance:=QuantityMeters/-- A duration in seconds. -/defDuration:=QuantitySeconds/-- A velocity in metres per second. -/defVelocity:=QuantityMetersPerSecond/-- The velocity `d / t`. `noncomputable` because ℝ division is a
noncomputable field operation — the lesson here is the *type* safety,
not executable arithmetic. -/noncomputabledefvelocity(d:Distance)(t:Duration):Velocity:=⟨d.value/t.value⟩-- velocity t d -- Would be a type error (wrong argument order)
Boundary of enforcement: Phantom tags make swapped types an elaboration error. Quantity Seconds and Quantity Meters are distinct types. Real division is total: velocity of a zero Duration produces a well-defined Velocity with value 0. No type error or exception occurs. See module 3 §3.2.1 for totalized operations and encoding a nonzero domain when required.
✗ Rejected by Lean, as intendedimportMathlib.Basic.Real.Basic/-- The unit tag for meters. -/inductiveMeters/-- The unit tag for seconds. -/inductiveSeconds/-- A real quantity tagged with its unit. -/structureQty(unit:Type)where/-- The quantity's magnitude. -/value:ℝ/-- A distance in meters. -/abbrevDistance:=QtyMeters/-- A duration in seconds. -/abbrevDuration:=QtySeconds/-- Argument order is enforced by the distinct tags. -/noncomputabledefratio(d:Distance)(t:Duration):ℝ:=d.value/t.value/-- Swapped arguments are a type error at elaboration. -/deffailed to compile definition, consider marking it as 'noncomputable' because it depends on 'ratio', which is 'noncomputable'bad:ℝ:=ratioApplication type mismatch: The argument{value:=1.0}has typeQtySecondsbut is expected to have typeDistancein the applicationratio{value:=1.0}(⟨1.0⟩:Duration)(⟨1.0⟩:Distance)
Lean's error at line 21, the checked evidence:
Application type mismatch: The argument
{ value := 1.0 }
has type
Qty Seconds
but is expected to have type
Distance
in the application
ratio { value := 1.0 }
Pattern: Define system concepts using abstract mathematical types when the claim does not depend on a concrete representation — and define them against the concrete executable types when it does. Abstraction is a tool for claim precision, not a universal exclusion of executable representations.
Rationale: An abstract model omits representation details that the claim does not need. Its proofs establish properties of that model. Transferring a result to an executable Lean definition MUST use a checked theorem relating the two under the required hypotheses. Claims about external execution additionally identify the compiler, runtime, and external-world assumptions that connect the formal objects to execution (module 1 §1.3).
Implementation Strategy:
Use mathematical types (ℝ, ℕ, Set α) when the claim is representation-independent
Keep representations abstract by parameterizing structures over the content type. A type parameter is quantified and introduces no project logical axiom. An implemented opaque package can instead expose selected operations over a hidden representation (module 6 §6.5.1).
Specify behavior through mathematical properties when abstraction serves the claim; specify executable behavior against the executable definition itself when it does not
Transfer a model claim to an executable definition only through a proved refinement theorem whose hypotheses name the exact machine conditions — bounds, overflow freedom, domain restrictions — under which the correspondence holds
Let implementations choose appropriate concrete representations
Example - Abstract Content Storage:
importMathlib.Data.Set.Basic/-- Abstract storage, parameterized over the content type. No representation
chosen, no logical assumption introduced: `Content` is a bound parameter. -/structureContentStore(Content:Type)where/-- The set of stored content. -/contents:SetContent/-- Retrieval relation (mathematical, not algorithmic). -/Retrievable:Content→Prop/-- Consistency: can only retrieve what's stored. -/mem_contents_of_retrievable:∀c,Retrievablec→c∈contents/-- A verification scheme specified by its relation and the law every
instance must prove. No commitment to binary trees, hash functions, or
byte arrays — `Content` and `Id` are parameters. -/structureVerificationScheme(ContentId:Type)where/-- Verification is a mathematical relation, not an algorithm. -/Verifies:Content→Id→Prop/-- Soundness: one identifier verifies at most one content. -/eq_of_verifies:∀c₁c₂:Content,∀i:Id,Verifiesc₁i→Verifiesc₂i→c₁=c₂
The mem_contents_of_retrievable field proves that retrieved content belongs to contents; it does not require retrieving any content. The eq_of_verifies field proves content uniqueness for each verifying identifier; it does not require any identifier to verify anything. An always-false relation satisfies either condition. Existence, retrieval completeness, or a decision procedure requires separate evidence when claimed.
Example - A Checked Refinement Relation:
When the claim transfers from a model to a machine implementation, the transfer theorem carries the machine's exact semantics in its statement and its hypotheses:
importMathlib.Data.Nat.Basic/-- Model: exact natural-number addition. -/defmodelAdd(ab:ℕ):ℕ:=a+b/-- Implementation: 32-bit machine addition, which wraps modulo 2^32. -/defimplAdd(ab:UInt32):UInt32:=a+b/-- The refinement theorem: under the no-overflow hypothesis the machine
semantics needs, the executable result is the model result. -/theoremtoNat_implAdd_of_lt(ab:UInt32)(h:a.toNat+b.toNat<2^32):(implAddab).toNat=modelAdda.toNatb.toNat:=a:UInt32b:UInt32h:a.toNat+b.toNat<2^32⊢ (implAddab).toNat=modelAdda.toNatb.toNata:UInt32b:UInt32h:a.toNat+b.toNat<2^32⊢ (a+b).toNat=a.toNat+b.toNatAll goals completed! 🐙/-- The hypothesis is load-bearing: at the word boundary the machine result
wraps to 0 while the model sum is 2^32. -/example:((0xffffffff:UInt32)+1).toNat=0∧modelAdd(0xffffffff:UInt32).toNat1=2^32:=by⊢ (4294967295+1).toNat=0∧modelAdd(UInt32.toNat4294967295)1=2^32decideAll goals completed! 🐙
Without h, the unconditional refinement statement is false, as the boundary example shows. The checked relation and its hypotheses justify the transfer. UInt32.toNat_add is the exact machine semantics — wrapping modulo 2 ^ 32 — stated by the core library and used by the proof.
Stateful refinement for finite-prefix safety relates transition systems as well as state representations. Relate concrete and abstract states, prove initialization and forward simulation into a precisely defined finite abstract closure, and derive invariant transfer over every finite concrete path. Module 3 §3.9 works through the actual limiter: occupied and free slots are different representations, refusal can stutter, and one reset can match several abstract releases. The proved Lean relation is part of the formal development, distinct from an unproved external adapter.
Anti-Pattern - Unsupported Claims About a Representation: A byte array, hash function, or binary tree is a valid subject for a Lean specification. The defect is claiming properties that its definition and proofs do not establish. A tree representation alone proves no hash-collision or authentication property, and a theorem about an abstract verification relation does not establish the behavior of an unrelated implementation. State the required property and prove it about the chosen representation, or provide the checked correspondence needed to transfer it.