Add Fractional instance for AsFractional
AsFractional
admits a very obvious Fractional
instance in the case where it's of the form AsFractional (Φ q) Φ q
. This commit defines that instance, which would help simplify proofs in #923.
Search issue
This loops with as_fractional_fractional
in cases where the typeclass search can't progress in any better way. I've tried to figure out how to prevent it, and the most obvious way would be to remove the typeclass coercion on as_fractional_fractional
.
I think doing so makes sense: the only case where the user would want this kind of coercion (and indeed the only place in the Iris codebase where it is used, AFAIK), is when there's an AsFractional
in the environment and the user doesn't want to manually destruct it to apply the Fractional
inside. I wouldn't expect to go from a Fractional
goal backwards to an AsFractional
with some evars, for example - but maybe this search behaviour is secretly necessary somewhere?
I would expect that the above case can somehow be handled with a less aggressive hint that doesn't allow for evars, but I couldn't figure out how to define it.
Merge request reports
Activity
- Resolved by Isaac van Bakel
I guess it makes sense that if you have
Fractional
for an abstract predicate in your context you don't want to give anAsFractional
instance.Declaring this instance using
Hint Immediate
might be better. (But, there are some issues withHint Immediate
).Could we have a timing CI run to see this does not cause spurious
Fractional
searches that slow things down.Edited by Robbert KrebbersFuture timing result links:
The lambda-rust job diverged. Looks like we do have infinite TC search indeed.
This loops with
as_fractional_fractional
in cases where the typeclass search can't progress in any better way. I've tried to figure out how to prevent it, and the most obvious way would be to remove the typeclass coercion onas_fractional_fractional
.I guess the question is, when do we need that
Fractional
instance? We could make itLocal
to that file. I don't know when it is needed outside, e.g. ↦ has its ownFractional
instance so we wouldn't want it to useas_fractional_fractional
.It took me some time to understand the problem in the merge request, but yes, the current MR's approach of having instances for
Fractional -> AsFractional
and vice versa is bad. My suggestion to use aHint Immediate
for the newFractional -> AsFractional
instance would solve that.Removing
as_fractional_fractional
could also be an option. It will break proofs such as:Global Instance into_sep_fractional P P1 P2 Φ q1 q2 : AsFractional P Φ (q1 + q2) → AsFractional P1 Φ q1 → AsFractional P2 Φ q2 → IntoSep P P1 P2. Proof. intros. rewrite /IntoSep [P]fractional_split //. Qed.
(These instances should not also have a
Fractional
premise, that would cause redundant search.)I think proofs such as the above can easily be fixed by having
as_fractional_fractional
as a local instance, or justdestruct
ingAsFractional
. (I prefer the latter.)Edited by Robbert KrebbersSo the most minimal solution (in terms of change size) is to take out the coercion on
as_fractional_fractional
and replace it with aLocal Hint Immediate
to maintain the proofs in the file.Interestingly, the
Hint Mode
is intended foras_fractional_fractional
, but also affects some other proofs. Marking itLocal
breaks at leastghost_var_combine_as
iniris/base_logic/lib/ghost_var.v
, which seems to not progress on typeclass instances because it doesn't match the only otherHint Mode
forAsFractional
.That file is also interesting because it manually declares an
AsFractional
instance based on aFractional
one, just slightly simplified i.e. the type has aFractional (λ q, Φ q)
instance, and also anAsFractional (Φ q) (λ q, Φ q) q
instance (compare toAsFractional ((λ q, Φ q) q) (λ q, Φ q) q
fromfractional_as_fractional
.) Removing the manually-declaredAsFractional
instance also breaks the proof.
added 2 commits
8 8 9 9 Class AsFractional {PROP : bi} (P : PROP) (Φ : Qp → PROP) (q : Qp) := { 10 10 as_fractional : P ⊣⊢ Φ q; 11 as_fractional_fractional :> Fractional Φ 11 as_fractional_fractional : Fractional Φ 12 12 }. 13 13 Global Arguments AsFractional {_} _%I _%I _%Qp. 14 14 15 15 Global Arguments fractional {_ _ _} _ _. 16 16 17 17 Global Hint Mode AsFractional - ! - - : typeclass_instances. 18 (* To make [as_fractional_fractional] a useful instance, we have to 19 allow [q] to be an evar. The head of [Φ] will always be a λ so ! is 20 not a useful mode there. *) 21 18 Global Hint Mode AsFractional - - + - : typeclass_instances. - Comment on lines -18 to 21
The comment still seems relevant if the hint stays, even if
as_fractional_fractional
no longer is an instance?Should the hint be made
Local
?EDIT: I saw you wrote that breaks other proofs. That means we need to investigate.
Edited by Ralf Jung Looks like this instance here also relies on using
AsFractional
'backwards'Global Instance combine_sep_fractional_bwd P P1 P2 Φ q1 q2 : AsFractional P1 Φ q1 → AsFractional P2 Φ q2 → AsFractional P Φ (q1 + q2) → CombineSepAs P1 P2 P | 50.
Specifically the third premise has a known
Φ
andq1+q2
and tries to compute theP
.This could in principle use
Φ (q1 + q2)
as the solution but I guess that also doesn't always work. But experience also says using the same typeclass forwards and backwards will cause problems long-term...Edited by Ralf JungLooking at the
AsFractional
instances, I think usingΦ (q1 + q2)
here should be fine. I agree that it is probably better to only useAsFractional
forwards.Also, if backwards is still wanted, I think it should have
Hint Mode AsFractional - - + !
? Since theq
is also used as an input for computingP
.The more restrictive hint mode breaks the
mapsto_evar_iSplit
test, which relies on a backwards search with an unknownq
. Why it's not in head-normal form in that test, I'm not exactly sure.That test is the only example in the codebase which is affected by restricting the hint mode in this way.
!928 (merged) should remove our reliance on backwards AsFractional.
- Resolved by Isaac van Bakel
34 31 by setoid_rewrite Hequiv. 35 32 Qed. 36 33 34 (* This hint is local because it is used in these proofs, but will loop 35 with [fractional_as_fractional] at the end of this file. It's not a 36 useful hint outside of this file (and inside this file, could be Nevermind, I must be missing something: duplicating the
Fractional
instance seems the regular pattern (also in our codebase). This change will break clients who haveLocal Instance ... : Fractional Foo ... Global Instance AsFractional (Foo q) Foo q
or the like, but they don't seem common.A lot of the duplication seems to be redex-elimination in the head of
AsFractional
, that is of the form:Instance my_pred_fractional : Fractional (λ q, Φ q) := ... Instance my_pred_as_fractional : AsFractional (Φ q) (λ q, Φ q) q := ...
I don't have a lot of experience with the typeclass machinery, but it seems to be that the second instance should be totally redundant off the back of the first, and there's no clear reason to support a module declaring both.
there's no clear reason to support a module declaring both.
Yes but all existing clients do that. And take more interesting examples like
Instance my_pred_fractional : Fractional (λ q, Φ q v) := ... Instance my_pred_as_fractional : AsFractional (Φ q v) (λ q, Φ q v) q := ...
or
mapsto
:mapsto_fractional l v :> Fractional (λ (q : Qp), mapsto l (DfracOwn q) v); mapsto_as_fractional l q v :> AsFractional (mapsto l (DfracOwn q) v) (λ q, mapsto l (DfracOwn q) v) q;
Clearly your instance can unify (up to beta-reduction), but would Coq find that solution? And whichever the answer, is that reliable?
Very roughly, unification modulo beta-reduction is undecidable and practical approximations are complex — but Coq does implement some approximation, and AFAIK that's not fully specified (unless you count https://dl.acm.org/doi/10.1145/2784731.2784751 and https://www.cambridge.org/core/journals/journal-of-functional-programming/article/comprehensible-guide-to-a-new-unifier-for-cic-including-universe-polymorphism-and-overloading/19A095CA0645F89A772B7E2B7B3D92B2 — but those describe neither of the two unification algorithms in Coq, just a cleaned up version).
We should decide whether we expect clients to always declare both
Fractional
andAsFractional
instances, and then document that here. From existing practice it seems like the answer is yes we do. @robbertkrebbers you don't happen to remember how this was originally intended?Note that @jjourdan developed the Fractional interface, see !23 (merged), so I don't remind the original intent.
Since we're all at the Iris workshop this week, let's discuss this in person.
226 230 by rewrite (comm _ Q (Φ q0)) !assoc (comm _ (Φ _)). 227 231 - move=>-[-> _]->. by rewrite bi.intuitionistically_if_elim -fractional Qp.div_2. 228 232 Qed. 233 234 (* Instance for allowing the use of [AsFractional]-using lemmas with just a 235 [Fractional] instance, doing the obvious conversion. *) 236 Global Instance fractional_as_fractional Φ q `{!Fractional Φ} : 237 AsFractional (Φ q) Φ q. With this new instance, it seems you'd need to remove your own
AsFractional
instances iffractional_as_fractional
triggers, which depends on higher-order unification (HOU). I honestly thought avoiding HOU was the entire point we bothered writingAsFractional
instances — even if the boilerplate for the latter is annoying.Is there a simple and reliable way to test if my own instance is redundant? Is it enough to write the following?
Global Instance ... AsFractional Foo ... Proof. Fail apply _.
Usually the answer is "obviously yes" but I've never become familiar with HOU's pitfalls (and I venture
nobody else hasmany other Iris users haven't either).Edited by Paolo G. Giarrussochanged this line in version 3 of the diff
it seems you'd need to remove your own
AsFractional
instances iffractional_as_fractional
triggersAh yeah, we are risking overlapping instances here... shouldn't it basically be: if your own instances matches the pattern
Φ q
then don't add it?This becomes tricky with generic instances though, as per the usual diamond problem. In particular
mul_as_fractional_l/r
... they keep theP
the same so we can tryfractional_as_fractional
both before and after themul_
instance, so ifP = Φ q
there might be duplicated search?-
AsFractional (Φ q) ?Φ' ?q'
can havefractional_as_fractional
applied, then we search forFractional Φ
- If that fails we apply
mul_as_fractional_l
, leading to a new goalAsFractional (Φ q) ?Φ'' (?q'' + ?q''')
, which can again havefractional_as_fractional
applied (ifq
unifies with?q'' + ?q'''
) - and then again for
mul_as_fractional_r
Though honestly that seems mostly a problem of the
mul_
instances? They are not making any progress of the main input to the search (P
), hence leading to an exploding search tree.-
added 1 commit
- edff3e1a - Move instance out of scope of looping co-instance
My general concern (as a user): this MR reduces boilerplate but risks decreasing reliability with its general
AsFractional
instance, while Iris usually goes the opposite direction. How big is the risk depends on internals of higher-order unification (HOU) which are intentionally under-specified (and possibly evolving?): will the new instance sometimes unify unexpectedly, or is it limited to unary predicates? If somebody understands the answer, great.I personally don't understand those details, but I've seen HOU get (seldom) in the way of other solutions, like https://github.com/bedrocksystems/BRiCk/blob/master/theories/lang/bi/spec/frac_splittable.v.
cc @janno I guess.
mentioned in merge request !932 (merged)
What is the relation between this MR and !928 (merged)?
Is !928 (merged) supposed to subsume this MR?
!928 (merged) contains the (hopefully) non-controversial parts of this MR. The only thing missing there is
fractional_as_fractional
, where we still have to figure out whether we want that instance or not.!928 (merged) should go first either way.
Indeed. Since !923 (merged) no longer has a motivating use for
fractional_as_fractional
, and there seems to be a compelling reason to not add that instance (namely, the HOU issues described above), !928 (merged) exists to divorce the good typeclasses changes from the instance.I'm leaning towards not adding
fractional_as_fractional
now, based on the problem with HOU. It seems an annoying footgun to have to remember to declare your ownAsFractional
instance for yourFractional
predicate whenever Coq isn't able to derive it (which is slightly unclear.) The alternative approach whereFractional
becomes an alias described here sounds more compelling - there would be no ambiguity on the user side about whether or not to add an instance in that case.mentioned in merge request !923 (merged)
Since the author no longer sees a use in this MR (@ivanbakel writes "I'm leaning towards not adding
fractional_as_fractional
now"), I am closing it.If someone wants to experiment with any of the proposals by @jjourdan in !928 (comment 92158) it makes most sense to open a new MR. (I would be interested if someone tries the third option, remove
Fractional
altogether, but won't have time to try it myself.)