Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: expr.mfold with branch pruning
Eric Wieser (Mar 01 2021 at 08:32):
Is there a variant of expr.mfold
that allows me to stop recursing into a particular subexpression?
Eric Wieser (Mar 01 2021 at 09:00):
I guess I'm asking for this:
meta def expr.pruned_mfold {α : Type} {m : Type → Type} [monad m]
(f : expr → α → m (α × bool)) : ∀ (init : α) (e : expr), m α
| init e@(expr.var _) := prod.fst <$> f e init
| init e@(expr.sort _) := prod.fst <$> f e init
| init e@(expr.const _ _) := prod.fst <$> f e init
| init e@(expr.mvar unique pretty type) := do
(val, prune) ← f e init,
if prune then return val
else expr.pruned_mfold val type
| init e@(expr.local_const unique pretty bi type) := do
(val, prune) ← f e init,
if prune then return val
else expr.pruned_mfold val type
| init e@(expr.app fn x) := do
(val, prune) ← f e init,
if prune then return val
else do
val ← expr.pruned_mfold val fn,
expr.pruned_mfold val x
| init e@(expr.lam var_name bi var_type body) := do
(val, prune) ← f e init,
if prune then return val
else do
val ← expr.pruned_mfold val var_type,
expr.pruned_mfold val body
| init e@(expr.pi var_name bi var_type body) := do
(val, prune) ← f e init,
if prune then return val
else do
val ← expr.pruned_mfold val var_type,
expr.pruned_mfold val body
| init e@(expr.elet var_name type assignment body) := do
(val, prune) ← f e init,
if prune then return val
else do
val ← expr.pruned_mfold val type,
val ← expr.pruned_mfold val assignment,
expr.pruned_mfold val body
| init e@(expr.macro _ ls) := do
(val, prune) ← f e init,
if prune then return val
else do
ls.mfoldl expr.pruned_mfold val
Jannis Limperg (Mar 01 2021 at 13:45):
I'm not aware of an existing implementation of this. I think you can implement it in terms of regular mfold
:
meta def expr.pruned_mfold {α : Type} {m : Type → Type} [monad m]
(f : expr → α → m (α × bool)) (init : α) (e : expr) : m α := do
(acc, _) ← e.mfold (init, ff) $ λ e' _ ⟨acc, prune⟩,
if prune
then pure (acc, tt)
else f e' acc,
pure acc
However, I don't think this short-circuits properly, so it'll continue to recurse after you've set prune
to tt
. However, it'll only do a trivial amount of work for each subexpression.
Eric Wieser (Mar 01 2021 at 13:47):
That doesn't prune branches though, that prunes the entire recursion
Eric Wieser (Mar 01 2021 at 13:48):
I want folding over g (f (f a)) (h (f b))
to run on g ...
, f (f a)
, h (f b)
and f b
, but not visit b
or (f a)
as those are children of a subexpression that has been "pruned"
Gabriel Ebner (Mar 01 2021 at 13:52):
Note that the big advantage of expr.fold
(which is used to implement expr.mfold
) is that it doesn't visit identical subexpressions twice.
Gabriel Ebner (Mar 01 2021 at 13:53):
This tastes a bit of #xy, what are you actually trying to do?
Eric Wieser (Mar 01 2021 at 13:57):
This looks like a shorter implementation of pruned_mfold
:
/-- get all child expressions of an expression -/
meta def expr.nested_exprs : ∀ (e : expr), list expr
| (expr.var _) := []
| (expr.sort _) := []
| (expr.const _ _) := []
| (expr.mvar unique pretty type) := [type]
| (expr.local_const unique pretty bi type) := [type]
| (expr.app fn x) := [fn, x]
| (expr.lam var_name bi var_type body) := [var_type, body]
| (expr.pi var_name bi var_type body) := [var_type, body]
| (expr.elet var_name type assignment body) := [type, assignment, body]
| (expr.macro _ ls) := ls
/-- mfold over an expression tree, without visiting branches for which `(f e val).snd` is true -/
meta def expr.pruned_mfold {α : Type} {m : Type → Type} [monad m]
(f : expr → α → m (α × bool)) : ∀ (init : α) (e : expr), m α :=
λ init e, do
(val, prune) ← f e init,
ff ← pure prune | return val,
list.mfoldl expr.pruned_mfold val e.nested_exprs
Eric Wieser (Mar 01 2021 at 13:58):
what are you actually trying to do?
Find decidable terms, without recursing into the decidable terms that they might be produced by
Eric Wieser (Mar 01 2021 at 14:00):
I don't know whether I actually need to do that pruning yet - CI seems stuck on #6485, and probably I just need to wait for the queue to clear
Jannis Limperg (Mar 01 2021 at 14:03):
Eric Wieser said:
That doesn't prune branches though, that prunes the entire recursion
I see. I'm not sure whether there's an mfold
/fold
incantation that doesn't have this problem, so you might indeed have to roll your own fold.
Gabriel Ebner (Mar 01 2021 at 14:23):
I don't think expr.mfold is a good idea here. When traversing lambdas/pis/lets, you probably want to instantiate the de Bruijn variables with local constants. Otherwise infer_type
, mk_instance
, etc. won't work.
Eric Wieser (Mar 01 2021 at 14:25):
Is it safe if I've done open_pis
first?
Gabriel Ebner (Mar 01 2021 at 14:29):
open_pis
will only do the outermost layer. E.g. in Π a b c, foo (λ x, irrational.decidable (x+1))
, the x+1
will actually be #0+1
if you traverse it via expr.fold
.
Eric Wieser (Mar 01 2021 at 14:31):
Ah, got it
Eric Wieser (Mar 01 2021 at 14:32):
That explains why infer_type
failed on me sometimes
Eric Wieser (Mar 01 2021 at 14:32):
But probably my linter is not well thought out for expressions inside binders anyway
Eric Wieser (Mar 01 2021 at 14:32):
So best to ignore those for now
Jesse Michael Han (Mar 02 2021 at 22:39):
i use something like this to extract data from proof terms in lean-step-public
Last updated: Dec 20 2023 at 11:08 UTC