Skip to content

Commit ed053df

Browse files
fpvandoornEdAyers
andcommitted
feat: replaceRec (#129)
* Add a more flexible version of `Expr.replace` Co-authored-by: E.W.Ayers <edward.ayers@outlook.com>
1 parent 977e6e1 commit ed053df

File tree

4 files changed

+110
-0
lines changed

4 files changed

+110
-0
lines changed

Mathlib.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import Mathlib.Init.Logic
4343
import Mathlib.Init.Set
4444
import Mathlib.Init.SetNotation
4545
import Mathlib.Lean.Expr
46+
import Mathlib.Lean.Expr.ReplaceRec
47+
import Mathlib.Lean.Expr.Traverse
4648
import Mathlib.Lean.LocalContext
4749
import Mathlib.Logic.Basic
4850
import Mathlib.Logic.Function.Basic

Mathlib/Lean/Expr/ReplaceRec.lean

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/-
2+
Copyright (c) 2019 Robert Y. Lewis. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Mario Carneiro, Simon Hudon, Scott Morrison, Keeley Hoek, Robert Y. Lewis, Floris van Doorn, E.W.Ayers
5+
-/
6+
import Lean
7+
import Lean.Meta
8+
import Mathlib.Util.TermUnsafe
9+
import Mathlib.Lean.Expr.Traverse
10+
import Mathlib.Util.MemoFix
11+
namespace Lean.Expr
12+
/-!
13+
# ReplaceRec
14+
15+
We define a more flexible version of `Expr.replace` where we can use recursive calls even when
16+
replacing a subexpression. We completely mimic the implementation of `Expr.replace`. -/
17+
18+
/-- A version of `Expr.replace` where the replacement function is available to the function `f?`.
19+
20+
`replaceRec f? e` will call `f? r e` where `r = replaceRec f?`.
21+
If `f? r e = none` then `r` will be called on each immediate subexpression of `e` and reassembled.
22+
If it is `some x`, traversal terminates and `x` is returned.
23+
If you wish to recursively replace things in the implementation of `f?`, you can apply `r`.
24+
25+
The function is also memoised, which means that if the
26+
same expression (by reference) is encountered the cached replacement is used. -/
27+
def replaceRec (f? : (Expr → Expr) → Expr → Option Expr) : Expr → Expr :=
28+
memoFix fun r e =>
29+
match f? r e with
30+
| some x => x
31+
| none => traverseChildren (M := Id) r e
32+
33+
/-- A version of `Expr.replace` where we can use recursive calls even if we replace a subexpression.
34+
When reaching a subexpression `e` we call `traversal e` to see if we want to do anything with this
35+
expression. If `traversal e = none` we proceed to the children of `e`. If
36+
`traversal e = some (#[e₁, ..., eₙ], g)`, we first recursively apply this function to
37+
`#[e₁, ..., eₙ]` to get new expressions `#[f₁, ..., fₙ]`.
38+
Then we replace `e` by `g [f₁, ..., fₙ]`.
39+
40+
Important: In order for this function to terminate, the `[e₁, ..., eₙ]` must all be smaller than
41+
`e` according to some measure (and this measure must also be strictly decreasing on the w.r.t.
42+
the structural subterm relation).
43+
-/
44+
def replaceRecTraversal (traversal : Expr → Option (Array Expr × (Array Expr → Expr))) : Expr → Expr :=
45+
replaceRec fun r e =>
46+
match traversal e with
47+
| none => none
48+
| some (get, set) => some <| set <| .map r <| get
49+
50+
end Lean.Expr

Mathlib/Lean/Expr/Traverse.lean

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/-
2+
Copyright (c) 2022 E.W.Ayers. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: E.W.Ayers
5+
-/
6+
7+
import Lean
8+
9+
/-!
10+
# Traversal functions for expressions.
11+
-/
12+
13+
namespace Lean.Expr
14+
15+
/-- Maps `f` on each immediate child of the given expression. -/
16+
def traverseChildren [Applicative M] (f : Expr → M Expr) : Expr → M Expr
17+
| e@(forallE _ d b _) => pure e.updateForallE! <*> f d <*> f b
18+
| e@(lam _ d b _) => pure e.updateLambdaE! <*> f d <*> f b
19+
| e@(mdata _ b _) => e.updateMData! <$> f b
20+
| e@(letE _ t v b _) => pure e.updateLet! <*> f t <*> f v <*> f b
21+
| e@(app l r _) => pure e.updateApp! <*> f l <*> f r
22+
| e@(proj _ _ b _) => e.updateProj! <$> f b
23+
| e => pure e
24+
25+
end Lean.Expr

test/Expr.lean

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Mathlib.Lean.Expr.ReplaceRec
2+
import Mathlib.Init.Data.Nat.Basic
3+
4+
open Lean Meta Elab
5+
6+
section replaceRec
7+
/-! Test the implementation of `Expr.replaceRec` -/
8+
9+
/-- Reorder the last two arguments of every function in the expression.
10+
(The resulting term will generally not be a type-correct) -/
11+
def reorderLastArguments : Expr → Expr :=
12+
Expr.replaceRecTraversal λ e =>
13+
let n := e.getAppNumArgs
14+
if n ≥ 2 then
15+
some (e.getAppArgs, λ es => mkAppN e.getAppFn $ es.swap! (n - 1) (n - 2)) else
16+
none
17+
18+
def foo (f : ℕ → ℕ → ℕ) (n₁ n₂ n₃ n₄ : ℕ) : ℕ := f (f n₁ n₂) (f n₃ n₄)
19+
def bar (f : ℕ → ℕ → ℕ) (n₁ n₂ n₃ n₄ : ℕ) : ℕ := f (f n₄ n₃) (f n₂ n₁)
20+
21+
#eval show TermElabM _ from do
22+
let d ← getConstInfo `foo
23+
let e := d.value!
24+
logInfo m!"before: {e}"
25+
let e := reorderLastArguments e
26+
logInfo m!"after: {e}"
27+
let t ← Meta.inferType e
28+
logInfo m!"new type: {t}"
29+
let d ← getConstInfo `bar
30+
logInfo m!"after: {d.value!}"
31+
guard $ e == d.value!
32+
33+
end replaceRec

0 commit comments

Comments
 (0)