Zulip Chat Archive

Stream: general

Topic: Is this equality definitional?


Mike Shulman (Sep 30 2022 at 22:39):

I'm stumped by the following in Lean 3 (haven't tried Lean 4):

def weird_zero :     
| 0 0 := 0
| 0 (n+1) := weird_zero 0 n
| (m+1) 0 := weird_zero m 0
| (m+1) (n+1) := weird_zero m n

#eval weird_zero 0 0 -- yields 0
#reduce weird_zero 0 0 -- yields 0

def test : weird_zero 0 0 = 0 := rfl -- fails

I suppose it must be something to do with the simultaneous/nested recursion on m and n, since replacing either the 0 (n+1) or (m+1) 0 clause with 0 makes it work. But I can't guess how that can matter, because regardless of whatever strange kind of recursion is going on behind the scenes, weird_zero 0 0 is a concrete closed term that normalizes to 0 with either eval or reduce. So why doesn't it likewise get normalized in the definition of test?

Mauricio Collares (Sep 30 2022 at 23:21):

https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/Base.20case.20of.20recursive.20definition sounds related

Mike Shulman (Sep 30 2022 at 23:26):

Yes, that seems to be the same issue. But the discussion there doesn't explain to me how weird_zero 0 0 can reduce to 0 with eval and reduce, yet rfl doesn't typecheck at weird_zero 0 0 = 0. It's contrary to all my experience with other dependent-type-theory-based proof assistants. It's not as if rfl is some weird tactic that's doing some fancy stuff that isn't invariant under definitional equality or doesn't notice definitional equality. I mean, maybe it is, but writing the explicit term eq.refl 0 also fails.

Mike Shulman (Sep 30 2022 at 23:29):

Is weird_zero 0 0 definitionally equal to 0 or not?

If so, then it seems that Lean's typechecking does not respect definitional equality, since weird_zero 0 0 = 0 is definitionally equal to 0 = 0, but eq.refl 0 has the latter type and not the former.

If not, then it seems that eval and reduce are doing some sort of "generalized computation" that doesn't preserve definitional equality. Which, I suppose, is a thing that one could do. But then there ought to be some other command that only performs definitional computations, right?

Mauricio Collares (Sep 30 2022 at 23:45):

AFAIK, #eval runs in the VM, while #reduce runs in the kernel. But see Mario's message at https://leanprover.zulipchat.com/#narrow/stream/113489-new-members/topic/not.20p.20versus.20p.20implies.20false/near/209606830 for a possible reason for rfl failing in cases where definitional equality holds.

Mike Shulman (Oct 01 2022 at 00:42):

Thanks for the link. But Mario explicitly says that Lean's definitional equality check is semantically equivalent to reducing both sides to a normal form. So since in this case both sides have the same normal form 0, shouldn't they should be judged as equal by the definitional equality checker whatever other fancy stuff it's doing?

Mauricio Collares (Oct 01 2022 at 00:54):

You're right, my bad. I'll wait for the experts to answer :)

Jason Rute (Oct 01 2022 at 01:01):

haven't tried Lean 4

For what it's worth, your weird_zero doesn't compile in Lean 4 as is:

def weird_zero : Nat  Nat  Nat -- termination error
| 0, 0 => 0
| 0, (n+1) => weird_zero 0 n
| (m+1), 0 => weird_zero m 0
| (m+1), (n+1) => weird_zero m n

I get the error:

fail to show termination for
  weird_zero
with errors
argument #1 was not used for structural recursion
  failed to eliminate recursive application
    weird_zero 0 n

argument #2 was not used for structural recursion
  failed to eliminate recursive application
    weird_zero m 0

structural recursion cannot be used

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

Adam Topaz (Oct 01 2022 at 01:18):

FWIW I tried the pascal example from the other thread in lean4 and got similar behaviour. (Presumably wierd_error will behave similarly if you provide the termination proof.)

Mike Shulman (Oct 01 2022 at 01:19):

Coq doesn't accept it either, but Agda does.

Jason Rute (Oct 01 2022 at 01:20):

Ok, this works in Lean 4:

