agda / agda

Agda is a dependently typed programming language / interactive theorem prover.

Home Page:https://wiki.portal.chalmers.se/agda/pmwiki.php

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Instance resolution runs too late, leads to `with`-abstraction failure

buggymcbugfix opened this issue · comments

I have a situation where a rewrite unexpectedly does nothing. How is this possible? I would expect a failed rewrite to give an error message, otherwise I expect the goal type to change.

I have minimised the proof script to the following:

module Gamma where

open import Relation.Binary.PropositionalEquality

record Semiring : Set₁ where
  field
    grade : Set
    1R           : grade
    _*_          : grade  grade  grade
    leftUnit*    : {r : grade}  1R * r ≡ r
    -- [...]

open Semiring {{...}}

foo : {{R : Semiring}} {r : grade}  1R * r ≡ r
foo {r = r} rewrite leftUnit* {r} = {!   !}

Without the rewrite, the goal is (R Semiring.* Semiring.1R R) r ≡ r. Adding the rewrite neither changes the goal, nor gives any error message.

Note: This is easy enough to work around, but I wanted to understand what is going on here.

Addendum

$ agda --version
Agda version 2.6.4.1

I haven't looked at why the rewrite doesn't trigger in this case, but there is no failure for failed rewrites. Rewrite is a special case of with (see https://agda.readthedocs.io/en/v2.6.4.1/language/with-abstraction.html#rewrite) and doesn't do any checks to see if anything was actually being rewritten.

Thanks for the quick reply. I have tried rewriting to explicit with per the documentation:

module Gamma where

open import Relation.Binary.PropositionalEquality

record Semiring : Set₁ where
  field
    grade : Set
    1R           : grade
    _*_          : grade  grade  grade
    leftUnit*    : {r : grade}  1R * r ≡ r
    -- [...]

open Semiring {{...}}

foo : {{R : Semiring}} {r : grade}  1R * r ≡ r
foo {r = r} with 1R * r | leftUnit* {r}
...            | .r     | refl = {!   !}

Now Agda complains:

I'm not sure if there should be a case for the constructor refl,
because I get stuck when trying to solve the following unification
problems (inferred index ≟ expected index):
  (R Semiring.* Semiring.1R R) r ≟ r
Possible reason why unification failed:
  Cannot solve variable r of type Semiring.grade R with solution
  (R Semiring.* Semiring.1R R) r because the variable occurs in the
  solution, or in the type of one of the variables in the solution.
when checking that the pattern refl has type
(R Semiring.* Semiring.1R R) r ≡ r

I hope I am not spamming you, I did ask a very experienced Agda user before posting here and they were not sure what was going on.

There seems to be a regression here. The above code works fine on Agda 2.6.2.2

Indeed, 2.6.4 is the first that cannot solve the instance arguments in the type signature of foo, leading to a subsequent unification failure.

Dropping the infix for better readability:

open import Agda.Builtin.Equality

record Semiring : Set₁ where
  field
    Carrier   : Set
    one       : Carrier
    mul       : Carrier  Carrier  Carrier
    left-unit : (r : Carrier)  mul one r ≡ r

open Semiring {{...}}

foo : {{R : Semiring}} (r : Carrier)  mul one r ≡ r
foo r with mul one r | left-unit r
...      | .r        | refl          = refl

The error is:

I'm not sure if there should be a case for the constructor refl,
because I get stuck when trying to solve the following unification problems (inferred index ≟ expected index):

 Semiring.mul R (Semiring.one R) r ≟ r

Possible reason why unification failed:
Cannot solve variable r of type Semiring.Carrier R with solution Semiring.mul R (Semiring.one R) r because the variable occurs in the solution, or in the type of one of the variables in the solution.
when checking that the pattern refl has type

Semiring.mul R (Semiring.one R) r ≡ r

The explanation is: because instance resolution failed, with was not able to abstract in the target type, so the type did not become r == r, so refl does not match.

@jespercockx : Is this an intended regression or an accidential one?

The changelog for 2.6.4 just says:

* [**Breaking**] The algorithm for resolution of instance arguments
has been simplified. It will now only rely on the type of instances
to determine which candidate it should use, and no longer on their
values.

However, it is not clear that this change is responsible, because, interestingly enough, when you turn foo into a postulate, there are no unsolved constraints. So maybe just instance search kicks in too late now.

Bisection anyway blames the respective commit:

2af2ff1 is the first bad commit
Date: Tue Nov 29 17:39:24 2022 +0100
[ fix #6364 ] Only filter instances based on their type, not their body

I'm sorry, could you point me to which instance argument exactly doesn't get resolved? If I just put the values in a let-binding rather than a with, then I don't get any unresolved instances:

foo : {{R : Semiring}} (r : Carrier)  mul one r ≡ r
foo r = 
  let x = mul one r
      eq = left-unit r
  in {!   !}

So this seems to be a problem with with rather than with instance search itself, maybe my change just revealed an existing bug?

After some further investigation, the problem seems to be indeed that instance search is called too late. If I add a call to wakeupConstraints_ to the beginning of checkWithRHS in Agda.TypeChecking.Rules.Def, then the test case passes.

Now, we should be a bit careful to not call wakeupConstraints_ too often to avoid performance degradations. However, given that with-abstraction is already an expensive operation (since it fully normalizes the term and the type of the function) maybe having it here is fine. @UlfNorell what do you think?

I'm still not completely sure why it is my change that triggered this bug, since I did not change anything to when instance search is called, and just calling instance search sooner seems to solve the problem.

Don't we run instance search after having checked the type signature?
I mean if constraints can be solved from the type signature alone (as witnessed by turning it into a postulate), why does this have to have to do anything with with?

In this case the instance constraint comes from the term that we are doing with-abstraction on, so it is only created after the type signature is checked.

Ok, maybe I was mislead by the yellow coloring of one in the type signature. The meta range might be off then.

Appearances can be deceiving. -- Agent Smith

@jespercockx : Should we aim to fix this for 2.6.4.2 or is that too risky?

If the fix is just adding an extra call to wakeupConstraints_ then I do not see how that could cause any regressions. The only possible issue is the negative effect it might have on performance, but since it would only be triggered for with-abstractions I am not too worried about that.

I will check if there are no issues in the test suite and if not, create a PR.

Hm, actually we already trigger instance search for with-abstracted expressions in some cases:

-- #6868: trigger instance search if we inserted any instance arguments
when (any isInstance args) $ solveAwakeInstanceConstraints

See #6868. I'm not really sure what's the rationale here: why do we trigger instance search if we inserted any trailing instance arguments to the with expression, but not if there are instance constraints arising from the with-expression itself?

(Also, why are we doing reduce and instantiateFull in inferExprForWith if we will later anyway call normalise on it? Even worse, the result of reduce is not even saved!)

Another question: should there be a warning for when you do with or rewrite but there are unsolved in the given term? It seems very unlikely that there will be any abstraction happening in that case, so it would perhaps be good to notify the user of that. Of course, with is also used as a way to just pattern match on a value without doing any abstraction, so perhaps the warning would be annoying too often. (See also the venerable #704.)

@jespercockx wrote:

Even worse, the result of reduce is not even saved!

This is often intended so that errors given to the user are not containing needless unfoldings in terms.

The fix for this issue (#7122) does not resolve the regression in the case of rewrite:

open import Agda.Builtin.Equality

record Semiring : Set₁ where
  field
    Carrier   : Set
    one       : Carrier
    mul       : Carrier  Carrier  Carrier
    left-unit : (r : Carrier)  mul one r ≡ r

open Semiring {{...}}

foo : {{R : Semiring}} (r : Carrier)  mul one r ≡ r
foo r rewrite left-unit r = refl

Error:

Semiring.mul R (Semiring.one R) r != r of type Semiring.Carrier R
when checking that the expression refl has type
Semiring.mul R (Semiring.one R) r ≡ r

I don't seem to be able to reopen this issue though.

This issue is already reported as fixed in the release notes of 2.6.4.2, so we need a new issue.