Theory Strategy_Tree_Program
theory Strategy_Tree_Program
imports "TD.Basics_side" "HOL-Library.Monad_Syntax"
begin
section ‹A typed frontend over the homogeneous vendor tree›
text ‹
‹strategy_tree› fixes query results and terminal answers to the same
carrier ‹'d› and therefore does not support a polymorphic bind.
‹strategy_program› supplies a continuation-based typed interface: a
program may produce any result type ‹'a› while its generated vendor tree
continues to use ‹'d› for solver values and contributions.
‹sp_compile_with› encodes the final result into ‹'d› and produces the vendor
tree consumed by the solver.
›
type_synonym ('x,'g,'d,'a) strategy_program =
"('a ⇒ ('x,'g,'d) strategy_tree) ⇒ ('x,'g,'d) strategy_tree"
definition sp_return :: "'a ⇒ ('x,'g,'d,'a) strategy_program" where
"sp_return a k = k a"
definition sp_bind ::
"('x,'g,'d,'a) strategy_program
⇒ ('a ⇒ ('x,'g,'d,'b) strategy_program)
⇒ ('x,'g,'d,'b) strategy_program" where
"sp_bind m f k = m (λv. f v k)"
definition sp_read_local :: "'x ⇒ ('x,'g,'d,'d) strategy_program" where
"sp_read_local x k = QueryL x k"
definition sp_read_global :: "'g ⇒ ('x,'g,'d,'d) strategy_program" where
"sp_read_global g k = QueryG g k"
definition sp_publish :: "'g ⇒ 'd ⇒ ('x,'g,'d,unit) strategy_program" where
"sp_publish g d k = Side g d (k ())"
text ‹
‹sp_read_at› reads an unknown in the solver's combined address space
\<^typ>‹'x + 'g›, dispatching \<^const>‹Inl› to ‹sp_read_local› and \<^const>‹Inr› to
‹sp_read_global› instead of fixing the constructor at the call site.
›
fun sp_read_at :: "'x + 'g ⇒ ('x,'g,'d,'d) strategy_program" where
"sp_read_at (Inl x) = sp_read_local x"
| "sp_read_at (Inr g) = sp_read_global g"
adhoc_overloading Monad_Syntax.bind == sp_bind
text ‹
‹sp_read_local›/‹sp_read_global›/‹sp_publish› build a program one effect at a time.
‹sp_lift_tree› instead embeds an already-built vendor tree -- the shape a
backend combinator produces by folding several contribution trees into one
-- as the program that runs it and continues, by recursing over the tree's
own constructors directly: an ‹Answer› becomes a pure result, a
‹QueryL›/‹QueryG› becomes a read continuing into the lifted subtree, a
‹Side› becomes a published contribution continuing into the lifted
subtree. Defined directly against the constructors, its primitive-recursion
equations are already the normal form needed by proofs about vendor trees.
›
primrec sp_lift_tree :: "('x,'g,'d) strategy_tree ⇒ ('x,'g,'d,'d) strategy_program" where
"sp_lift_tree (Answer d) k = k d"
| "sp_lift_tree (QueryL x f) k = QueryL x (λd. sp_lift_tree (f d) k)"
| "sp_lift_tree (QueryG g f) k = QueryG g (λd. sp_lift_tree (f d) k)"
| "sp_lift_tree (Side g d t) k = Side g d (sp_lift_tree t k)"
lemma traverse_rhs_sp_lift_tree [simp]:
"traverse_rhs (sp_lift_tree t k) σ = traverse_rhs (k (traverse_rhs t σ)) σ"
by (induction t arbitrary: k) (auto intro: rangeI)
lemma dep_aux_sp_lift_tree [simp]:
"dep_aux σ (sp_lift_tree t k) = dep_aux σ t ∪ dep_aux σ (k (traverse_rhs t σ))"
by (induction t arbitrary: k) (auto intro: rangeI)
lemma sides_of_rhs_sp_lift_tree [simp]:
fixes t :: "('x, 'g, 'd::bounded_semilattice_sup_bot) strategy_tree"
shows "sides_of_rhs (sp_lift_tree t k) σ
= sides_of_rhs t σ ⊔ sides_of_rhs (k (traverse_rhs t σ)) σ"
by (induction t arbitrary: k) (auto simp: Let_def sup_fun_def fun_upd_def ac_simps intro: rangeI)
text ‹
‹traverse_rhs_sp_lift_tree_mono›: environment-monotonicity of the answer
\<^const>‹sp_lift_tree› produces, not of its dependency set -- if ‹t› is
monotone in the environment, every continuation ‹k v› is monotone in the
environment, and ‹k› is monotone in the value it receives, then
‹sp_lift_tree t k› is monotone in the environment.
›
lemma traverse_rhs_sp_lift_tree_mono:
fixes t :: "('x, 'g, 'd::{order,bot}) strategy_tree"
assumes t_mono:
"⋀σ1 σ2. σ1 ≤ σ2 ⟹ traverse_rhs t σ1 ≤ traverse_rhs t σ2"
assumes k_mono_env:
"⋀v σ1 σ2. σ1 ≤ σ2 ⟹ traverse_rhs (k v) σ1 ≤ traverse_rhs (k v) σ2"
assumes k_mono_val:
"⋀σ v1 v2. v1 ≤ v2 ⟹ traverse_rhs (k v1) σ ≤ traverse_rhs (k v2) σ"
assumes le: "σ1 ≤ σ2"
shows "traverse_rhs (sp_lift_tree t k) σ1 ≤ traverse_rhs (sp_lift_tree t k) σ2"
proof -
have "traverse_rhs (k (traverse_rhs t σ1)) σ1
≤ traverse_rhs (k (traverse_rhs t σ1)) σ2"
by (rule k_mono_env[OF le])
also have "…
≤ traverse_rhs (k (traverse_rhs t σ2)) σ2"
by (rule k_mono_val[OF t_mono[OF le]])
finally show ?thesis
by simp
qed
text ‹
\<^const>‹mono_tree_deps› gives the generally applicable composition rule: the
continuation may query more unknowns as its received value grows.
\<^const>‹env_indep_deps› is also preserved, under the stronger condition that
the continuation's dependency set is independent of both its argument and
the environment.
›
text ‹
‹sp_compile› only accepts a program whose answer is already ‹'d›.
‹sp_compile_with encode› generalizes that to any ‹'a›, packing the final
answer through ‹encode›; ‹sp_compile› is its ‹id› specialization.
›
definition sp_compile_with ::
"('a ⇒ 'd) ⇒ ('x,'g,'d,'a) strategy_program ⇒ ('x,'g,'d) strategy_tree" where
"sp_compile_with encode p = p (Answer o encode)"
definition sp_compile :: "('x,'g,'d,'d) strategy_program ⇒ ('x,'g,'d) strategy_tree" where
"sp_compile p = sp_compile_with id p"
subsection ‹Compiling programs to vendor trees›
text ‹One rewrite per program constructor, each ‹simp›, so compiling a concrete program is
normalization rather than proof: the simplifier builds the tree from the program's own
syntax, and a transfer written against the program layer never has to name a tree
constructor.›
lemma sp_compile_with_bind_read_local [simp]:
"sp_compile_with encode ((sp_read_local x) ⤜ f) = QueryL x (λd. sp_compile_with encode (f d))"
by (simp add: sp_compile_with_def sp_bind_def sp_read_local_def)
lemma sp_compile_with_bind_read_global [simp]:
"sp_compile_with encode ((sp_read_global g) ⤜ f) = QueryG g (λd. sp_compile_with encode (f d))"
by (simp add: sp_compile_with_def sp_bind_def sp_read_global_def)
lemma sp_compile_with_bind_publish [simp]:
"sp_compile_with encode ((sp_publish g d) ⤜ f) = Side g d (sp_compile_with encode (f ()))"
by (simp add: sp_compile_with_def sp_bind_def sp_publish_def)
lemma sp_compile_with_return [simp]:
"sp_compile_with encode (sp_return a) = Answer (encode a)"
by (simp add: sp_compile_with_def sp_return_def)
lemma sp_compile_with_read_local [simp]:
"sp_compile_with encode (sp_read_local x) = QueryL x (Answer o encode)"
by (simp add: sp_compile_with_def sp_read_local_def)
lemma sp_compile_with_read_global [simp]:
"sp_compile_with encode (sp_read_global g) = QueryG g (Answer o encode)"
by (simp add: sp_compile_with_def sp_read_global_def)
lemma sp_compile_with_publish [simp]:
"sp_compile_with encode (sp_publish g d) = Side g d (Answer (encode ()))"
by (simp add: sp_compile_with_def sp_publish_def)
lemma sp_compile_with_bind_read_at [simp]:
"sp_compile_with encode ((sp_read_at src) ⤜ f) =
(case src of Inl x ⇒ QueryL x (λd. sp_compile_with encode (f d))
| Inr g ⇒ QueryG g (λd. sp_compile_with encode (f d)))"
by (cases src) simp_all
lemma sp_compile_with_read_at [simp]:
"sp_compile_with encode (sp_read_at src) =
(case src of Inl x ⇒ QueryL x (Answer o encode) | Inr g ⇒ QueryG g (Answer o encode))"
by (cases src) simp_all
lemma sp_compile_sp_lift_tree [simp]: "sp_compile (sp_lift_tree t) = t"
by (induction t) (simp_all add: sp_compile_def sp_compile_with_def)
subsection ‹Programs that run their continuation once›
text ‹
A \<^typ>‹('x,'g,'d,'a) strategy_program› is any function from a continuation to a
tree, so nothing stops one from ignoring its continuation, or from using it
twice. Such a program has no semantics the tree vocabulary can describe: what
it publishes and what it reads would depend on the continuation it is handed.
‹sp_wf p› names the programs that do not do this. It says ‹p› commutes with
grafting: continuing into ‹k› and then grafting ‹h› onto every leaf of the
result is the same as continuing into ‹k› already grafted. A program that
dropped its continuation would lose ‹h› on the left and keep it on the
right; one that used it twice would duplicate the grafting. Everything the
generator builds runs its continuation once, and this is the condition under
which asking a program what it publishes is a well-posed question at all.
Stating it this way rather than as ‹p = sp_lift_tree (sp_compile p)› is what
lets it apply to a transfer, whose answer type is the analysis' own ‹'dl›
rather than the solver's ‹'d›: there is no tree whose leaves carry a ‹'dl›,
so a transfer has no compilation to be recoverable from, but it commutes with
grafting all the same. For a program that does answer ‹'d› the two agree, which
is ‹sp_wfD›.
›
lemma sp_lift_tree_sp_lift_tree:
"sp_lift_tree (sp_lift_tree t g) k = sp_lift_tree t (λv. sp_lift_tree (g v) k)"
by (induction t) simp_all
definition sp_wf :: "('x,'g,'d,'a) strategy_program ⇒ bool" where
"sp_wf p ⟷ (∀k h. p (λv. sp_lift_tree (k v) h) = sp_lift_tree (p k) h)"
lemma sp_wfI:
"(⋀k h. p (λv. sp_lift_tree (k v) h) = sp_lift_tree (p k) h) ⟹ sp_wf p"
by (simp add: sp_wf_def)
lemma sp_wf_graft:
"sp_wf p ⟹ p (λv. sp_lift_tree (k v) h) = sp_lift_tree (p k) h"
by (simp add: sp_wf_def)
text ‹
The homogeneous reading: a program answering the solver's own value type is
recoverable from the tree it compiles to. This is the form every consumer
below uses, and ‹sp_compile› only accepts such a program in the first place.
›
lemma sp_wfD:
assumes "sp_wf p"
shows "p k = sp_lift_tree (sp_compile p) k"
using sp_wf_graft[OF assms, where k = Answer and h = k]
by (simp add: sp_compile_def sp_compile_with_def)
text ‹Closure under the constructors a generator actually builds with, so a
well-formedness side condition discharges by ‹simp› at the use site instead
of being threaded through as a hypothesis.›
lemma sp_wf_lift_tree [intro, simp]: "sp_wf (sp_lift_tree t)"
by (simp add: sp_wf_def sp_lift_tree_sp_lift_tree)
lemma sp_wf_return [intro, simp]: "sp_wf (sp_return a)"
by (simp add: sp_wf_def sp_return_def)
lemma sp_wf_read_local [intro, simp]: "sp_wf (sp_read_local x)"
by (simp add: sp_wf_def sp_read_local_def)
lemma sp_wf_read_global [intro, simp]: "sp_wf (sp_read_global g)"
by (simp add: sp_wf_def sp_read_global_def)
lemma sp_wf_publish [intro, simp]: "sp_wf (sp_publish g d)"
by (simp add: sp_wf_def sp_publish_def)
lemma sp_wf_read_at [intro, simp]: "sp_wf (sp_read_at src)"
by (cases src) simp_all
lemma sp_wf_bind [intro]:
assumes "sp_wf p" and "⋀v. sp_wf (f v)"
shows "sp_wf (p ⤜ f)"
proof (rule sp_wfI)
fix k h
have "(p ⤜ f) (λv. sp_lift_tree (k v) h) = p (λv. sp_lift_tree (f v k) h)"
by (simp add: sp_bind_def sp_wf_graft[OF assms(2)])
also have "… = sp_lift_tree (p (λv. f v k)) h"
by (rule sp_wf_graft[OF assms(1)])
finally show "(p ⤜ f) (λv. sp_lift_tree (k v) h) = sp_lift_tree ((p ⤜ f) k) h"
by (simp add: sp_bind_def)
qed
text ‹
Post-composing a program's answer with an encoding, so a transfer answering
‹'dl› becomes a contribution answering the packed ‹('dl,'dg) dg_state› the
generator folds. It is \<^const>‹sp_compile_with›'s program-level counterpart:
compiling it is compiling through the same encoding.
›
definition sp_map ::
"('a ⇒ 'b) ⇒ ('x,'g,'d,'a) strategy_program ⇒ ('x,'g,'d,'b) strategy_program"
where
"sp_map e p = (λk. p (λv. k (e v)))"
lemma sp_map_apply [simp]: "sp_map e p k = p (λv. k (e v))"
by (simp add: sp_map_def)
lemma sp_compile_with_sp_map [simp]:
"sp_compile_with e' (sp_map e p) = sp_compile_with (e' ∘ e) p"
by (simp add: sp_map_def sp_compile_with_def comp_def)
lemma sp_compile_sp_map [simp]: "sp_compile (sp_map e p) = sp_compile_with e p"
by (simp add: sp_compile_def sp_compile_with_def comp_def)
lemma sp_wf_map [intro, simp]: "sp_wf p ⟹ sp_wf (sp_map e p)"
by (simp add: sp_wf_def)
text ‹
What a well-formed program answers, publishes and reads, asked of the program
rather than of a tree. Each is the tree vocabulary applied to its compilation,
so the three carry over unchanged --- and the point of stating them is that a
characterization of a combinator can now speak one vocabulary on both sides,
instead of a program on the left and its contributions' trees on the right.
›
abbreviation traverse_program ::
"('x,'g,'d::bot,'d) strategy_program ⇒ ('x + 'g ⇒ 'd) ⇒ 'd"
where "traverse_program p σ ≡ traverse_rhs (sp_compile p) σ"
abbreviation sides_of_program ::
"('x,'g,'d::bounded_semilattice_sup_bot,'d) strategy_program
⇒ ('x + 'g ⇒ 'd) ⇒ 'x + 'g ⇒ 'd"
where "sides_of_program p σ ≡ sides_of_rhs (sp_compile p) σ"
abbreviation dep_program ::
"('x + 'g ⇒ 'd::bot) ⇒ ('x,'g,'d,'d) strategy_program ⇒ ('x + 'g) set"
where "dep_program σ p ≡ dep_aux σ (sp_compile p)"
text ‹Running a well-formed program under any continuation: its own effects
happen, then the continuation runs on the value it answered.
Left bare. Their left-hand sides are ‹f (p k) σ› with both ‹p› and ‹k›
schematic, which is not a higher-order pattern: as ‹simp› rules they match any
application under one of the three observers and spawn an ‹sp_wf› subgoal for
it --- including their own right-hand sides, where ‹sides_of_program p σ›
unfolds to ‹sides_of_rhs (sp_compile p) σ› and matches at ‹?p := sp_compile›.
Nothing loops while ‹sp_wf sp_compile› is unprovable by the simpset, but that is
an accident to rely on, so each use cites the rule.›
lemma traverse_rhs_sp_wf:
assumes "sp_wf p"
shows "traverse_rhs (p k) σ = traverse_rhs (k (traverse_program p σ)) σ"
by (subst sp_wfD[OF assms]) simp
lemma dep_aux_sp_wf:
assumes "sp_wf p"
shows "dep_aux σ (p k) = dep_program σ p ∪ dep_aux σ (k (traverse_program p σ))"
by (subst sp_wfD[OF assms]) simp
lemma sides_of_rhs_sp_wf:
fixes p :: "('x,'g,'d::bounded_semilattice_sup_bot,'d) strategy_program"
assumes "sp_wf p"
shows "sides_of_rhs (p k) σ
= sides_of_program p σ ⊔ sides_of_rhs (k (traverse_program p σ)) σ"
by (subst sp_wfD[OF assms]) simp
text ‹The three together: what a proof that has just exposed ‹p k› under an
observer needs, cited as one name.›
lemmas sp_wf_observes = traverse_rhs_sp_wf sides_of_rhs_sp_wf dep_aux_sp_wf
text ‹
The ‹id›-specialized counterparts of the ‹sp_compile_with_*› equations above,
stated directly against ‹sp_compile› so a proof reasoning about compiled
programs does not have to unfold ‹sp_compile_def› at every step.
›
lemma sp_compile_bind_read_local [simp]:
"sp_compile ((sp_read_local x) ⤜ f) = QueryL x (λd. sp_compile (f d))"
by (simp add: sp_compile_def)
lemma sp_compile_bind_read_global [simp]:
"sp_compile ((sp_read_global g) ⤜ f) = QueryG g (λd. sp_compile (f d))"
by (simp add: sp_compile_def)
lemma sp_compile_bind_publish [simp]:
"sp_compile ((sp_publish g d) ⤜ f) = Side g d (sp_compile (f ()))"
by (simp add: sp_compile_def)
lemma sp_compile_return [simp]: "sp_compile (sp_return a) = Answer a"
by (simp add: sp_compile_def)
lemma sp_compile_read_local [simp]: "sp_compile (sp_read_local x) = QueryL x Answer"
by (simp add: sp_compile_def)
lemma sp_compile_read_global [simp]: "sp_compile (sp_read_global g) = QueryG g Answer"
by (simp add: sp_compile_def)
lemma sp_compile_bind_read_at [simp]:
"sp_compile ((sp_read_at src) ⤜ f) =
(case src of Inl x ⇒ QueryL x (λd. sp_compile (f d))
| Inr g ⇒ QueryG g (λd. sp_compile (f d)))"
by (cases src) simp_all
lemma sp_compile_read_at [simp]:
"sp_compile (sp_read_at src) =
(case src of Inl x ⇒ QueryL x Answer | Inr g ⇒ QueryG g Answer)"
by (cases src) simp_all
text ‹
Left bare rather than ‹[simp]›: using this equation on arbitrary binds
exposes ‹m› as a raw CPS function application. The specialized equations
above preserve the program abstraction and give the simplifier the
vendor-tree constructors it needs.
›
lemma sp_compile_bind: "sp_compile (m ⤜ f) = m (λx. sp_compile (f x))"
by (simp add: sp_compile_def sp_compile_with_def sp_bind_def)
lemma sp_compile_with_bind: "sp_compile_with encode (m ⤜ f) = m (λx. sp_compile_with encode (f x))"
by (simp add: sp_compile_with_def sp_bind_def)
subsection ‹Monad laws›
text ‹The three laws, so a caller may rewrite a program before compiling it and know the
tree is unchanged. All three hold by function extensionality alone -- a program is a
continuation transformer, and ‹⤜› is composition -- so no tree-level reasoning is
involved.›
lemma sp_bind_sp_return_left [simp]: "(sp_return a) ⤜ f = f a"
by (rule ext) (simp add: sp_bind_def sp_return_def)
lemma sp_bind_sp_return_right [simp]: "m ⤜ sp_return = m"
by (rule ext) (simp add: sp_bind_def sp_return_def)
lemma sp_bind_assoc:
fixes m :: "('x, 'g, 'd, 'a) strategy_program"
and f :: "'a ⇒ ('x, 'g, 'd, 'b) strategy_program"
and g :: "'b ⇒ ('x, 'g, 'd, 'c) strategy_program"
shows "(m ⤜ f) ⤜ g = m ⤜ (λx. f x ⤜ g)"
by (rule ext) (simp add: sp_bind_def)
end