def weird_zero (n m : Nat) : Nat :=
match n, m with
| 0, 0 => 0
| 0, n+1 => weird_zero 0 n
| m+1, 0 => weird_zero m 0
| m+1, n+1 => weird_zero m n
termination_by weird_zero n m => (n, m)

#eval weird_zero 0 0 -- yields 0
#reduce weird_zero 0 0 -- yields 0

def test : weird_zero 0 0 = 0 := rfl -- passes

Jason Rute (Oct 01 2022 at 01:23):

(I copied the code from this example. I still have no idea how the termination checker works.)

Mike Shulman (Oct 01 2022 at 01:24):

Ok, that's fairly nice. I'm still curious what's going on in Lean 3, though.

Leonardo de Moura (Oct 01 2022 at 01:25):

For more information about termination_by:
https://leanprover.github.io/theorem_proving_in_lean4/induction_and_recursion.html#well-founded-recursion-and-induction

Leonardo de Moura (Oct 01 2022 at 01:29):

The Lean 3 version is also using well founded recursion. You can use the #print command to see how the frontend compiled the declaration.

Mike Shulman (Oct 01 2022 at 01:32):

#print weird_zero gives me

def weird_zero :      :=
weird_zero._main

Mike Shulman (Oct 01 2022 at 01:33):

What I really want to know is why eq.refl 0 doesn't typecheck at weird_zero 0 0 = 0.

Jason Rute (Oct 01 2022 at 01:34):

@Mike Shulman You can print weird_zero._main (and so on ...) but it gets pretty hairy.

Leonardo de Moura (Oct 01 2022 at 01:35):

We have to keep going:

