Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression in 2.6.2: primEraseEquality fails to compute (lack of normalization) #5784

Closed
Trebor-Huang opened this issue Feb 10, 2022 · 17 comments · Fixed by #5811
Closed

Regression in 2.6.2: primEraseEquality fails to compute (lack of normalization) #5784

Trebor-Huang opened this issue Feb 10, 2022 · 17 comments · Fixed by #5811
Assignees
Labels
builtin Enhancements to the builtin modules and builtin definitions missing normalise A term is not normalized far enough reduction regression in 2.6.2 Regression that first appeared in Agda 2.6.2 trustMe
Milestone

Comments

@Trebor-Huang
Copy link

Trebor-Huang commented Feb 10, 2022

I'm implementing NbE for STLC. I found out that if I wrap some of my substitution lemmas with primEraseEquality, it blocks the computation of concrete terms when I hit C-c C-n.

How should I start debugging the source of the problem? I have no idea how to produce a MWE. I don't know how to read terms like this:

⟦
 (^ ^ var (𝕤 𝕫) ∙ (var (𝕤 𝕫) ∙ var 𝕫)) ∙
 (^ ^ ^ var (𝕤 𝕤 𝕫) ∙ var 𝕫 ∙ var (𝕤 𝕫))
 ⟧
 (λ v  reflect (var v))
 | ^ ^ var (𝕤 𝕫) ∙ (var (𝕤 𝕫) ∙ var 𝕫)
 | symm (Agda.Builtin.Equality.Erase.primEraseEquality refl)

Does this mean that primEraseEquality blocks even on refl? I'm not sure I understand how it works, but in the docs it says

The function takes a proof of an equality between two values x and y and stays stuck on it until x and y actually become definitionally equal. Whenever that is the case, primEraseEquality e reduces to refl.

But since the normalization result is Agda.Builtin.Equality.Erase.primEraseEquality refl (I enabled --prop --safe flags), shouldn't x and y already be definitionally equal?

My Agda version is v2.6.2.1-af5bfc0, running on OS X.

@andreasabel
Copy link
Member

Such terms come from a stuck with-auxiliary function.

@Trebor-Huang
Copy link
Author

So it is stuck when rewriting with symm (primEraseEquality refl), right?

@Trebor-Huang
Copy link
Author

I tried to print that term with --show-implicit on. It produces a huge term

primEraseEquality {LEVEL} {TYPE}
    {TERM1} {TERM2} refl

Then I copy-pasted these two terms, and let Agda typecheck

_ : TERM1 ≡ TERM2
_ = refl

And it checks as expected. Then why couldn't primEraseEquality compute? I think after printing all the implicit variables, they are definitely definitionally equal.

@gallais
Copy link
Member

gallais commented Feb 10, 2022

Are the values equal up to eta or on the nose? Or are they equal up to whnf or just nf?
Because according to the comments in the code primEraseEquality only does whnf
at the moment (no eta, no nf):

-- Andreas, 2013-07-22.
-- Note that we cannot call the conversion checker here,
-- because 'reduce' might be called in a context where
-- some bound variables do not have a type (just __DUMMY_TYPE__),
-- and the conversion checker for eliminations does not
-- like this.
-- We can only do untyped equality, e.g., by normalisation.
-- Jesper, 2020-04-04: We reduce rather than normalise for
-- efficiency reasons. In general this is weaker but it is
-- equivalent at base types. A stronger version of
-- primEraseEquality (using type-directed conversion) may be
-- implemented using --rewriting.

@gallais gallais added builtin Enhancements to the builtin modules and builtin definitions reduction labels Feb 10, 2022
@Trebor-Huang
Copy link
Author

