Zulip Chat Archive

Stream: lean4

Topic: Mutual Recursion


Chiyoku (Apr 05 2022 at 18:56):

Hi, i'm trying to run this code with a mutual recursion but I'm getting a strange error about the tactic assumption

def Ident : Type := String
  deriving BEq

inductive Term where
  | var : Ident  Term
  | abs : Ident  Term  Term
  | app : Term   Term  Term

  | zero : Term
  | succ : Term  Term

  | case : Term  Term  Ident  Term  Term
  | fixpoint : Ident  Term  Term

open Term

mutual
  def boundSubs (from_ : Ident) (to: Term) (app: Ident  Term  Term) (binder: Ident) (body: Term) : Term :=
    if binder == from_
      then app binder body
      else app binder (substitute from_ to body)

  def substitute (from_ : Ident) (to : Term) : Term  Term
    | var x => if x == from_ then to else var x
    | app a b => app (substitute from_ to a) (substitute from_ to b)
    | zero    => zero
    | succ e  => succ (substitute from_ to e)
    | case cond onZ name onS =>
      let onCase := (case (substitute from_ to cond) (substitute from_ to onZ))
      boundSubs from_ to onCase name onS
    | abs param body => boundSubs from_ to abs param body
    | fixpoint n t => boundSubs from_ to fixpoint n t
end

termination_by
  substitute _ _ expr => sizeOf expr
  boundSubs _ _ _ _ f => sizeOf f

Error

tactic 'assumption' failed,
app : Ident  Term  Term
binder : Ident
body : Term
 sizeOf body < sizeOf body

Leonardo de Moura (Apr 05 2022 at 18:59):

You should be using an old Lean 4 binary. We have improved the error message. Could you please get the latest nightly build and try again?

Leonardo de Moura (Apr 05 2022 at 19:04):

Note that boundSubs has a recursive substitute application and the argument body is the same one taken by boundSubs. In the well-founded relation you selected, this application is not "smaller". You can use a slightly different well-founded relation.

termination_by
  boundSubs _ _ _ _ f => (sizeOf f, 1)
  substitute _ _ expr => (sizeOf expr, 0)

Leonardo de Moura (Apr 05 2022 at 19:06):

BTW, the termination_by notation names arguments from right to left. So, you don't need all the _s.

termination_by
  boundSubs f => (sizeOf f, 1)
  substitute expr => (sizeOf expr, 0)

sgcs (Mar 27 2023 at 22:39):

I'm running into an error when doing mutual recursion:

fail to show termination for
  normal_does_not_reduce
  neutral_does_not_reduce
with errors
structural recursion does not handle mutually recursive functions

failed to prove termination, use `termination_by` to specify a well-founded relation

Essentially I'm doing a very dumbed down lambda calculus and I've defined mutually inductive Normal and Neutral data types that are indexed by well-typed expressions. The code is the following:

mutual

private theorem normal_does_not_reduce
  {n : Nat} {Γ : Context n} {e₁ e₂ : Expr n} {τ : Ty} {t₁ : Γ  e₁ : τ} {t₂ : Γ  e₂ : τ} :
  Normal t₁  (t₁  t₂)  Empty
  | .neutral neut, tr => neutral_does_not_reduce neut tr

private theorem neutral_does_not_reduce
  {n : Nat} {Γ : Context n} {e₁ e₂ : Expr n} {τ : Ty} {t₁ : Γ  e₁ : τ} {t₂ : Γ  e₂ : τ} :
  Neutral t₁  (t₁  t₂)  Empty
  | .plus₁ neut₁ _, .ξ_plus₁ tr₁ => neutral_does_not_reduce neut₁ tr₁
  | .plus₁ _ norm₂, .ξ_plus₂ norm₁ tr₂ => normal_does_not_reduce norm₂ tr₂
  | .plus₂ v₁ _, .ξ_plus₁ tr₁ => v₁.does_not_reduce tr₁
  | .plus₂ _ neut₂, .ξ_plus₂ norm₁ tr₂ => neutral_does_not_reduce neut₂ tr₂

end

I spent a good amount of time trying to come up with a MWE, but I didn't succeed (see https://pastebin.com/0pxHUrmw,). It doesn't give the same error for some reason, but that's essentially what I have (except I have contexts and De Bruijn indices added).