#print weird_zero._main
#print weird_zero._main._pack
/-
def weird_zero._main._pack : Π (_x : Σ' (a : ℕ), ℕ), (λ (_x : Σ' (a : ℕ), ℕ), ℕ) _x :=
λ (_x : Σ' (a : ℕ), ℕ),
  well_founded.fix _
    (λ (_x : Σ' (a : ℕ), ℕ),
       psigma.cases_on _x
         (λ (fst snd : ℕ),
            nat.cases_on fst
              (nat.cases_on snd
                 (id_rhs ((Π (_y : Σ' (a : ℕ), ℕ), has_well_founded.r _y ⟨0, 0⟩ → ℕ) → ℕ)
  ...
-/

Mauricio Collares (Oct 01 2022 at 01:37):

I also want to understand the answer to Mike's question, to be honest: If #reduce weird_zero 0 0 returns 0, why does rfl not prove weird_zero 0 0 = 0? Is #reduce doing more powerful things? If so, is there a #command to "emulate" what the kernel is doing?

Jason Rute (Oct 01 2022 at 01:41):

The plot gets stranger: In the Lean 3 official online editor (which I assume is running 3.4.2) it again works. The rfl succeeds. So whatever is broken is broken in the community version of Lean 3.

Leonardo de Moura (Oct 01 2022 at 01:44):

Mike Shulman said:

What I really want to know is why eq.refl 0 doesn't typecheck at weird_zero 0 0 = 0.

The #reduce and the frontend type checker are using different settings to decide which declarations can be delta reduced.
#reduce uses transparency_mode::All and will reduce even theorems if needed. Since weird_zero is defined using well-founded recursion, theorems needed to be reduced. The type checker in the Lean 3 frontend is using a more conservative setting in the community version. I think this was an intentional change in the community version to address nasty performance issues. @Gabriel Ebner do you remember the issue?

Mauricio Collares (Oct 01 2022 at 02:09):

I've verified that Mike's example works in Lean 3.28.0 and not in Lean 3.29.0, so I think a relevant PR is https://github.com/leanprover-community/lean/pull/562 ("backport(type_context.cpp): wf should not compute"), but perhaps this is orthogonal to the transparency issue above (all I could find for that was https://github.com/leanprover-community/lean/pull/211, but this landed way earlier)

Mauricio Collares (Oct 01 2022 at 02:11):

It's a bit unexpected that this is a backport and yet the equivalent example works in Lean 4

Mike Shulman (Oct 01 2022 at 02:18):

Hmm, interesting. Is there a way for the user to alter the attributes or settings so that rfl will work?

Matt Diamond (Oct 01 2022 at 02:37):

this may be the relevant thread for the PR Mauricio linked: https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/mathport.3Awf-refl

Mario Carneiro (Oct 01 2022 at 04:10):

The issue was that we don't want proofs to be computed to a normal form because they aren't designed for such anyway. We would end up with some weird incentives to avoid propext and other axioms (which will block computation)

Mario Carneiro (Oct 01 2022 at 04:13):

Basically, "every closed term of nat normalizes to a numeral" is just false for lean as actually practiced

Mario Carneiro (Oct 01 2022 at 04:15):

Even before that PR, wf reduction was really flaky because of these issues

Mario Carneiro (Oct 01 2022 at 04:23):

To reiterate an example I used recently:

set_option pp.all true
#check show , from eq.rec_on (eq_true_intro (@id true)) 2 -- closed term of type ℕ
#reduce show , from eq.rec_on (eq_true_intro (@id true)) 2
-- @eq.rec.{1 1} Prop (true → true) (λ (_x : Prop), nat) (nat.succ (nat.succ nat.zero)) true
--   (@propext (true → true) true
--      (@iff.intro (true → true) true (λ (hl : true → true), true.intro) (λ (hr a : true), a)))

Even if we reduce proofs, we get stuck whenever any axioms are used. Definitions by well founded recursion are often susceptible to variants on this if the proof of well foundedness uses simp or rw

Mike Shulman (Oct 01 2022 at 04:32):

So many things go wrong when you don't have univalence... (-:O

Mario Carneiro (Oct 01 2022 at 04:37):

Or you can just use theorems for rewriting. simp proves this just fine

Mario Carneiro (Oct 01 2022 at 04:38):

It's impossible to have all the definitional equalities you would like, unless you accept an extensional type theory. m + 0 = m and 0 + m = m are equally true to any mathematician

Mario Carneiro (Oct 01 2022 at 04:39):

I personally think that we should put as little weight on the DTT side of things as we can. You have to really contort the mathematics to fit into the framework even if you invest in an expansive and useful defeq

Mario Carneiro (Oct 01 2022 at 04:40):

and the underlying theory becomes monstrously complicated when you go that route

Mario Carneiro (Oct 01 2022 at 04:41):

After spending a while on these kinds of systems, it's amazing to me how much this is just a non-issue in HOL based systems

Mike Shulman (Oct 01 2022 at 04:47):

I didn't mean to start an argument, just making a tongue-in-cheek observation. (-:

Mike Shulman (Oct 01 2022 at 04:50):

FWIW, your example of m + 0 = m and 0 + m = m can both be made to hold definitionally using nu-equality or rewriting type theory. But I think what it actually illustrates is the folly of conflating definitional equality with mathematical equality. They are equally true to any mathematician, and indeed they are equally propositionally true in DTT.

Mike Shulman (Oct 01 2022 at 04:55):

So if all you want to do is formalize set-based mathematics, then perhaps indeed there's not much use for dependent types or definitional equality. Those of us who want to do higher-typed mathematics do seem to need the latter. But even apart from that, definitional equality is foundational to the computational interpretation of DTT as a programming language. I ran into this issue because I'm teaching a class using Lean with a lot of computer science students in it, and so we're doing some basic functional programming, and for that purpose it's important to actually have things compute.

Mike Shulman (Oct 01 2022 at 04:56):

It's very weird if you write a program that computes when you run it, but when you start trying to prove things about it, it doesn't compute any more during your proof!

Mario Carneiro (Oct 01 2022 at 05:09):

I'd love to have a tactic that will prove that a computation under the VM erasing map preserves propositional equality, but as far as I can tell that would need no less than full HoTT style reasoning

Mario Carneiro (Oct 01 2022 at 05:09):

because you can get casts and casts upon casts and so on

Mike Shulman (Oct 01 2022 at 05:15):

Mario Carneiro said:

as far as I can tell that would need no less than full HoTT style reasoning

The solution seems obvious to me... (-:O


Last updated: Dec 20 2023 at 11:08 UTC