-
Notifications
You must be signed in to change notification settings - Fork 116
Add rcorres definition, lemmas, and automation
#959
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
base: master
Are you sure you want to change the base?
Conversation
|
The |
70cd9c9 to
c03b79f
Compare
Signed-off-by: Michael McInerney <michael.mcinerney@proofcraft.systems>
This adds rcorres, an approach to correspondence with general relations. Many lemmas to use this concept are included, as well as a lightweight method that is very similar to wpsimp. Signed-off-by: Michael McInerney <michael.mcinerney@proofcraft.systems>
c750327 to
3126b6a
Compare
|
Now including the Instead of targeting the other PR with Lib rules, this now targets |
lib/RCorres.thy
Outdated
| Corres_UL DetWPLib SubMonadLib | ||
| begin | ||
|
|
||
| text \<open>An approach to correspondence with general relations\<close> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit terse, which is the Isabelle tradition, but we're trying not to do that :-)
As a heading (eg. section) it is fine, but it should be followed by a bit more text that describes what it does and what it is intended for (can be a forward reference to examples after the defs/lemmas are there).
lib/RCorres.thy
Outdated
| assumes det: "\<And>s'. det_wp (\<lambda>s. R s s') f" | ||
| assumes valid: "\<And>rv s t. (rv, t) \<in> fst (f s) \<Longrightarrow> \<lbrace>\<lambda>s'. R s s'\<rbrace> f' \<lbrace>\<lambda>rv' t'. R' rv rv' t t'\<rbrace>" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're doing using assms you don't need to name them:
| assumes det: "\<And>s'. det_wp (\<lambda>s. R s s') f" | |
| assumes valid: "\<And>rv s t. (rv, t) \<in> fst (f s) \<Longrightarrow> \<lbrace>\<lambda>s'. R s s'\<rbrace> f' \<lbrace>\<lambda>rv' t'. R' rv rv' t t'\<rbrace>" | |
| assumes "\<And>s'. det_wp (\<lambda>s. R s s') f" | |
| assumes "\<And>rv s t. (rv, t) \<in> fst (f s) \<Longrightarrow> \<lbrace>\<lambda>s'. R s s'\<rbrace> f' \<lbrace>\<lambda>rv' t'. R' rv rv' t t'\<rbrace>" |
lib/RCorres.thy
Outdated
| using assms | ||
| apply (clarsimp simp: rcorres_def det_wp_def) | ||
| apply (rename_tac s s' rv' t') | ||
| apply (drule_tac x=s' in meta_spec) | ||
| apply (drule_tac x=s in spec) | ||
| apply (force simp: det_wp_def rcorres_def valid_def) | ||
| done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consideration:
| using assms | |
| apply (clarsimp simp: rcorres_def det_wp_def) | |
| apply (rename_tac s s' rv' t') | |
| apply (drule_tac x=s' in meta_spec) | |
| apply (drule_tac x=s in spec) | |
| apply (force simp: det_wp_def rcorres_def valid_def) | |
| done | |
| using assms | |
| by (force simp: rcorres_def det_wp_def valid_def) |
works, but is slow. The by means it doesn't matter much that it is slow, but it's still a trade-off. Personally I like to avoid instantiations when I can, but it is mostly a matter of preference, I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like avoiding instantiations, too, and if you're happy with slow proofs in Lib, I'll go with your suggestion. I sometimes try to imagine how much an extra 2 seconds in Lib that's run all the time will affect the overall time these proofs will run in the future. But I guess that's already a lost cause.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't affect the wall time of a run of Lib -- because of the by the force will run in parallel with the rest and we rarely actually use all cores. This one only affects CPU time and potential impatience :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I’ll go with the by, thanks
| lemma rcorres_req: | ||
| "\<lbrakk>\<And>s s'. R s s' \<Longrightarrow> F; F \<Longrightarrow> rcorres R f f' R'\<rbrakk> \<Longrightarrow> rcorres R f f' R'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does req stand for? Not that I have a better name. The normal corres rules have something similar with corres_gen_asm. Not quite the same, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually not sure what req stands for. I'm just copying the format of corres_req, which also has just a plain schematic in the guard and then produces a new assumption. I would only use this rule when the precondition is schematic and I'm doing a bit of forwards reasoning, and it works nicely for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that's where it comes from. I don't think I have used that rule yet. The req is a bit obscure, I'm not sure we should proliferate it, but I also don't have a good suggestion. @Xaphiosis or @corlewis do you have any ideas on this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do also have monadic_rewrite_req in the other PR, so yeah we should definitely decide on that before we go futher.
| \<Longrightarrow> rcorres R f f' R'" | ||
| by (fastforce simp: rcorres_def) | ||
|
|
||
| lemma rcorres_req: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest adding the following rule for when you don't want to spell out F:
lemma rcorres_assume_pre:
"⟦⋀s s'. R s s' ⟹ rcorres R f f' R'⟧ ⟹ rcorres R f f' R'"
by (rule rcorres_req)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to put an and (%_ _. F) or something in there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no F at all in this rule (this is not a replacement of the _req rule, but an additional rule)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh OK. I think I have seen rules like this before, but they're not something I've used. I'll add this one in, thanks.
| lemma rcorres_assert_l: | ||
| "\<lbrakk>\<And>s s'. R s s' \<Longrightarrow> P; rcorres (\<lambda>s s'. R s s' \<and> P) (b ()) c R'\<rbrakk> | ||
| \<Longrightarrow> rcorres R (assert P >>= b) c R'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this behaves nicely in backward reasoning -- requiring R s s' ==> P means that you already need to have a fully instantiated R that is also strong enough for P.
I'd have expected something like
lemma rcorres_assert_l:
"⟦P ⟹ rcorres R (b ()) c R'⟧ ⟹ rcorres (λs s'. R s s' ∧ P) (assert P >>= b) c R'"
by (fastforce intro: rcorres_assume_pre)
Maybe we do want both, though. Your formulation is nice at the beginning for sp-style reasoning when you have a bunch of asserts at the beginning of a function that you want to remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be using this one for forwards reasoning. I could definitely look to add backwards versions of many of the rules I have here.
Early on when we were discussing rcorres, you said that I shouldn't try to write a complete rule set for it, so the rules I have here are all actually used. But I can look to flesh things out some more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have a backwards rule, you can immediately use it after corres_pre even if you are going forward, so I would usually write the more general rule.
| lemma rcorres_assert_r: | ||
| "\<lbrakk>\<And>s s'. R s s' \<Longrightarrow> P; rcorres (\<lambda>s s'. R s s' \<and> P) a (c ()) R'\<rbrakk> | ||
| \<Longrightarrow> rcorres R a (assert P >>= c) R'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(same as for assert_l)
| lemma rcorres_return_stateAssert: | ||
| "rcorres (\<lambda>s s'. R' s s' \<and> P s') (return ()) (stateAssert P []) (\<lambda>_ _. R')" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works (and I would keep it), but do we also want an assert_l/assert_r style rule for stateAssert? It's a bit more awkward in backwards style, because of the state dependency, so you might only do something like:
lemma rcorres_stateAssert_l:
"⟦P ⟹ rcorres R (b ()) c R'⟧ ⟹ rcorres (λs s'. R s s' ∧ P s) (stateAssert P xs >>= b) c R'"
by (fastforce intro: rcorres_assume_pre)
That seems roughly equivalent to what we get in the return_stateAssert rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the P depends on the abstract state, the P in the assumptions for this rule doesn't type check. If you just remove the P, it works, it would then be exactly like corres_stateAssert_r, so I guess that's nice and maybe what we'd like. I think that if we want to add the asserted property to the guard, then we'd need to use the forwards style rule, which has the analogue in corres world of corres_stateAssert_add_assertion. Or maybe my brain isn't sufficiently backwards(-style) and I'm wrong.
lib/RCorres.thy
Outdated
| method rcorres uses wp simp declares rcorres rcorres_lift = | ||
| wp_pre, | ||
| ((rcorres_step rcorres: rcorres rcorres_lift: rcorres_lift wp: wp simp: simp)+)[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also add rcorres_del here as a parameter and pass it through to rcorres_step, since rcorres will be the main method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yep, that was a bad oversight. Thanks.
| rule rcorres_weaken_pre, | ||
| (rule rcorres_lift, (solves \<open>rule rule\<close> | solves \<open>wpsimp wp: wp simp: simp\<close>)+)[1], | ||
| solves solve | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The methods look good, we should add a few examples in an experiment sections for showing intended use. You can also add an RCorres_Test file instead if you don't want to clutter up the theory here, but I think it's probably small enough.
lib/RCorres.thy
Outdated
| lemma rcorres_gets_the: | ||
| "\<lbrakk>\<forall>rv rv' s s'. P s s' \<and> f s = Some rv \<and> f' s' = Some rv' \<longrightarrow> rrel rv rv'; | ||
| \<And>s'. no_ofail (\<lambda>s. P s s') f \<rbrakk> | ||
| \<Longrightarrow> rcorres P (gets_the f) (gets_the f') (\<lambda>rv rv' _ _. rrel rv rv')" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also an sp-style rule. Again, I would keep it, because it is useful at the beginning of functions, but I'd also provide a wp rule where the post condition is just Q and the precondition is some transformation of Q. (Probably just Q rv rv' s s' with some conjunction about rrel rv rv', but I haven't investigated further).
lib/RCorres.thy
Outdated
| done | ||
|
|
||
| lemma rcorres_gets_the: | ||
| "\<lbrakk>\<forall>rv rv' s s'. P s s' \<and> f s = Some rv \<and> f' s' = Some rv' \<longrightarrow> rrel rv rv'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| "\<lbrakk>\<forall>rv rv' s s'. P s s' \<and> f s = Some rv \<and> f' s' = Some rv' \<longrightarrow> rrel rv rv'; | |
| "\<lbrakk>\<forall>rv rv' s s'. P s s' --> f s = Some rv --> f' s' = Some rv' \<longrightarrow> rrel rv rv'; |
We should almost always write implications as nested implications, not as conjunctions. The difference is that nested implications can be solved step-wise, whereas if it is a conjunction, you have to do all in one go. Automation deals better with the first kind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would in this case probably make them all meta implications anyway, i.e.
[| !!rv rv' s s'. [| P s s'; f s = Some rv; f' s' = Some rv' |] ==> rrel rv rv'; ...
| lemma rcorres_rrel: | ||
| "\<lbrakk>rcorres P (gets_the f) (gets_the g) (\<lambda>rv rv' _ _. rrel rv rv'); P s s'; | ||
| f s = Some rv; g s' = Some rv'\<rbrakk> | ||
| \<Longrightarrow> rrel rv rv'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting rule. I have the feeling there is something more general lurking here, some equivalent of use_valid for normal wp. Probably something like:
"⟦rcorres P f g Q; P s s'; (rv, t) : f s; (rv', t') : g s'⟧ ⟹ Q rv rv' s s'"
You could then use that to prove the gets_the variant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would require determinacy for the function f. This will still give us the gets_the variant, so I guess it's OK. Shall I go with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stating det_wp in the assumptions for f is not quite what we want, instead it should be fst (f s) = { (rv, t) }, so that's what I'd put, if we wanted this.
lib/RCorres.thy
Outdated
| lemma rcorres_split_gets_the: | ||
| "\<lbrakk>rcorres R (gets_the f) (gets_the g) (\<lambda>rv rv' _ _. rrel rv rv'); | ||
| \<And>s'. no_ofail (\<lambda>s. R s s') f; | ||
| \<And>rv rv'. | ||
| rcorres | ||
| (\<lambda>s s'. R s s' \<and> f s = Some rv \<and> g s' = Some rv' \<and> rrel rv rv' ) | ||
| (b rv) (d rv') R'\<rbrakk> | ||
| \<Longrightarrow> rcorres R (gets_the f >>= b) (gets_the g >>= d) R'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The shared R between the conclusion and the rcorres and no_ofail assumption means you can only use this in forward reasoning at the beginning. If you write it as
| lemma rcorres_split_gets_the: | |
| "\<lbrakk>rcorres R (gets_the f) (gets_the g) (\<lambda>rv rv' _ _. rrel rv rv'); | |
| \<And>s'. no_ofail (\<lambda>s. R s s') f; | |
| \<And>rv rv'. | |
| rcorres | |
| (\<lambda>s s'. R s s' \<and> f s = Some rv \<and> g s' = Some rv' \<and> rrel rv rv' ) | |
| (b rv) (d rv') R'\<rbrakk> | |
| \<Longrightarrow> rcorres R (gets_the f >>= b) (gets_the g >>= d) R'" | |
| lemma rcorres_split_gets_the: | |
| "\<lbrakk>rcorres R1 (gets_the f) (gets_the g) (\<lambda>rv rv' _ _. rrel rv rv'); | |
| \<And>s'. no_ofail P f; | |
| \<And>rv rv'. | |
| rrel rv rv' ==> rcorres | |
| R2 | |
| (b rv) (d rv') R'\<rbrakk> | |
| \<Longrightarrow> rcorres (R1 and (%s _. P s) and (%s s'. f s = Some rv --> g s' = Some rv' --> rrel rv rv' --> R2 s s') (gets_the f >>= b) (gets_the g >>= d) R'" |
you can also use it in the middle of a proof (might be slightly messed up, GitHub is not the best place to type formulas).
We might also a variant of this, where the gets_the rule is already applied to the first assumption, not sure.
lib/RCorres.thy
Outdated
| lemma rcorres_if_r: | ||
| "\<lbrakk>b \<Longrightarrow> rcorres R f f' R'; \<not> b \<Longrightarrow> rcorres R f g' R'\<rbrakk> \<Longrightarrow> rcorres R f (if b then f' else g') R'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These also only work for instantiated preconditions. You'd generally want the preconditions of the two branches to be independent and the precondition of the entire if the conjunction of the two.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(We should also add an _l rule)
| lemma rcorres_conj_lift: | ||
| "\<lbrakk>\<And>s'. det_wp (\<lambda>s. P s s') f; rcorres Q f f' R'; rcorres R f f' Q'\<rbrakk> | ||
| \<Longrightarrow> rcorres | ||
| (\<lambda>s s'. P s s' \<and> Q s s' \<and> R s s') | ||
| f f' (\<lambda>rv rv' s s'. R' rv rv' s s' \<and> Q' rv rv' s s')" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The det_wp in this one is surprising, I'd have thought it would work without. Is there a similar rule that would work without it? Not saying we should get rid of this one, just trying to get an intuition. (I see it's a theme in the other rules as well)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the argument about not having a conj lift rule for exs_valid, or not being able to go from (Ex x. P x) /\ (Ex x. Q x) to Ex x. P x /\ Q x. We may be able to have one state that satisfies R' and then perhaps another state that satisfies Q', but not one state that satisfies both. Forcing the function to be deterministic means that the unique state satisfies both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, of course, that's what it is and that is also why it doesn't behave like any of the other forms of Hoare triples -- the post condition is existential in one and universal in the other state parameter, so you get effects of both. I think that would be worth a comment somewhere, either at this rule or at the definition of rcorres. Because it's really something slightly new.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing. I could even explain there why we might not want to introduce anything about failure into rcorres itself. It might be a bit of an essay, but I could try if you'd like.
lib/RCorres.thy
Outdated
| lemma rcorres_return: | ||
| "(\<And>s s'. R s s' \<Longrightarrow> R' v v' s s') \<Longrightarrow> rcorres R (return v) (return v') R'" | ||
| by (clarsimp simp: rcorres_def return_def) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one should move down to where the bind rule is (unless it's needed before)
lsf37
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some suggestions for tweaks and a request for more documentation (mostly examples). But I think this is looking pretty nice!
It's interesting to see where it behaves exactly like Hoare triples and where it deviates -- mostly around failure. Maybe it would correspond more closely to total correctness, but I don't think it quite does that either.
Signed-off-by: Michael McInerney <michael.mcinerney@proofcraft.systems>
|
I've updated this, addressing all the comments so far, except the I'm expecting that we will want to still tweak things quite a lot, so please let me know if there are any further changes I should make to these changes. |
|
Actually, I'm not sure if |
This work should be rebased over the work Gerwin has adding the
declaringmethod, and then the automation should be updated to support thedel:feature. I will do that once that work is merged. But I thought I would make this PR now so that people could comment on it, just in case it generated a lot of thoughts/comments.