Theory Prisms

section ‹Prisms›

theory Prisms
  imports Lenses
begin

subsection ‹ Signature and Axioms ›

text ‹Prisms are like lenses, but they act on sum types rather than product types~cite‹"Gibbons17"›.
  See \url{https://hackage.haskell.org/package/lens-4.15.2/docs/Control-Lens-Prism.html}
  for more information.›

record ('v, 's) prism =
  prism_match :: "'s ⇒ 'v option" (‹matchı›)
  prism_build :: "'v ⇒ 's" (‹buildı›)

type_notation
  prism (infixr ‹⟹△› 0)

locale wb_prism =
  fixes x :: "'v ⟹△ 's" (structure)
  assumes match_build: "match (build v) = Some v"
  and build_match: "match s = Some v ⟹ s = build v"
begin

  lemma build_match_iff: "match s = Some v ⟷ s = build v"
    using build_match match_build by blast

  lemma range_build: "range build = dom match"
    using build_match match_build by fastforce

  lemma inj_build: "inj build"
    by (metis injI match_build option.inject)

end

declare wb_prism.match_build [simp]
declare wb_prism.build_match [simp]

subsection ‹ Co-dependence ›

text ‹ The relation states that two prisms construct disjoint elements of the source. This
  can occur, for example, when the two prisms characterise different constructors of an
  algebraic datatype. ›

definition prism_diff :: "('a ⟹△ 's) ⇒ ('b ⟹△ 's) ⇒ bool" (infix ‹∇› 50) where
[lens_defs]: "prism_diff X Y = (range build⇘X⇙ ∩ range build⇘Y⇙ = {})"

lemma prism_diff_intro:
  "(⋀ s1 s2. build⇘X⇙ s1 = build⇘Y⇙ s2 ⟹ False) ⟹ X ∇ Y"
  by (auto simp add: prism_diff_def)

lemma prism_diff_irrefl: "¬ X ∇ X"
  by (simp add: prism_diff_def)

lemma prism_diff_sym: "X ∇ Y ⟹ Y ∇ X"
  by (auto simp add: prism_diff_def)

lemma prism_diff_build: "X ∇ Y ⟹ build⇘X⇙ u ≠ build⇘Y⇙ v"
  by (simp add: disjoint_iff_not_equal prism_diff_def)

lemma prism_diff_build_match: "⟦ wb_prism X; X ∇ Y ⟧ ⟹ match⇘X⇙ (build⇘Y⇙ v) = None" 
  using UNIV_I wb_prism.range_build by (fastforce simp add: prism_diff_def)

subsection ‹ Canonical prisms ›

definition prism_id :: "('a ⟹△ 'a)" (‹1△›) where
[lens_defs]: "prism_id = ⦇ prism_match = Some, prism_build = id ⦈"

lemma wb_prism_id: "wb_prism 1△"
  unfolding prism_id_def wb_prism_def by simp

lemma prism_id_never_diff: "¬ 1△ ∇ X"
  by (simp add: prism_diff_def prism_id_def)

subsection ‹ Summation ›

definition prism_plus :: "('a ⟹△ 's) ⇒ ('b ⟹△ 's) ⇒ 'a + 'b ⟹△ 's" (infixl ‹+△› 85) 
  where
[lens_defs]: "X +△ Y = ⦇ prism_match = (λ s. case (match⇘X⇙ s, match⇘Y⇙ s) of
                                 (Some u, _) ⇒ Some (Inl u) |
                                 (None, Some v) ⇒ Some (Inr v) |
                                 (None, None) ⇒ None),
           prism_build = (λ v. case v of Inl x ⇒ build⇘X⇙ x | Inr y ⇒ build⇘Y⇙ y) ⦈"

lemma prism_plus_wb [simp]: "⟦ wb_prism X; wb_prism Y; X ∇ Y ⟧ ⟹ wb_prism (X +△ Y)"
  apply (unfold_locales)
   apply (auto simp add: prism_plus_def sum.case_eq_if option.case_eq_if prism_diff_build_match)
  apply (metis map_option_case map_option_eq_Some option.exhaust option.sel sum.disc(2) sum.sel(1) wb_prism.build_match_iff)
  apply (metis (no_types, lifting) isl_def not_None_eq option.case_eq_if option.sel sum.sel(2) wb_prism.build_match)
  done

lemma build_plus_Inl [simp]: "build⇘c +△ d⇙ (Inl x) = build⇘c⇙ x"
  by (simp add: prism_plus_def)

lemma build_plus_Inr [simp]: "build⇘c +△ d⇙ (Inr y) = build⇘d⇙ y"
  by (simp add: prism_plus_def)

lemma prism_diff_preserved_1 [simp]: "⟦ X ∇ Y; X ∇ Z ⟧ ⟹ X ∇ Y +△ Z"
  by (auto simp add: lens_defs sum.case_eq_if)

lemma prism_diff_preserved_2 [simp]: "⟦ X ∇ Z; Y ∇ Z ⟧ ⟹ X +△ Y ∇ Z"
  by (meson prism_diff_preserved_1 prism_diff_sym)

text ‹ The following two lemmas are useful for reasoning about prism sums ›

