lean4-htt/src/Lean/Meta/PProdN.lean
Paul Reichert 98e4b2882f
refactor: migrate to new ranges (#8841)
This PR migrates usages of `Std.Range` to the new polymorphic ranges.

This PR unfortunately increases the transitive imports for
frequently-used parts of `Init` because the ranges now rely on iterators
in order to provide their functionality for types other than `Nat`.
However, iteration over ranges in compiled code is as efficient as
before in the examples I checked. This is because of a special
`IteratorLoop` implementation provided in the PR for this purpose.

There were two issues that were uncovered during migration:

* In `IndPredBelow.lean`, migrating the last remaining range causes
`compilerTest1.lean` to break. I have minimized the issue and came to
the conclusion it's a compiler bug. Therefore, I have not replaced said
old range usage yet (see #9186).
* In `BRecOn.lean`, we are publicly importing the ranges. Making this
import private should theoretically work, but there seems to be a
problem with the module system, causing the build to panic later in
`Init.Data.Grind.Poly` (see #9185).
* In `FuzzyMatching.lean`, inlining fails with the new ranges, which
would have led to significant slowdown. Therefore, I have not migrated
this file either.
2025-07-07 12:41:53 +00:00

200 lines
6.5 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joachim Breitner
-/
prelude
import Lean.Meta.InferType
import Lean.Meta.Transform
/-!
This module provides functions to pack and unpack values using nested `PProd` or `And`,
as used in the `.below` construction, in the `.brecOn` construction for mutual recursion and
and the `mutual_induct` construction.
It uses `And` (equivalent to `PProd.{0}` when possible).
The nesting is `t₁ ×' (t₂ ×' t₃)`, not `t₁ ×' (t₂ ×' (t₃ ×' PUnit))`. This is more readable,
slightly shorter, and means that the packing is the identity if `n=1`, which we rely on in some
places. It comes at the expense that hat projection needs to know `n`.
Packing an empty list uses `True` or `PUnit` depending on the given `lvl`.
Also see `Lean.Meta.ArgsPacker` for a similar module for `PSigma` and `PSum`, used by well-founded recursion.
-/
namespace Lean.Meta
/-- Given types `t₁` and `t₂`, produces `t₁ ×' t₂` (or `t₁ ∧ t₂` if the universes allow) -/
def mkPProd (e1 e2 : Expr) : MetaM Expr := do
let lvl1 ← getLevel e1
let lvl2 ← getLevel e2
if lvl1 matches .zero && lvl2 matches .zero then
return mkApp2 (.const `And []) e1 e2
else
return mkApp2 (.const ``PProd [lvl1, lvl2]) e1 e2
/-- Given values of typs `t₁` and `t₂`, produces value of type `t₁ ×' t₂` (or `t₁ ∧ t₂` if the universes allow) -/
def mkPProdMk (e1 e2 : Expr) : MetaM Expr := do
let t1 ← inferType e1
let t2 ← inferType e2
let lvl1 ← getLevel t1
let lvl2 ← getLevel t2
if lvl1 matches .zero && lvl2 matches .zero then
return mkApp4 (.const ``And.intro []) t1 t2 e1 e2
else
return mkApp4 (.const ``PProd.mk [lvl1, lvl2]) t1 t2 e1 e2
/-- `PProd.fst` or `And.left` (using `.proj`) -/
def mkPProdFst (t e : Expr) : Expr :=
match_expr t with
| PProd _ _ => .proj ``PProd 0 e
| And _ _ => .proj ``And 0 e
| _ => panic! s!"mkPProdFst: cannot handle {e}\nof type {t}"
/-- `PProd.fst` or `And.left` (using `.proj`), inferring the type of `e` -/
def mkPProdFstM (e : Expr) : MetaM Expr := do
return mkPProdFst (← whnf (← inferType e)) e
private def mkTypeSnd (t : Expr) : Expr :=
match_expr t with
| PProd _ t => t
| And _ t => t
| _ => panic! s!"mkTypeSnd: cannot handle type {t}"
/-- `PProd.snd` or `And.right` (using `.proj`) -/
def mkPProdSnd (t e : Expr) : Expr :=
match_expr t with
| PProd _ _ => .proj ``PProd 1 e
| And _ _ => .proj ``And 1 e
| _ => panic! s!"mkPProdSnd: cannot handle {e}\nof type {t}"
/-- `PProd.snd` or `And.right` (using `.proj`), inferring the type of `e` -/
def mkPProdSndM (e : Expr) : MetaM Expr := do
return mkPProdSnd (← whnf (← inferType e)) e
namespace PProdN
/--
Essentially a form of `foldrM1`. Underlies `pack` and `mk`, and is useful to construct proofs
that should follow the structure of `pack` and `mk` (e.g. admissibility proofs)
-/
def genMk {α : Type _} [Inhabited α] (mk : αα → MetaM α) (xs : Array α) : MetaM α :=
assert! !xs.isEmpty
xs.pop.foldrM mk xs.back!
/-- Given types `tᵢ`, produces `t₁ ×' t₂ ×' t₃` -/
def pack (lvl : Level) (xs : Array Expr) : MetaM Expr := do
if xs.size = 0 then
if lvl matches .zero then return .const ``True []
else return .const ``PUnit [lvl]
genMk mkPProd xs
/-- Given values `xᵢ` of type `tᵢ`, produces value of type `t₁ ×' t₂ ×' t₃` -/
def mk (lvl : Level) (xs : Array Expr) : MetaM Expr := do
if xs.size = 0 then
if lvl matches .zero then return .const ``True.intro []
else return .const ``PUnit.unit [lvl]
genMk mkPProdMk xs
/-- Given a value `e` of type `t = t₁ ×' … ×' tᵢ ×' … ×' tₙ`, return a value of type `tᵢ` -/
def proj (n i : Nat) (t e : Expr) : Expr := Id.run <| do
unless i < n do panic! "PProdN.proj: {i} not less than {n}"
let mut t := t
let mut value := e
for _ in *...i do
value := mkPProdSnd t value
t := mkTypeSnd t
if i+1 < n then
mkPProdFst t value
else
value
/-- Given a value `e` of type `t = t₁ ×' … ×' tᵢ ×' … ×' tₙ`, return the values of type `tᵢ` -/
def projs (n : Nat) (t e : Expr) : Array Expr :=
Array.ofFn (n := n) fun i => PProdN.proj n i t e
/-- Given a value of type `t₁ ×' … ×' tᵢ ×' … ×' tₙ`, return a value of type `tᵢ` -/
def projM (n i : Nat) (e : Expr) : MetaM Expr := do
let mut value := e
for _ in *...i do
value ← mkPProdSndM value
if i+1 < n then
mkPProdFstM value
else
pure value
/--
Packs multiple type-forming lambda expressions taking the same parameters using `PProd`.
The parameter `type` is the common type of the these expressions
For example
```
packLambdas (Nat → Sort u) #[(fun (n : Nat) => Nat), (fun (n : Nat) => Fin n -> Fin n )]
```
will return
```
fun (n : Nat) => (Nat ×' (Fin n → Fin n))
```
It is the identity if `es.size = 1`.
It returns a dummy motive `(xs : ) → PUnit` or `(xs : … ) → True` if no expressions are given.
(this is the reason we need the expected type in the `type` parameter).
-/
def packLambdas (type : Expr) (es : Array Expr) : MetaM Expr := do
if h : es.size = 1 then
return es[0]
forallTelescope type fun xs sort => do
assert! sort.isSort
-- NB: Use beta, not instantiateLambda; when constructing the belowDict below
-- we pass `C`, a plain FVar, here
let es' := es.map (·.beta xs)
let packed ← PProdN.pack sort.sortLevel! es'
mkLambdaFVars xs packed
/--
The value analogue to `PProdN.packLambdas`.
It is the identity if `es.size = 1`.
-/
def mkLambdas (type : Expr) (es : Array Expr) : MetaM Expr := do
if h : es.size = 1 then
return es[0]
forallTelescope type fun xs body => do
let lvl ← getLevel body
let es' := es.map (·.beta xs)
let packed ← PProdN.mk lvl es'
mkLambdaFVars xs packed
/-- Strips topplevel `PProd` and `And` projections -/
def stripProjs (e : Expr) : Expr :=
match e with
| .proj ``PProd _ e' => stripProjs e'
| .proj ``And _ e' => stripProjs e'
| e => e
/--
Reduces `⟨x,y⟩.1` redexes for `PProd` and `And`
-/
def reduceProjs (e : Expr) : CoreM Expr := do
Core.transform e (post := fun e => do
if e.isProj then
if e.projExpr!.isAppOfArity ``PProd.mk 4 || e.projExpr!.isAppOfArity ``And.intro 2 then
if e.projIdx! == 0 then
return .continue e.projExpr!.appFn!.appArg!
else
return .continue e.projExpr!.appArg!
return .continue
)
end PProdN
end Lean.Meta