I understand that without a MWE, it may be hard to help, but was hoping someone may have a quick tip to help resolve the issue.

Heather Macbeth (Mar 28 2023 at 00:29):

@sgcs This is probably the part of the error message you should listen to:

failed to prove termination, use `termination_by` to specify a well-founded relation

Heather Macbeth (Mar 28 2023 at 00:30):

So your struture will be (fill in the XXX)

mutual

private theorem normal_does_not_reduce
  {n : Nat} {Γ : Context n} {e₁ e₂ : Expr n} {τ : Ty} {t₁ : Γ  e₁ : τ} {t₂ : Γ  e₂ : τ} :
  Normal t₁  (t₁  t₂)  Empty
  | .neutral neut, tr => neutral_does_not_reduce neut tr

private theorem neutral_does_not_reduce
  {n : Nat} {Γ : Context n} {e₁ e₂ : Expr n} {τ : Ty} {t₁ : Γ  e₁ : τ} {t₂ : Γ  e₂ : τ} :
  Neutral t₁  (t₁  t₂)  Empty
  | .plus₁ neut₁ _, .ξ_plus₁ tr₁ => neutral_does_not_reduce neut₁ tr₁
  | .plus₁ _ norm₂, .ξ_plus₂ norm₁ tr₂ => normal_does_not_reduce norm₂ tr₂
  | .plus₂ v₁ _, .ξ_plus₁ tr₁ => v₁.does_not_reduce tr₁
  | .plus₂ _ neut₂, .ξ_plus₂ norm₁ tr₂ => neutral_does_not_reduce neut₂ tr₂

end
termination_by normal_does_not_reduce => XXX ; neutral_does_not_reduce => XXX

Heather Macbeth (Mar 28 2023 at 00:31):

See some of the later examples at this link:
https://leanprover.github.io/theorem_proving_in_lean4/induction_and_recursion.html#well-founded-recursion-and-induction

sgcs (Mar 28 2023 at 04:26):

I had given that a shot:

termination_by normal_does_not_reduce norm tr => norm
                neutral_does_not_reduce neut tr => neut

But I was getting:

type mismatch
  norm
has type
  Normal t₁ : Type
but is expected to have type
  ?β : Sort ?u.269218

Which I don't understand since it seems like it should unify just fine? I ended up taking a different approach and got it working though:

private theorem normal_or_neutral_does_not_reduce
  {n : Nat} {Γ : Context n} {e₁ e₂ : Expr n} {τ : Ty} {t₁ : Γ  e₁ : τ} {t₂ : Γ  e₂ : τ}
  (norm_or_neut : Sum (Normal t₁) (Neutral t₁)) (tr : t₁  t₂) : Empty :=
  match t₁, norm_or_neut, tr with
  | _, .inl (.neutral neut), tr => normal_or_neutral_does_not_reduce (Sum.inr neut) tr
  | .number _, .inl .number, tr => nomatch tr
  | .string _, .inl .string, tr => nomatch tr
  | .plus _ _, .inr (.plus₁ neut₁ _), .ξ_plus₁ tr₁ => normal_or_neutral_does_not_reduce (Sum.inr neut₁) tr₁
  | .plus _ _, .inr (.plus₁ _ norm₂), .ξ_plus₂ norm₁ tr₂ => normal_or_neutral_does_not_reduce (Sum.inl norm₂) tr₂
  | .plus _ _, .inr (.plus₂ v₁ _), .ξ_plus₁ tr₁ => v₁.does_not_reduce tr₁
  | .plus _ _, .inr (.plus₂ _ neut₂), .ξ_plus₂ norm₁ tr₂ => normal_or_neutral_does_not_reduce (Sum.inr neut₂) tr₂

Mario Carneiro (Mar 28 2023 at 05:16):

your alignment is bad in that example

Mario Carneiro (Mar 28 2023 at 05:17):

termination_by normal_does_not_reduce norm tr => norm
                neutral_does_not_reduce neut tr => neut

will be interpreted as

termination_by normal_does_not_reduce norm tr => norm neutral_does_not_reduce neut tr => neut

Mario Carneiro (Mar 28 2023 at 05:17):

which fails since norm isn't a function

sgcs (Mar 28 2023 at 06:38):

I think the alignment was just a copy-paste issue, it was aligned for my personally


Last updated: Dec 20 2023 at 11:08 UTC