Zulip Chat Archive

Stream: lean4

Topic: Debugging an issue with calc mode


Philip Wadler (Nov 19 2023 at 16:28):

I am having an issue with calc mode, which I hope the incredibly supportive Lean community can help me sort out.

I want to use Lean to model a simple lambda calculus. I've already done this in Agda (see plfa.inf.ed.ac.uk). Relation (.~>.) models a single reduction step, and relation (.~>>.) is its transitive closure.

I declare the relation between (.~>.) and (.~>>.) as follows.

instance : Trans (.~>.  : Γ  A  Γ  A  Type)
                 (.~>.  : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type) where
  trans := one_one

instance : Trans (.~>.  : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type) where
  trans := one_many

instance : Trans (.~>>. : Γ  A  Γ  A  Type)
                 (.~>.  : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type) where
  trans := many_one

instance : Trans (.~>>. : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type) where
  trans := many_manyinstance : Trans (.~>.  : Γ  A  Γ  A  Type)
                 (.~>.  : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type) where
  trans := one_one

instance : Trans (.~>.  : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type) where
  trans := one_many

instance : Trans (.~>>. : Γ  A  Γ  A  Type)
                 (.~>.  : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type) where
  trans := many_one

instance : Trans (.~>>. : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type)
                 (.~>>. : Γ  A  Γ  A  Type) where
  trans := many_many

Further, I have succeeded in using Lean to demonstrate individual reduction steps.

