Proposal: Local Rewrites#2371
Conversation
facundominguez
left a comment
There was a problem hiding this comment.
Thanks @AlecsFerra! I shared some additional notes from our discussion.
|
|
||
| data Term where | ||
| {-@ MkId :: Prop (Term id) @-} | ||
| MkId :: Term |
There was a problem hiding this comment.
This example would work fine with non-GADT syntax, right? That is
{-@ MkId :: Prop (Term id) @-}
data Term = MkId| - Extract the rewrites during the construction of the bindings (in the | ||
| `withAssm` function in `PLE.hs`) by inspecting each binding to determine if it | ||
| was generated by a case split and extracting equalities in the same way as | ||
| described in Solution 1. |
There was a problem hiding this comment.
This solution can be broken in four steps.
First, extract equalities from bindings that are about the same terms. e.g.
{v:_ | prop v = Term id && someOtherPredicate v }
{v:_ | prop v = Term f}
{k:_ | k = Term g && k = Term h}would produce equalities
prop v = Term id
prop v = Term f
k = Term g
k = Term hSecond, infer equalities of data constructors by transitivity.
Term id = Term f
Term g = Term hThird, infer rewrites on the arguments of data constructors, obtaining
id ~> f
f ~> id
g ~> h
h ~> gFourth, discard sufficient rewrites to ensure termination of rewriting.
f ~> id
g ~> hNow f can be replaced with identity whenever found.
The fourth step is so far underspecified. Users of LF need to indicate what variables are ok to appear in lhs and the rhs of the final rewrites (hence the attempt to decide this based on whether variables are local or global).
There was a problem hiding this comment.
We don't generate the unification id ~> f as id is not a "local" and it has a definition
| was generated by a case split and extracting equalities in the same way as | ||
| described in Solution 1. | ||
|
|
||
| **Challenges:** |
There was a problem hiding this comment.
An unknown to this approach: While the criterion to cut loops is not settled, it could happen that LF needs to be more restrictive on what rewrites are accepted, because it has less context on how the rewrites are produced.
If the local rewrites were produced by LH instead, LH could produce more rewrites known to be terminating.
This point arouse when discussing the criterion to use in the fourth step of Solution 2. It could be unsafe to allow local variables in the right-hand side of rewrites, but forbidding local variables there would also discard many innocuous rewrites.
| [#705](https://github.com/ucsd-progsys/liquid-fixpoint/pull/705), but it was | ||
| removed because the solution was too dependent on the variable naming scheme | ||
| used by Liquid Haskell. This in LH is a non issue as GHC Variables are | ||
| annotated with extra information. |
There was a problem hiding this comment.
And there is a third approach that is to lean on rest-rewrite to apply the local rewrites, so we don't need to ensure termination separately.
There was a problem hiding this comment.
You can add it in the proposal the only thing I'm not sure about is if there is a way to encode lambda into REST terms
There was a problem hiding this comment.
We also mentioned that the integration to REST will be much slower because REST is checking for termination, which we do not really need in this case.
|
@AlecsFerra this is how you can understand which variables are local and which global: |
This is a strong argument for doing it inside LH |
|
hi @AlecsFerra -- sorry for the delay - i'm traveling this week, but I'll try to get you some feedback soon, thanks! Ranjit. |
|
In the mean time I've started implementing proposal 2: And the example produces the following The local rewrites and "dependant cases" case can be tried using my forks of liquid-haskell and liquid-fixpoint The only missing features at the moment are:
|
| This proposal suggests the following steps: | ||
|
|
||
| - Introduce a mechanism to provide local definitions in the `.fq` file format. | ||
| For example: `define n f = {(Example.id)}`, where `n` is the binding ID that |
There was a problem hiding this comment.
I'm just noticing that if the new define form allowed parameters, as the old form does, this mechanism could be reused to unfold local definitions as well.
For instance,
{-@ reflect map @-}
map f e = go
where
go [] = e
go (x:xs) = f x : go xs
{-@ lemma :: f:_ -> e:_ -> { map f e [] == [] } @-}
lemma _ _ = ()Oh, well, it would have to be spelled out how the binding of go is introduced when doing PLE. So it is not something we could address immediately, but I think it is an interesting derivation of the current effort.
There was a problem hiding this comment.
You're right but first we need to allow lambdas in reflected function as GHC is generating lambda when using where clauses to define functions
Edit: I was wrong is another error regarding recursive definitions
|
In the final solution, option 2 was chosen. Liquid Fixpoint now includes a flag
It is the responsibility of the user of liquid fixpoint to ensure that the rewrites do not cause infinite loops, as the surface language typically has more contextual information and can make more accurate, less conservative approximations. Currently, all the modifications required for Liquid Fixpoint, except the Horn format parser (we are waiting for a final specification), have already been merged into the The feature that utilizes local rewrites in Liquid Haskell is also |
|
The implementation has been merged in #2384 and ucsd-progsys/liquid-fixpoint#710. I'm merging this proposal for the record. |
Rendered proposal here