lemma Bex_Sum_iff: "(∃x∈A<+>B. P x) ⟷ (∃ x∈A. P (Inl x)) ∨ (∃ y∈B. P (Inr y))"
  by (auto)

lemma Ball_Sum_iff: "(∀x∈A<+>B. P x) ⟷ (∀ x∈A. P (Inl x)) ∧ (∀ y∈B. P (Inr y))"
  by (auto)

subsection ‹ Instances ›

definition prism_suml :: "('a, 'a + 'b) prism" (‹Inl△›) where
[lens_defs]: "prism_suml = ⦇ prism_match = (λ v. case v of Inl x ⇒ Some x | _ ⇒ None), prism_build = Inl ⦈"

definition prism_sumr :: "('b, 'a + 'b) prism" (‹Inr△›) where
[lens_defs]: "prism_sumr = ⦇ prism_match = (λ v. case v of Inr x ⇒ Some x | _ ⇒ None), prism_build = Inr ⦈"

lemma wb_prim_suml [simp]: "wb_prism Inl△"
  apply (unfold_locales)
   apply (simp_all add: prism_suml_def sum.case_eq_if)
  apply (metis option.inject option.simps(3) sum.collapse(1))
  done

lemma wb_prim_sumr [simp]: "wb_prism Inr△"
  apply (unfold_locales)
   apply (simp_all add: prism_sumr_def sum.case_eq_if)
  apply (metis option.distinct(1) option.inject sum.collapse(2))
  done

lemma prism_suml_indep_sumr [simp]: "Inl△ ∇ Inr△"
  by (auto simp add: lens_defs)

lemma prism_sum_plus: "Inl△ +△ Inr△ = 1△"
  unfolding lens_defs prism_plus_def by (auto simp add: Inr_Inl_False sum.case_eq_if)

subsection ‹ Lens correspondence ›

text ‹ Every well-behaved prism can be represented by a partial bijective lens. We prove 
  this by exhibiting conversion functions and showing they are (almost) inverses. ›

definition prism_lens :: "('a, 's) prism ⇒ ('a ⟹ 's)" where
"prism_lens X = ⦇ lens_get = (λ s. the (match⇘X⇙ s)), lens_put = (λ s v. build⇘X⇙ v) ⦈"

definition lens_prism :: "('a ⟹ 's) ⇒ ('a, 's) prism" where
"lens_prism X = ⦇ prism_match = (λ s. if (s ∈ 𝒮⇘X⇙) then Some (get⇘X⇙ s) else None)
                , prism_build = create⇘X⇙ ⦈"

lemma mwb_prism_lens: "wb_prism a ⟹ mwb_lens (prism_lens a)"
  by (simp add: mwb_lens_axioms_def mwb_lens_def weak_lens_def prism_lens_def)

lemma get_prism_lens: "get⇘prism_lens X⇙ = the ∘ match⇘X⇙"
  by (simp add: prism_lens_def fun_eq_iff)

lemma src_prism_lens: "𝒮⇘prism_lens X⇙ = range (build⇘X⇙)"
  by (auto simp add: prism_lens_def lens_source_def)

lemma create_prism_lens: "create⇘prism_lens X⇙ = build⇘X⇙"
  by (simp add: prism_lens_def lens_create_def fun_eq_iff)

lemma prism_lens_inverse:
  "wb_prism X ⟹ lens_prism (prism_lens X) = X"
  unfolding lens_prism_def src_prism_lens create_prism_lens get_prism_lens
  by (auto intro: prism.equality simp add: fun_eq_iff domIff wb_prism.range_build)

text ‹ Function @{const lens_prism} is almost inverted by @{const prism_lens}. The $put$
  functions are identical, but the $get$ functions differ when applied to a source where
  the prism @{term X} is undefined. ›

lemma lens_prism_put_inverse:
  "pbij_lens X ⟹ put⇘prism_lens (lens_prism X)⇙ = put⇘X⇙"
  unfolding prism_lens_def lens_prism_def
  by (auto simp add: fun_eq_iff pbij_lens.put_is_create)

lemma wb_prism_implies_pbij_lens:
  "wb_prism X ⟹ pbij_lens (prism_lens X)"
  by (unfold_locales, simp_all add: prism_lens_def)

lemma pbij_lens_implies_wb_prism:
  assumes "pbij_lens X" 
  shows "wb_prism (lens_prism X)"
proof (unfold_locales)
  fix s v
  show "match⇘lens_prism X⇙ (build⇘lens_prism X⇙ v) = Some v"
    by (simp add: lens_prism_def weak_lens.create_closure assms)
  assume a: "match⇘lens_prism X⇙ s = Some v"
  show "s = build⇘lens_prism X⇙ v"
  proof (cases "s ∈ 𝒮⇘X⇙")
    case True
    with a assms show ?thesis 
      by (simp add: lens_prism_def lens_create_def, 
          metis mwb_lens.weak_get_put pbij_lens.put_det pbij_lens_mwb)
  next
    case False
    with a assms show ?thesis by (simp add: lens_prism_def)
  qed
qed

ML_file ‹Prism_Lib.ML›

end