example : two ~> (ƛ (suc_c  (suc_c  # Z)))  0
    := xi_app_1 (beta Value.lambda)

example : ((ƛ (suc_c  (suc_c  # Z)))  0 :   ) ~> suc_c  (suc_c  0)
    := beta Value.zero

example : (suc_c  (suc_c  0) :   ) ~> suc_c  1
    := xi_app_2 Value.lambda (beta Value.zero)

example : (suc_c  1 :   ) ~> 2
    := beta (Value.succ Value.zero)

However, if I try to combine them with calc then Lean complains.

example : two ~>> 2 :=
  calc
    two_c  suc_c  0
      ~> (ƛ (suc_c  (suc_c  # Z)))  0  := xi_app_1 (beta Value.lambda)
    _ ~> (suc_c  (suc_c  0))            := beta Value.zero
    _ ~> suc_c  1                       := xi_app_2 Value.lambda (beta Value.zero)
    _ ~> 2                              := beta (Value.succ Value.zero)

It complains about beta Value.lambda in the first line and beta Value.zero in the second line, even though exactly the same individual reductions are accepted in the examples above. The error messages complain that it is given one thing when it expects another, but the given and expected things are in fact definitionally equal.

The full file from which the above is excerpted is at https://github.com/plfa/plfl/blob/main/src/Typesig.lean.

Any help would be greatly appreciated.

Mauricio Collares (Nov 19 2023 at 17:18):

Replacing two_c ⬝ suc_c ⬝ 0 by two in your calc example makes it compile, but Lean seems happy to accept

example : two = two_c  suc_c  0 := rfl

That's interesting.

Philip Wadler (Nov 19 2023 at 17:29):

Thanks, Mauricio. That is indeed interesting.

On my machine, when I make the change you suggest it stops complaining about beta Value.zero but it still complains about beta Value.lambda. (I'm running Lean4 v0.0.113 on Visual Studio 1.73.1 on a 2021 iMac.)

Should I regard this as a bug in Lean? Will it go away? Is there a workaround in the meantime?

Kevin Buzzard (Nov 19 2023 at 17:30):

I would definitely consider updating to a modern Lean (it's on v4.3.something right now) to see if things work any better.

Mauricio Collares (Nov 19 2023 at 17:31):

v0.0.113 is the VS Code extension version, but you can get the lean version by typing #eval Lean.versionString on a new line in any open Lean file

Mauricio Collares (Nov 19 2023 at 17:34):

I noticed the PLFL repository is not a Lake project, which is the preferred way of setting up projects these days. The reason I mention this here is because Lake projects contain a lean-toolchain file pinning the project to a particular Lean version, which is useful for reproducibility. Without that, you get whatever default version elan gives you (which you can see with elan show).

Philip Wadler (Nov 19 2023 at 17:35):

Lean version string tells me I am running "4.0.0-nightly-2022-08-05".

I updated my Lean version earlier today by running elan toolchain install stable (which told me it fetched Lean 4.2 despite the string above). What should I do to get a more up to date version running? Will Visual Studio immediately pick up on the update, or will I need to inform it somehow?

Many thanks for your help!

Mauricio Collares (Nov 19 2023 at 17:37):

Currently you're not using a project (which means imports probably don't work!), and if you want to keep it that way you can type elan default stable, after which VS Code will probably pick up the new version.

lake new PLFL is the command you'd use to create a new project, and then you can edit the lean-toolchain file to use a new version, which has the advantage of ensuring users of your project will also run your code with the same Lean version.

Philip Wadler (Nov 19 2023 at 17:39):

Running elan show gives me the following.

rambla$ elan show
installed toolchains
--------------------

stable
leanprover/lean4:nightly (default)
leanprover/lean4:stable

active toolchain
----------------

leanprover/lean4:nightly (default)
Lean (version 4.0.0-nightly-2022-08-05, commit fdaae2059410, Release)

That suggests I'm running the nightly version, which should be more up to date than v4.3.3.something, so I don't know why it's numbered 4.0.0. Quite possibly I have this all wrong, in which case please let me know what I should do instead.

Mauricio Collares (Nov 19 2023 at 17:40):

You're running _a_ nightly version, but it doesn't get updated automatically (elan update nightly does that)

Philip Wadler (Nov 19 2023 at 17:40):

@Mauricio Collares Thank you for the suggestion about lake.

Mauricio Collares (Nov 19 2023 at 17:42):

Stable versions are quite fresh these days, so running on nightly is no longer as essential as it was a few months ago. You can switch your default channel to stable by running elan default stable if you prefer, but it's not too important.

Mauricio Collares (Nov 19 2023 at 17:53):

I tried updating your repository to the newest Lean version and I found a funny calc behavior which may be the same one. The following code fails with "type mismatch; rfl has type 3 + 9 = 3 + 9 : Prop but is expected to have type 3 + 9 = 12 : Prop":

example : 3 + 9 = 12 :=
  calc
    3 + 9
    _ = 12 := rfl

But this one succeeds:

example : 3 + 9 = 12 :=
  calc
    3 + 9
    _ = 12 := by rfl

Richard Copley (Nov 19 2023 at 17:57):

rfl the tactic can be a little sophisticated. rfl the term just means x=x. Check their respective docstrings! Might be fun to think about why by exact rfl does work there, too.

Mauricio Collares (Nov 19 2023 at 17:59):

Another calc-related funny thing from the repo:

open Nat

inductive le : Nat  Nat  Prop
| refl :  {m}, le m m
| step :  {m n}, le m n  le m (succ n)

instance : LE Nat where
  le := le

example : 2  4 := le.step (le.step le.refl) -- succeeds

example : 2  4 :=
  calc
    2  4 := le.step (le.step le.refl) -- fails

Mauricio Collares (Nov 19 2023 at 18:00):

Richard Copley said:

rfl the tactic can be a little sophisticated. rfl the term just means x=x. Check their respective docstrings! Might be fun to think about why by exact rfl does work there, too.

I don't think that's the issue. example : 3 + 9 = 12 := rfl outside calc works fine (as it should, since the two sides are defeq).

Mauricio Collares (Nov 19 2023 at 18:01):

I'm sure someone will come here soon saying the calc tactic implementation is missing a whnfR, instantiateMVars or whatever

Philip Wadler (Nov 19 2023 at 18:10):

Thank you for the suggestions. I've updated to stable-4.2.0, but am still seeing the same issue. Interestingly, the update also broke my first single step reduction example, which worked previously. (Same thing happens if I switch to nightly-4.4.0.)

Mauricio Collares (Nov 19 2023 at 18:15):

Whoops. Well, you can go back to the previous nightly with elan default leanprover/lean4:nightly-2022-08-05, but indeed I see the same error here now. I don't understand why my editor didn't show an error before.

Mauricio Collares (Nov 19 2023 at 18:15):

I will submit a PR creating the project boilerplate in the hopes of avoiding these sorts of reproducibility issues in the future. No need to accept it if you don't feel like it fits the project.

Philip Wadler (Nov 19 2023 at 18:17):

@Richard Copley Thanks for the minimal example. How do I proceed to get this fixed? Is the first step just to wait and see if someone offers the answer you expect?

@Mauricio Collares Thank you!

Richard Copley (Nov 19 2023 at 18:22):

  • Mauricio's example?
  • I don't know, but maybe somebody who does will chime in?

Mauricio Collares (Nov 19 2023 at 18:25):

I will file a bug

Mauricio Collares (Nov 19 2023 at 18:36):

Philip Wadler said:

Thank you for the suggestions. I've updated to stable-4.2.0, but am still seeing the same issue. Interestingly, the update also broke my first single step reduction example, which worked previously. (Same thing happens if I switch to nightly-4.4.0.)

Filed lean4#2913 for the minimized issue. You hit this issue in other PLFL files, but I don't think the minimized issue is the same as the calc problem you originally reported, since it occurs outside calc mode in the most recent Lean version.

Would be good to understand why the single-step reduction example fails in the most recent Lean version (it seems like there are some metavars in the error output).

Mauricio Collares (Nov 19 2023 at 18:55):

@Philip Wadler Weirdly I see the same issue in the single-step reduction example (xi_app_1 (beta Value.lambda)) in your file even using nightly-2022-08-05.

Philip Wadler (Nov 19 2023 at 19:05):

@Mauricio Collares Thanks for filing a bug report. Regarding the example failing under nightly-2022-08-05 when it didn't earlier, I wonder if this is a heisenbug. That would explain why you didn't see an error at one point and did see one later. Anyhow, it certainly should work and the problem is not related to calc: is there another bug report to file here?

Mauricio Collares (Nov 19 2023 at 19:15):

Most likely, but extracting a #mwe here will probably require more knowledge of the example than I have (I've read PLFA, but it's been a while!), and I guess will take longer too.

Philip Wadler (Nov 19 2023 at 19:25):

Indeed, extracting a #mwe will be difficult. (I'm pretty familiar with the problem, and I have no idea how to go about doing so.) Is it possible to just declare the current example an #mwe? I'm happy to remove extraneous code, although even after doing so it will be quite large.

Mauricio Collares (Nov 19 2023 at 19:29):

Ah, wait, this is related to the problem in the other thread! two_c uses the # 0 notation which doesn't work.

Mauricio Collares (Nov 19 2023 at 19:30):

So then this is just the calc problem, I think. Replacing two_c ⬝ suc_c ⬝ 0 by two in the calc block (and # 0 by # Z in the definition of two_c) makes it work.

Kyle Miller (Nov 19 2023 at 19:44):

Mauricio Collares said:

... But this one succeeds: ...

If you look at the types, you can see for example @Eq ?m.239 (3 + 9) 12, indicating it hasn't decided on the types of the numerals yet. Numerals rely on a @[default_instance] to eventually get specialized to Nat if no better idea comes along first.

Giving a type ascription lets it proceed:

example : 3 + 9 = 12 :=
  calc
    (3 + 9 : Nat)
    _ = 12 := rfl

When you did by rfl, what happened is that this allows the rfl to be deferred until later (by exact rfl works too for this -- there's nothing special about the rfl tactic in this case).

Mauricio Collares (Nov 19 2023 at 19:46):

Is the other example in lean4#2913 (the 2 ≤ 4 one) also expected to fail? I can close the issue if so. The only reason I opened it was because it was a regression, but maybe it was never supposed to work in the first place.

Mauricio Collares (Nov 19 2023 at 19:49):

I guess my question is: It works if I give a type ascription inside the calc mode, but not if I include a type ascription in the goal. Should calc be able to use the expected types to help figure out the types of the numerals inside it?

Kyle Miller (Nov 19 2023 at 19:52):

I think at least for =, the fact calc fails is consistent with this failing:

example : True := by
  have := (rfl : 3 + 8 = 12)
  /-
  type mismatch
    rfl
  has type
    3 + 8 = 3 + 8 : Prop
  but is expected to have type
    3 + 8 = 12 : Prop
  -/

Kyle Miller (Nov 19 2023 at 19:53):

It's too bad you can't get it to succeed with (rfl (α := Nat) : 3 + 8 = 12)...

Kyle Miller (Nov 19 2023 at 19:55):

In your example : 2 ≤ 4 := le.step (le.step le.refl) example, this succeeds because default instances are chosen by the time the body is elaborated.

Kyle Miller (Nov 19 2023 at 19:57):

@Philip Wadler I'm not sure where you are right now with needing a workaround, but I suspect you can replace := ... with by exact ... where you're having errors in calc.

Kyle Miller (Nov 19 2023 at 19:59):

Never mind, that doesn't work.

Kyle Miller (Nov 19 2023 at 20:01):

Anyway, here's a style you could try that helps keep different proofs in a calc block from interacting with each other:

example : two ~>> 2 := by
  calc
    two_c  suc_c  0
      ~> (ƛ (suc_c  (suc_c  # Z)))  0  := ?_
    _ ~> (suc_c  (suc_c  0))            := ?_
    _ ~> suc_c  1                       := ?_
    _ ~> 2                              := ?_
  · exact xi_app_1 (beta Value.lambda) -- error here
  · exact beta Value.zero
  · exact xi_app_2 Value.lambda (beta Value.zero)
  · exact beta (Value.succ Value.zero)

Philip Wadler (Nov 19 2023 at 20:03):

Aha, so this explains a lot. I had replaced # S Z and # Z by # 1 and # 0 where the original definitions did not report an error, so I thought it made no difference. But it _did_ make a difference further down, in the examples to test single reduction steps. This explains why the bug came and went, because I made those changes between when it worked and when it started to display the bug.

Further, if I add a type ascription to the first line then calc now works.

example : two ~>> 2 :=
  calc
    (two_c  suc_c  0 :   )
      ~> (ƛ (suc_c  (suc_c  # Z)))  0  := xi_app_1 (beta Value.lambda)
    _ ~> (suc_c  (suc_c  0))            := beta Value.zero
    _ ~> suc_c  1                       := xi_app_2 Value.lambda (beta Value.zero)
    _ ~> 2                              := beta (Value.succ Value.zero)

That also explains why replacing two_c ⬝ suc_c ⬝ 0 by two makes calc work, because two is equal to the former but provides additional information about the type.

So it works, and no need for a bug report. Yay!

Of course it would be nice if the type information from the signature of the example could propagate into the calc, but I guess that's a deeper matter.

Philip Wadler (Nov 19 2023 at 20:12):

@Mauricio Collares Adding a type ascription to the 2 ≤ 4 example fixes that problem as well.

Thank you everyone for the help! The Lean community is fantastic.

Mauricio Collares (Nov 19 2023 at 20:32):

I noticed that Leo added a workaround to the show tactic (https://github.com/leanprover/lean4/commit/474f1a4d39bac6310fbd47aa4545d6c15fb14d53) a day after the original change (which was a fix for lean4#2011). Hopefully the calc tactic can use a similar one to reduce the number of needed ascriptions.

Kyle Miller (Nov 19 2023 at 22:15):

@Mauricio Collares It's been pointed out to me that my arithmetic was bad, and that actually this works:

example : True := by
  have := (rfl : 3 + 9 = 12)
  sorry

That suggests to me that the calc equivalent should work too.


Last updated: Dec 20 2023 at 11:08 UTC