feat: configuration options zeta and zetaDelta

TODO: bootstrapping issues, set `zetaDelta := false` in the simplifier.
This commit is contained in:
Leonardo de Moura 2024-02-18 12:13:20 -08:00 committed by Leonardo de Moura
parent b882ebcf4a
commit 457d33d660
12 changed files with 65 additions and 62 deletions

View file

@ -531,8 +531,8 @@ private partial def mkClosureForAux (toProcess : Array FVarId) : StateRefT Closu
let toProcess ← pushLocalDecl toProcess fvarId userName type bi k
mkClosureForAux toProcess
| .ldecl _ _ userName type val _ k =>
let zetaFVarIds ← getZetaFVarIds
if !zetaFVarIds.contains fvarId then
let zetaDeltaFVarIds ← getZetaDeltaFVarIds
if !zetaDeltaFVarIds.contains fvarId then
/- Non-dependent let-decl. See comment at src/Lean/Meta/Closure.lean -/
let toProcess ← pushLocalDecl toProcess fvarId userName type .default k
mkClosureForAux toProcess
@ -696,8 +696,8 @@ def main (sectionVars : Array Expr) (mainHeaders : Array DefViewElabHeader) (mai
let letRecsToLift := letRecsToLift.toArray
let mainFVarIds := mainFVars.map Expr.fvarId!
let recFVarIds := (letRecsToLift.map fun toLift => toLift.fvarId) ++ mainFVarIds
resetZetaFVarIds
withTrackingZeta do
resetZetaDeltaFVarIds
withTrackingZetaDelta do
-- By checking `toLift.type` and `toLift.val` we populate `zetaFVarIds`. See comments at `src/Lean/Meta/Closure.lean`.
let letRecsToLift ← letRecsToLift.mapM fun toLift => withLCtx toLift.lctx toLift.localInstances do
Meta.check toLift.type

View file

@ -739,7 +739,7 @@ private def addDefaults (lctx : LocalContext) (defaultAuxDecls : Array (Name ×
throwError "invalid default value for field, it contains metavariables{indentExpr value}"
/- The identity function is used as "marker". -/
let value ← mkId value
discard <| mkAuxDefinition declName type value (zeta := true)
discard <| mkAuxDefinition declName type value (zetaDelta := true)
setReducibleAttribute declName
/--

View file

@ -356,7 +356,7 @@ def elabAsFVar (stx : Syntax) (userName? : Option Name := none) : TacticM FVarId
/--
Make sure `expectedType` does not contain free and metavariables.
It applies zeta-reduction to eliminate let-free-vars.
It applies zeta and zetaDelta-reduction to eliminate let-free-vars.
-/
private def preprocessPropToDecide (expectedType : Expr) : TermElabM Expr := do
let mut expectedType ← instantiateMVars expectedType

View file

@ -35,10 +35,10 @@ private def mkAuxLemma (e : Expr) : M Expr := do
let s ← get
let lemmaName ← mkAuxName (ctx.baseName ++ `proof) s.nextIdx
modify fun s => { s with nextIdx := s.nextIdx + 1 }
/- We turn on zeta-expansion to make sure we don't need to perform an expensive `check` step to
identify which let-decls can be abstracted. If we design a more efficient test, we can avoid the eager zeta expansion step.
/- We turn on zetaDelta-expansion to make sure we don't need to perform an expensive `check` step to
identify which let-decls can be abstracted. If we design a more efficient test, we can avoid the eager zetaDelta expansion step.
It a benchmark created by @selsam, The extra `check` step was a bottleneck. -/
mkAuxTheoremFor lemmaName e (zeta := true)
mkAuxTheoremFor lemmaName e (zetaDelta := true)
partial def visit (e : Expr) : M Expr := do
if e.isAtomic then

View file

@ -96,17 +96,17 @@ structure Config where
-/
transparency : TransparencyMode := TransparencyMode.default
/--
When `trackZeta = true`, we track all free variables that have been zeta-expanded.
When `trackZetaDelta = true`, we track all free variables that have been zetaDelta-expanded.
That is, suppose the local context contains
the declaration `x : t := v`, and we reduce `x` to `v`, then we insert `x` into `State.zetaFVarIds`.
We use `trackZeta` to discover which let-declarations `let x := v; e` can be represented as `(fun x => e) v`.
the declaration `x : t := v`, and we reduce `x` to `v`, then we insert `x` into `State.zetaDeltaFVarIds`.
We use `trackZetaDelta` to discover which let-declarations `let x := v; e` can be represented as `(fun x => e) v`.
When we find these declarations we set their `nonDep` flag with `true`.
To find these let-declarations in a given term `s`, we
1- Reset `State.zetaFVarIds`
2- Set `trackZeta := true`
1- Reset `State.zetaDeltaFVarIds`
2- Set `trackZetaDelta := true`
3- Type-check `s`.
-/
trackZeta : Bool := false
trackZetaDelta : Bool := false
/-- Eta for structures configuration mode. -/
etaStruct : EtaStructMode := .all
@ -261,12 +261,12 @@ structure PostponedEntry where
`MetaM` monad state.
-/
structure State where
mctx : MetavarContext := {}
cache : Cache := {}
/-- When `trackZeta == true`, then any let-decl free variable that is zeta expansion performed by `MetaM` is stored in `zetaFVarIds`. -/
zetaFVarIds : FVarIdSet := {}
mctx : MetavarContext := {}
cache : Cache := {}
/-- When `trackZetaDelta == true`, then any let-decl free variable that is zetaDelta expansion performed by `MetaM` is stored in `zetaDeltaFVarIds`. -/
zetaDeltaFVarIds : FVarIdSet := {}
/-- Array of postponed universe level constraints -/
postponed : PersistentArray PostponedEntry := {}
postponed : PersistentArray PostponedEntry := {}
deriving Inhabited
/--
@ -330,7 +330,7 @@ protected def saveState : MetaM SavedState :=
/-- Restore backtrackable parts of the state. -/
def SavedState.restore (b : SavedState) : MetaM Unit := do
Core.restore b.core
modify fun s => { s with mctx := b.meta.mctx, zetaFVarIds := b.meta.zetaFVarIds, postponed := b.meta.postponed }
modify fun s => { s with mctx := b.meta.mctx, zetaDeltaFVarIds := b.meta.zetaDeltaFVarIds, postponed := b.meta.postponed }
instance : MonadBacktrack SavedState MetaM where
saveState := Meta.saveState
@ -374,7 +374,7 @@ section Methods
variable [MonadControlT MetaM n] [Monad n]
@[inline] def modifyCache (f : Cache → Cache) : MetaM Unit :=
modify fun { mctx, cache, zetaFVarIds, postponed } => { mctx, cache := f cache, zetaFVarIds, postponed }
modify fun { mctx, cache, zetaDeltaFVarIds, postponed } => { mctx, cache := f cache, zetaDeltaFVarIds, postponed }
@[inline] def modifyInferTypeCache (f : InferTypeCache → InferTypeCache) : MetaM Unit :=
modifyCache fun ⟨ic, c1, c2, c3, c4, c5, c6⟩ => ⟨f ic, c1, c2, c3, c4, c5, c6⟩
@ -394,11 +394,11 @@ def getLocalInstances : MetaM LocalInstances :=
def getConfig : MetaM Config :=
return (← read).config
def resetZetaFVarIds : MetaM Unit :=
modify fun s => { s with zetaFVarIds := {} }
def resetZetaDeltaFVarIds : MetaM Unit :=
modify fun s => { s with zetaDeltaFVarIds := {} }
def getZetaFVarIds : MetaM FVarIdSet :=
return (← get).zetaFVarIds
def getZetaDeltaFVarIds : MetaM FVarIdSet :=
return (← get).zetaDeltaFVarIds
/-- Return the array of postponed universe level constraints. -/
def getPostponed : MetaM (PersistentArray PostponedEntry) :=
@ -790,10 +790,10 @@ def elimMVarDeps (xs : Array Expr) (e : Expr) (preserveOrder : Bool := false) :
mapMetaM <| withReader (fun ctx => { ctx with config := f ctx.config })
/--
Executes `x` tracking zeta reductions `Config.trackZeta := true`
Executes `x` tracking zetaDelta reductions `Config.trackZetaDelta := true`
-/
@[inline] def withTrackingZeta (x : n α) : n α :=
withConfig (fun cfg => { cfg with trackZeta := true }) x
@[inline] def withTrackingZetaDelta (x : n α) : n α :=
withConfig (fun cfg => { cfg with trackZetaDelta := true }) x
@[inline] def withoutProofIrrelevance (x : n α) : n α :=
withConfig (fun cfg => { cfg with proofIrrelevance := false }) x

View file

@ -56,14 +56,14 @@ def aux := fun (y : Nat) => h (g y)
Note that in this particular case, it is safe to lambda abstract the let-varible `y`.
This module uses the following approach to decide whether it is safe or not to lambda
abstract a let-variable.
1) We enable zeta-expansion tracking in `MetaM`. That is, whenever we perform type checking
if a let-variable needs to zeta expanded, we store it in the set `zetaFVarIds`.
We say a let-variable is zeta expanded when we replace it with its value.
1) We enable zetaDelta-expansion tracking in `MetaM`. That is, whenever we perform type checking
if a let-variable needs to zetaDelta expanded, we store it in the set `zetaDeltaFVarIds`.
We say a let-variable is zetaDelta expanded when we replace it with its value.
2) We use the `MetaM` type checker `check` to type check the expression we want to close,
and the type of the binders.
3) If a let-variable is not in `zetaFVarIds`, we lambda abstract it.
3) If a let-variable is not in `zetaDeltaFVarIds`, we lambda abstract it.
Remark: We still use let-expressions for let-variables in `zetaFVarIds`, but we move the
Remark: We still use let-expressions for let-variables in `zetaDeltaFVarIds`, but we move the
`let` inside the lambdas. The idea is to make sure the auxiliary definition does not have
an interleaving of `lambda` and `let` expressions. Thus, if the let-variable occurs in
the type of one of the lambdas, we simply zeta-expand it there.
@ -89,7 +89,7 @@ let n : Nat := 20;
(let y : {v // v=n} := {val := 20, property := ex._proof_1}; y.val+n+f x, z+10)
```
BTW, this module also provides the `zeta : Bool` flag. When set to true, it
BTW, this module also provides the `zetaDelta : Bool` flag. When set to true, it
expands all let-variables occurring in the target expression.
-/
@ -102,7 +102,7 @@ structure ToProcessElement where
deriving Inhabited
structure Context where
zeta : Bool
zetaDelta : Bool
structure State where
visitedLevel : LevelMap Level := {}
@ -165,9 +165,9 @@ def collectLevel (u : Level) : ClosureM Level := do
def preprocess (e : Expr) : ClosureM Expr := do
let e ← instantiateMVars e
let ctx ← read
-- If we are not zeta-expanding let-decls, then we use `check` to find
-- If we are not zetaDelta-expanding let-decls, then we use `check` to find
-- which let-decls are dependent. We say a let-decl is dependent if its lambda abstraction is type incorrect.
if !ctx.zeta then
if !ctx.zetaDelta then
check e
pure e
@ -207,7 +207,7 @@ partial def collectExprAux (e : Expr) : ClosureM Expr := do
}
return mkFVar newFVarId
| Expr.fvar fvarId =>
match (← read).zeta, (← fvarId.getValue?) with
match (← read).zetaDelta, (← fvarId.getValue?) with
| true, some value => collect (← preprocess value)
| _, _ =>
let newFVarId ← mkFreshFVarId
@ -259,14 +259,14 @@ partial def process : ClosureM Unit := do
pushFVarArg (mkFVar fvarId)
process
| .ldecl _ _ userName type val _ _ =>
let zetaFVarIds ← getZetaFVarIds
if !zetaFVarIds.contains fvarId then
let zetaDeltaFVarIds ← getZetaDeltaFVarIds
if !zetaDeltaFVarIds.contains fvarId then
/- Non-dependent let-decl
Recall that if `fvarId` is in `zetaFVarIds`, then we zeta-expanded it
Recall that if `fvarId` is in `zetaDeltaFVarIds`, then we zetaDelta-expanded it
during type checking (see `check` at `collectExpr`).
Our type checker may zeta-expand declarations that are not needed, but this
Our type checker may zetaDelta-expand declarations that are not needed, but this
check is conservative, and seems to work well in practice. -/
pushLocalDecl newFVarId userName type
pushFVarArg (mkFVar fvarId)
@ -315,15 +315,15 @@ structure MkValueTypeClosureResult where
exprArgs : Array Expr
def mkValueTypeClosureAux (type : Expr) (value : Expr) : ClosureM (Expr × Expr) := do
resetZetaFVarIds
withTrackingZeta do
resetZetaDeltaFVarIds
withTrackingZetaDelta do
let type ← collectExpr type
let value ← collectExpr value
process
pure (type, value)
def mkValueTypeClosure (type : Expr) (value : Expr) (zeta : Bool) : MetaM MkValueTypeClosureResult := do
let ((type, value), s) ← ((mkValueTypeClosureAux type value).run { zeta := zeta }).run {}
def mkValueTypeClosure (type : Expr) (value : Expr) (zetaDelta : Bool) : MetaM MkValueTypeClosureResult := do
let ((type, value), s) ← ((mkValueTypeClosureAux type value).run { zetaDelta }).run {}
let newLocalDecls := s.newLocalDecls.reverse ++ s.newLocalDeclsForMVars
let newLetDecls := s.newLetDecls.reverse
let type := mkForall newLocalDecls (mkForall newLetDecls type)
@ -344,8 +344,8 @@ end Closure
A "closure" is computed, and a term of the form `name.{u_1 ... u_n} t_1 ... t_m` is
returned where `u_i`s are universe parameters and metavariables `type` and `value` depend on,
and `t_j`s are free and meta variables `type` and `value` depend on. -/
def mkAuxDefinition (name : Name) (type : Expr) (value : Expr) (zeta : Bool := false) (compile : Bool := true) : MetaM Expr := do
let result ← Closure.mkValueTypeClosure type value zeta
def mkAuxDefinition (name : Name) (type : Expr) (value : Expr) (zetaDelta : Bool := false) (compile : Bool := true) : MetaM Expr := do
let result ← Closure.mkValueTypeClosure type value zetaDelta
let env ← getEnv
let decl := Declaration.defnDecl {
name := name
@ -361,16 +361,16 @@ def mkAuxDefinition (name : Name) (type : Expr) (value : Expr) (zeta : Bool := f
return mkAppN (mkConst name result.levelArgs.toList) result.exprArgs
/-- Similar to `mkAuxDefinition`, but infers the type of `value`. -/
def mkAuxDefinitionFor (name : Name) (value : Expr) (zeta : Bool := false) : MetaM Expr := do
def mkAuxDefinitionFor (name : Name) (value : Expr) (zetaDelta : Bool := false) : MetaM Expr := do
let type ← inferType value
let type := type.headBeta
mkAuxDefinition name type value (zeta := zeta)
mkAuxDefinition name type value (zetaDelta := zetaDelta)
/--
Create an auxiliary theorem with the given name, type and value. It is similar to `mkAuxDefinition`.
-/
def mkAuxTheorem (name : Name) (type : Expr) (value : Expr) (zeta : Bool := false) : MetaM Expr := do
let result ← Closure.mkValueTypeClosure type value zeta
def mkAuxTheorem (name : Name) (type : Expr) (value : Expr) (zetaDelta : Bool := false) : MetaM Expr := do
let result ← Closure.mkValueTypeClosure type value zetaDelta
let env ← getEnv
let decl :=
if env.hasUnsafe result.type || env.hasUnsafe result.value then
@ -396,9 +396,9 @@ def mkAuxTheorem (name : Name) (type : Expr) (value : Expr) (zeta : Bool := fals
/--
Similar to `mkAuxTheorem`, but infers the type of `value`.
-/
def mkAuxTheoremFor (name : Name) (value : Expr) (zeta : Bool := false) : MetaM Expr := do
def mkAuxTheoremFor (name : Name) (value : Expr) (zetaDelta : Bool := false) : MetaM Expr := do
let type ← inferType value
let type := type.headBeta
mkAuxTheorem name type value zeta
mkAuxTheorem name type value zetaDelta
end Lean.Meta

View file

@ -682,7 +682,7 @@ builtin_initialize matcherExt : EnvExtension (PHashMap (Expr × Bool) Name) ←
def mkMatcherAuxDefinition (name : Name) (type : Expr) (value : Expr) : MetaM (Expr × Option (MatcherInfo → MetaM Unit)) := do
trace[Meta.Match.debug] "{name} : {type} := {value}"
let compile := bootstrap.genMatcherCode.get (← getOptions)
let result ← Closure.mkValueTypeClosure type value (zeta := false)
let result ← Closure.mkValueTypeClosure type value (zetaDelta := false)
let env ← getEnv
let mkMatcherConst name :=
mkAppN (mkConst name result.levelArgs.toList) result.exprArgs

View file

@ -76,7 +76,7 @@ private def reduceProjFn? (e : Expr) : SimpM (Option Expr) := do
reduceProjCont? (← unfoldDefinition? e)
private def reduceFVar (cfg : Config) (thms : SimpTheoremsArray) (e : Expr) : MetaM Expr := do
if cfg.zeta || thms.isLetDeclToUnfold e.fvarId! then
if cfg.zetaDelta || thms.isLetDeclToUnfold e.fvarId! then
match (← getFVarLocalDecl e).value? with
| some v => return v
| none => return e

View file

@ -165,7 +165,7 @@ structure SimpTheorems where
lemmaNames : PHashSet Origin := {}
/--
Constants (and let-declaration `FVarId`) to unfold.
When `zeta := false`, the simplifier will expand a let-declaration if it is in this set.
When `zetaDelta := false`, the simplifier will expand a let-declaration if it is in this set.
-/
toUnfold : PHashSet Name := {}
erased : PHashSet Origin := {}

View file

@ -14,7 +14,7 @@ namespace Simp
/-- The result of simplifying some expression `e`. -/
structure Result where
/-- The simplified version of `e` -/
/-- The simplified version of `e` -/
expr : Expr
/-- A proof that `$e = $expr`, where the simplified expression is on the RHS.
If `none`, the proof is assumed to be `refl`. -/
@ -483,6 +483,8 @@ def tryAutoCongrTheorem? (e : Expr) : SimpM (Option Result) := do
Return a WHNF configuration for retrieving `[simp]` from the discrimination tree.
If user has disabled `zeta` and/or `beta` reduction in the simplifier, we must also
disable them when retrieving lemmas from discrimination tree. See issues: #2669 and #2281
-- TODO: zetaDelta
-/
def getDtConfig (cfg : Config) : WhnfCoreConfig :=
match cfg.beta, cfg.zeta with

View file

@ -134,6 +134,7 @@ partial def transform {m} [Monad m] [MonadLiftT MetaM m] [MonadControlT MetaM m]
| _ => visitPost e
visit input |>.run
-- TODO: add options to distinguish zeta and zetaDelta reduction
def zetaReduce (e : Expr) : MetaM Expr := do
let pre (e : Expr) : MetaM TransformStep := do
match e with

View file

@ -371,9 +371,9 @@ structure WhnfCoreConfig where
match decl with
| .cdecl .. => return e
| .ldecl (value := v) .. =>
unless config.zeta do return e
if (← getConfig).trackZeta then
modify fun s => { s with zetaFVarIds := s.zetaFVarIds.insert fvarId }
unless config.zetaDelta do return e
if (← getConfig).trackZetaDelta then
modify fun s => { s with zetaDeltaFVarIds := s.zetaDeltaFVarIds.insert fvarId }
whnfEasyCases v k config
| .mvar mvarId =>
match (← getExprMVarAssignment? mvarId) with