Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: how am I getting infinite recursion?


Joe Palermo (Apr 29 2021 at 19:17):

Hi,

I'm learning to write tactics to traverse the structure of an expr and extract certain bits. It seems that a tactic I wrote is leading to the error: "excessive memory consumption detected at 'vm' (potential solution: increase memory consumption threshold)". This sounds to me like it's running a bottomless recursion and it runs out of memory, but looking at my code I can't see how it would fail to reach the base case which ends the recursion. Perhaps what I wrote doesn't mean what I think it does. I would appreciate some guidance on how to fix it. Thanks!

namespace expr
open tactic

meta def extract_bindings (e: expr) : expr :=
  match e with
    | lam var_name b_info var_type body := lam var_name b_info var_type (extract_bindings body)
    | pi var_name b_info var_type body := pi var_name b_info var_type (extract_bindings body)
    | _ := (@expr.var tt 0)
  end

-- this one is fine
run_cmd tactic.trace (extract_bindings (@expr.var tt 0))

-- this one produces the error
run_cmd tactic.trace (extract_bindings
 (expr.lam (mk_simple_name "α") binder_info.implicit (expr.sort $ level.succ level.zero)
  (expr.lam (mk_simple_name "β") binder_info.implicit (expr.sort $ level.succ level.zero) (@expr.var tt 0))
 ))

end expr

Yakov Pechersky (Apr 29 2021 at 19:39):

Can you express your function using expr.fold?

Yakov Pechersky (Apr 29 2021 at 19:40):

If your thing truly recurses properly, you wouldn't need to write is as a meta def, just def

Joe Palermo (Apr 29 2021 at 21:18):

Thanks for the suggestion. I'll try to rewrite it with fold

Jannis Limperg (Apr 29 2021 at 23:56):

Okay this is a weird one. Here's a working version:

namespace expr
open tactic

meta def extract_bindings : expr  expr := λ e,
  match e with
    | lam var_name b_info var_type body := lam var_name b_info var_type (extract_bindings body)
    | pi var_name b_info var_type body := pi var_name b_info var_type (extract_bindings body)
    | _ := (@expr.var tt 0)
  end

run_cmd tactic.trace (extract_bindings (@expr.var tt 0))

run_cmd trace $
    extract_bindings $
      lam `α binder_info.default (expr.sort level.zero) $
      lam `β binder_info.default (expr.sort level.zero) $
      var 0

end expr

The difference is in the definition of extract_bindings. Arguments before the colon, like e in your version, are treated as parameters in definitions, meaning they are fixed in recursive calls. But I don't understand why Lean even accepted your definition in the first place. Usually, extract_bindings should have type expr in the body of its definition (since the e is fixed), so extract_bindings body should be a type error.

@Gabriel Ebner can you tell what's going on here?

Yakov Pechersky said:

If your thing truly recurses properly, you wouldn't need to write is as a meta def, just def

In this case, expr is already meta, so the definition must be meta as well.

Gabriel Ebner (Apr 30 2021 at 07:45):

Jannis Limperg said:

But I don't understand why Lean even accepted your definition in the first place. Usually, extract_bindings should have type expr in the body of its definition (since the e is fixed), so extract_bindings body should be a type error.

docs#expr.has_coe_to_fun :smile:

Jannis Limperg (Apr 30 2021 at 11:17):

Ha! That explains the infinite recursion. Thanks!

Joe Palermo (Apr 30 2021 at 14:59):

Wow, I didn't expect the problem to be in the function signature...

Joe Palermo (Apr 30 2021 at 15:11):

So (extract_bindings body) was being expanded to (expr.app extract_bindings body). Also extract_bindings had an implicit argument (causing infinite recursion), but has_coe_to_fun prevented a type error from showing up on (extract_bindings body) because of the extra expr.app...

Joe Palermo (Apr 30 2021 at 15:15):

I hope I'm understanding correctly here.

How can you prevent has_coe_to_fun from doing that? When does has_coe_to_fun apply? Was the change to the function signature enough to inhibit it? Because I don't see an extra expr.app in the resulting expression now that the program works

Eric Wieser (Apr 30 2021 at 15:26):

local attribute [-instance] expr.has_coe_to_fun

Jannis Limperg (Apr 30 2021 at 15:52):

Your understanding seems correct.

Joe Palermo said:

When does has_coe_to_fun apply?

Whenever Lean sees an application x y with x : T and T not a function type, it tries to look for an instance of has_coe_to_fun T. (There are probably nasty details to consider, but that's the idea.) For expr specifically, this allows you to write f x with f, x : expr and f presumably of function type, which is occasionally handy.

Was the change to the function signature enough to inhibit it? Because I don't see an extra expr.app in the resulting expression now that the program works

With the change to the function signature, extract_bindings has type expr -> expr in the body of its definition, so it's already a function and the has_coe_to_fun business doesn't apply.


Last updated: Dec 20 2023 at 11:08 UTC