They are plain data values. The complete term is too long to paste here I guess. But I also noticed that when I shift some symm or rewrite around some of the blocking primEraseEquality disappears (and others appear :( ). I'll keep trying to minimize it.

@jespercockx
Copy link
Member

@Trebor-Huang could you try if you still have the same problem if you use this erasure function instead of primEraseEquality:

{-# OPTIONS --rewriting #-}

open import Agda.Builtin.Equality
open import Agda.Builtin.Equality.Rewrite

postulate
  eraseEquality :  {ℓ} {A : Set ℓ} {x y : A}  x ≡ y  x ≡ y
  eraseEquality-comp :  {ℓ} {A : Set ℓ} {x : A} {eq : x ≡ x}  eraseEquality eq ≡ refl

{-# REWRITE eraseEquality-comp #-}

This one does use type-directed conversion, while I believe primEraseEquality is only guaranteed to compute on closed first-order terms.

@Trebor-Huang
Copy link
Author

Yes, I was going to do that. The problem does go away with the REWRITE version. But I'm still wondering why primEraseEquality did not reduce. I can narrow it down to

^ ^ var (𝕤 𝕫) ∙ (var (𝕤 𝕫) ∙ var 𝕫)

which is a plain lambda term using the well-scoped de Bruijn syntax.

@jespercockx
Copy link
Member

Perhaps the reason is that primEraseEquality uses reduce rather than normalise, so it will not normalise constructor arguments.

@UlfNorell
Copy link
Member

That seems like a bug, no? Here's a small test case

open import Agda.Builtin.Nat
open import Agda.Builtin.Equality
open import Agda.Builtin.Equality.Erase

data Wrap : Set where
  wrap : Nat  Wrap

test : wrap (1 + 1) ≡ wrap 2
test = primEraseEquality refl

thm : test ≡ refl
thm = refl   -- Fails: primEraseEquality refl != refl

@andreasabel andreasabel added the regression in 2.6.2 Regression that first appeared in Agda 2.6.2 label Feb 10, 2022
@andreasabel
Copy link
Member

andreasabel commented Feb 10, 2022

agda-bisect says:
f728537 is the first bad commit (ping @jespercockx)

Date: Sun Apr 5 09:08:54 2020 +0200

[ perf ] Reduce instead of normalise in primEraseEquality

Reasoning: the current implementation is anyway incomplete b/c of eta-equality, so we might as well make it a bit weaker if this avoids a call to normalise. If this breaks anything, a workaround is to use rewrite rules instead.

@andreasabel
Copy link
Member

It seems to me that we should revert f728537 because it confuses the users.

However, @jespercockx seems to suggest that the REWRITE version works better. If we go for the REWRITE version, we should place it somewhere canonical and deprecate the old primEraseEquality.
A caveat is of course that then the user needs --rewriting in their project, and this might have repercussions.

@andreasabel andreasabel added this to the 2.6.2.2 milestone Feb 11, 2022
@jespercockx
Copy link
Member

jespercockx commented Feb 11, 2022

The way I see it each of them has their own use cases:

  • primEraseEquality is used for implementing decidable equality on primitive, non-composite types such as Float and String.
  • eraseEquality defined with rewrite rules is used when you actually want an equality that computes to refl whenever it becomes reflexive.

So IMO the only thing that should really change is the documentation which should point users towards the version they need.

@UlfNorell
Copy link
Member

non-composite types such as Float and String

Also works for Nat since suc is strict.

@Trebor-Huang
Copy link
Author

Is it possible to internally use REWRITE mechanisms to define eraseEquality, but make it exempt from the --rewriting flag? Is that safe? I think since eraseEquality is defined via a postulate it is a conservative extension.

@jespercockx
Copy link
Member

The easiest way to do that is just to change the implementation of primEraseEquality to use pureEqualTerm from Agda.TypeChecking.Conversion.Pure, which is the function internally used by --rewriting as well. However, that might make it less efficient than the current implementation for base types Float, String, Nat, ... so I'm not sure if we want to do that.

@Trebor-Huang
Copy link
Author

The easiest way to do that is just to change the implementation of primEraseEquality to use pureEqualTerm from Agda.TypeChecking.Conversion.Pure, which is the function internally used by --rewriting as well. However, that might make it less efficient than the current implementation for base types Float, String, Nat, ... so I'm not sure if we want to do that.

Why not keep both versions? This means

  • Add a built-in eraseEquality (we can bikeshed a better name later), that act exactly like the version using rewrite pragmas, but without requiring the --rewrite flag;
  • Keep the primEraseEquality in place where we need efficiency on the base types;
  • Switch to eraseEquality where appropriate in the standard library. (Is there even any place where we should switch?)

@andreasabel
Copy link
Member

The discussion has died down. So I went ahead and proposed the easiest fix by reverting the commit that introduced the regression: #5811.

@andreasabel andreasabel changed the title Debugging why primEraseEquality fails to compute Regression in 2.6.2: primEraseEquality fails to compute (lack of normalization) Mar 27, 2022
@andreasabel andreasabel added the missing normalise A term is not normalized far enough label Apr 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
builtin Enhancements to the builtin modules and builtin definitions missing normalise A term is not normalized far enough reduction regression in 2.6.2 Regression that first appeared in Agda 2.6.2 trustMe
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants