Theory Nats

(*<*)
(*
 * Domains of natural numbers.
 * (C)opyright 2009-2011, Peter Gammie, peteg42 at gmail.com.
 * License: BSD
 *)

theory Nats
imports HOLCF
begin
(*>*)

section‹Boxed Types, HOL's natural numbers hoisted.›

subsection‹Unboxed naturals.›

text‹We can represent the unboxed naturals as a discrete domain (every
number is equal to itself and unequal to everything else, and there is no
bottom). Think of these functions as the machine operations.

We could actually lift all HOL types and classes in this way; indeed
the HOLCF theory "Lift" does something similar, but is not as
fine-grained as this development.

Note: we default to just CPOs (not pointed CPOs) in this theory. We
adopt bothg the Isabelle syntax for overloaded arithmetic and the
notation for unboxed operators of citet"SPJ-JL:1991".›

default_sort predomain

type_synonym UNat = "nat discr"

instantiation discr :: (zero) zero
begin
definition zero_discr_def: "0  Discr 0"
instance ..
end

lemma zero_discr[simp]: "undiscr 0 = 0" unfolding zero_discr_def by simp

instantiation discr :: (one) one
begin
definition one_discr_def: "1  Discr 1"
instance ..
end

lemma one_discr[simp]: "undiscr 1 = 1" unfolding one_discr_def by simp

instantiation discr :: (ord) ord
begin
definition less_def[simp]: "m < n  (undiscr m) < (undiscr n)"
definition le_def[simp]:   "m  n  (undiscr m)  (undiscr n)"
instance ..
end

definition
  uPlus :: "UNat  UNat  UNat" where
  "uPlus  Λ x y. Discr (undiscr x + undiscr y)"

abbreviation
  uPlus_syn :: "UNat  UNat  UNat" (infixl "+#" 65) where
  "x +# y  uPlusxy"

instantiation discr :: (plus) plus
begin
definition plus_discr_def[simp]: "x + y  Discr (undiscr x + undiscr y)"
instance ..
end

definition
  uMinus :: "UNat  UNat  UNat" where
  "uMinus  Λ x y. Discr (undiscr x - undiscr y)"

abbreviation
  uMinus_syn :: "UNat  UNat  UNat" (infixl "-#" 65) where
  "x -# y  uMinusxy"

instantiation discr :: (minus) minus
begin
definition minus_discr_def[simp]: "x - y  Discr (undiscr x - undiscr y)"
instance ..
end

definition
  uMult :: "UNat  UNat  UNat" where
  "uMult  Λ x y. Discr (undiscr x * undiscr y)"

abbreviation
  uMult_syn :: "UNat  UNat  UNat" (infixl "*#" 65) where
  "x *# y  uMultxy"

instantiation discr :: (times) times
begin
definition times_discr_def[simp]: "x * y  Discr (undiscr x * undiscr y)"
instance ..
end

lemma uMult_unit_left: "1 *# (x::UNat) = x"
  unfolding uMult_def one_discr_def by simp
lemma uMult_unit_right: "(x::UNat) *# 1 = x"
  unfolding uMult_def one_discr_def by simp

lemma uMult_assoc: "(x *# y) *# z = x *# (y *# z)"
  unfolding uMult_def by simp

lemma uMult_commute: "x *# y = y *# x"
  unfolding uMult_def by simp

lemma uMult_left_commute: "a *# (b *# c) = b *# (a *# c)"
  unfolding uMult_def by simp

lemmas uMult_arithmetic =
  uMult_unit_left uMult_unit_right uMult_assoc uMult_commute uMult_left_commute

subsection ‹The "@{term }" monad.›

text‹

As Brian Huffman helpfully observed, the "@{term ""}" type
constructor supports the monadic operations; it's isomorphic to
Haskell's @{term "Maybe a"}, or ML's @{typ "'a option"}.

Note that @{term "return"} is @{term "up"}.

›

definition
  bbind :: "('a::cpo)  ('a  ('b::pcpo))  'b" where
  "bbind  Λ b g. fupgb"

abbreviation
  bbind_syn :: "('a::cpo)  ('a  ('b::pcpo))  'b" (infixl ">>=" 65) where
  "b >>= g  bbindbg"

lemma bbind_strict1[simp]: "bbind = "
  by (simp add: bbind_def)
lemma bbind_strict2[simp]: "x >>=  = "
  by (cases x, simp_all add: bbind_def)

lemma bbind_leftID[simp]: "upa >>= f = fa" by (simp add: bbind_def)
lemma bbind_rightID[simp]: "m >>= up = m" by (cases m, simp_all)

lemma bbind_assoc[simp]: "f >>= g >>= h = f >>= (Λ x. gx >>= h)"
  by (cases f, simp_all)

lemma bbind_case_distr_strict: "f =   f(g >>= h) = g >>= (Λ x. f(hx))"
  unfolding bbind_def by (cases g, simp_all)

text‹

Kleisli composition is sometimes helpful. It forms a monad too,
and has many useful algebraic properties. Unfortunately it is more
work than is useful to write the lemmas in a way that makes the
higher-order unifier in the simplifier happy. Seems easier just to
unfold the definition and go from there.

›

definition
  bKleisli :: "('a::cpo  ('b::cpo))  ('b  ('c::cpo))  ('a  'c)" where
  "bKleisli  Λ f g x. fx >>= g"

abbreviation
  bKleisli_syn :: "('a::cpo  ('b::cpo))  ('b  ('c::cpo))  ('a  'c)" (infixl ">=>" 65) where
  "b >=> g  bKleislibg"

lemma bKleisli_strict1[simp]: "bKleisli = "
  by (simp add: bKleisli_def)
lemma bKleisli_strict2[simp]: "b >=>  = "
  by (rule cfun_eqI, simp add: bKleisli_def)

lemma bKleisli_bbind: "(f >>= g) >=> h = f >>= (Λ x. gx >=> h)"
  by (cases f, simp_all)

text ‹

The "Box" type denotes computations that, when demanded, yield
either @{term ""} or an unboxed value. Note that the @{term "Box"}
constructor is strict, and so merely tags the lifted computation @{typ
"'a"}. @{typ "'a"} can be pointed or unpointed. This approach enables
us to distinguish the boxed values from the lifted-function-space that
models recursive functions with unboxed codomains.

›

domain 'a Box = Box (unbox :: "'a")

definition
  box :: "('a::predomain)  'a Box" where
  "box  Box oo up"

lemma boxI: "Box(upx) = boxx" unfolding box_def by simp
lemma unbox_box[simp]: "unbox(boxx) = upx" unfolding box_def by simp
lemma unbox_Box[simp]: "x    unbox(Boxx) = x" by simp

text‹

If we suceed in @{term "box"}ing something, then clearly that
something was not @{term ""}.

›

lemma box_casedist[case_names bottom Box, cases type: Box]:
  assumes xbot: "x =   P"
      and xbox: "u. x = boxu  P"
  shows "P"
proof(cases x)
  case bottom with xbot show ?thesis by simp
next
  case (Box u) with xbox show ?thesis
    by (cases u, simp_all add: box_def up_def cont_Iup bottomI[OF minimal_up])
qed

lemma bbind_leftID'[simp]: "unboxa >>= box = a" by (cases a, simp_all add: bbind_def)

(*<*)
text ‹

The optimisations of citet"SPJ-JL:1991".

p11: Repeated unboxing of the same value can be done once (roughly:
store the value in a register). Their story is more general.

›

lemma box_repeated:
  "x >>= (Λ x'. f >>= (Λ y'. x >>= bodyx'y'))
  = x >>= (Λ x'. f >>= (Λ y'. bodyx'y'x'))"
  by (cases x, simp_all)
(*>*)

text‹Lift binary predicates over @{typ "'a discr"} into @{typ "'a discr Box"}.›

text @{term "bliftM"} and @{term "bliftM2"} encapsulate the boxing
and unboxing.›

definition
  bliftM :: "('a::predomain  'b::predomain)  'a Box  'b Box" where
  "bliftM f  Λ x. unboxx >>= (Λ x'. box(fx'))"

lemma bliftM_strict1[simp]: "bliftM f = " by (simp add: bliftM_def)
lemma bliftM_op[simp]: "bliftM f(boxx) = box(fx)" by (simp add: bliftM_def)

definition
  bliftM2 :: "('a::predomain  'b::predomain  'c::predomain)  'a Box  'b Box  'c Box" where
  "bliftM2 f  Λ x y. unboxx >>= (Λ x'. unboxy >>= (Λ y'. box(fx'y')))"

lemma bliftM2_strict1[simp]: "bliftM2 f = " by (rule cfun_eqI)+ (simp add: bliftM2_def)
lemma bliftM2_strict2[simp]: "bliftM2 fx = " by (cases x, simp_all add: bliftM2_def)
lemma bliftM2_op[simp]: "bliftM2 f(boxx)(boxy) = box(fxy)" by (simp add: bliftM2_def)

text‹

Define the arithmetic operations. We need extra continuity lemmas as
we're using the full function space, so we can re-use the conventional
operators. The goal is to work at this level.

›

instantiation Box :: ("{predomain,plus}") plus
begin
definition plus_Box_def: "x + y  bliftM2 (Λ a b. a + b)xy"
instance ..
end

lemma plus_Box_cont[simp, cont2cont]:
  "cont g; cont h  cont (λx. (g x :: 'a :: {predomain, plus} Box) + h x)"
  unfolding plus_Box_def by simp

lemma plus_Box_strict1[simp]: " + (y :: 'a::{predomain, plus} Box) = "
  unfolding plus_Box_def by simp
lemma plus_Box_strict2[simp]: "(x :: 'a::{predomain, plus} Box) +  = "
  unfolding plus_Box_def by simp

instantiation Box :: ("{predomain,minus}") minus
begin
definition minus_Box_def: "x - y  bliftM2 (Λ a b. a - b)xy"
instance ..
end

lemma minus_Box_cont[simp, cont2cont]:
  "cont g; cont h  cont (λx. (g x :: 'a :: {predomain, minus} Box) - h x)"
  unfolding minus_Box_def by simp

lemma minus_Box_strict1[simp]: " - (y :: 'a::{predomain, minus} Box) = "
  unfolding minus_Box_def by simp
lemma minus_Box_strict2[simp]: "(x :: 'a::{predomain, minus} Box) -  = "
  unfolding minus_Box_def by simp

instantiation Box :: ("{predomain,times}") times
begin
definition times_Box_def: "x * y  bliftM2 (Λ a b. a * b)xy"
instance ..
end

lemma times_Box_cont[simp, cont2cont]:
  "cont g; cont h  cont (λx. (g x :: 'a :: {predomain, times} Box) * h x)"
  unfolding times_Box_def by simp

lemma times_Box_strict1[simp]: " * (y :: 'a::{predomain, times} Box) = "
  unfolding times_Box_def by simp
lemma times_Box_strict2[simp]: "(x :: 'a::{predomain, times} Box) *  = "
  unfolding times_Box_def by simp

definition
  bpred :: "('a::countable discr  'a discr  bool)  'a discr Box  'a discr Box  tr" where
  "bpred p  Λ x y. unboxx >>= (Λ x'. unboxy >>= (Λ y'. if p x' y' then TT else FF))"

lemma bpred_strict1[simp]: "bpred p = " unfolding bpred_def by (rule cfun_eqI, simp)
lemma bpred_strict2[simp]: "bpred px = " unfolding bpred_def by (cases x, simp_all)

lemma bpred_eval[simp]: "bpred p(boxx)(boxy) = (if p x y then TT else FF)"
  unfolding bpred_def by simp

abbreviation
  beq_syn :: "'a::countable discr Box  'a discr Box  tr" (infix "=B" 50) where
  "x =B y  bpred (=)xy"

abbreviation
  ble_syn :: "'a::{countable,ord} discr Box  'a discr Box  tr" (infix "B" 50) where
  "x B y  bpred (≤)xy"

abbreviation
  blt_syn :: "'a::{countable,ord} discr Box  'a discr Box  tr" (infix "<B" 50) where
  "x <B y  bpred (<)xy"

subsection‹The flat domain of natural numbers›

text‹Lift arithmetic to the boxed naturals. Define some things that make
playing with boxed naturals more convenient.›

type_synonym Nat = "UNat Box"

instantiation Box :: ("{predomain, zero}") zero
begin
definition zero_Nat_def: "0  box0"
instance ..
end

instantiation Box :: ("{predomain, one}") one
begin
definition one_Nat_def: "1  box1"
instance ..
end

text ‹We need to know the underlying operations are continuous for these to work.›

lemma plus_Nat_eval[simp]: "(boxx :: Nat) + boxy = box(x + y)" unfolding plus_Box_def by simp
lemma minus_Nat_eval[simp]: "(boxx :: Nat) - boxy = box(x - y)" unfolding minus_Box_def by simp
lemma times_Nat_eval[simp]: "(boxx :: Nat) * boxy = box(x * y)" unfolding times_Box_def by simp

definition
  Nat_case :: "'a::domain  (Nat  'a)  Nat  'a" where
  "Nat_case  Λ z s n. unboxn >>= (Λ n'. case_nat z (λn''. s(box(Discr n''))) (undiscr n'))"

lemma cont_case_nat[simp]:
  "cont (λx. f x); n. cont (λx. g x n)   cont (λx. case_nat (f x) (g x) n)"
  by (cases n, simp_all)

lemma Nat_case_strict[simp]: "Nat_casezs = " by (simp add: Nat_case_def)
lemma Nat_case_zero[simp]: "Nat_casezs0 = z" by (simp add: Nat_case_def zero_Nat_def zero_discr_def)
lemma Nat_case_suc[simp]:  "Nat_casezs(box(Discr (Suc n))) = s(box(Discr n))"
  by (simp add: Nat_case_def)

lemma Nat_case_add_1[simp]:
  assumes ndef: "n  "
  shows "Nat_casezs(n + 1) = sn"
proof -
  from ndef obtain nu where nu: "n = boxnu"
    unfolding box_def
    apply (cases "n" rule: Box.exhaust)
    apply simp
    apply (case_tac "u")
    apply simp_all
    done
  then obtain u where "nu = Discr u"
    unfolding box_def
    apply (cases nu)
    apply simp
    done
  with nu show ?thesis unfolding one_Nat_def by simp
qed

lemma Nat_case_case_nat: "Nat_casezs(box(Discr n)) = case_nat z (λn'. s(box(Discr n'))) n"
  by (simp add: Nat_case_def)

lemma Nat_casedist[case_names bottom zero Suc]:
  fixes x :: Nat
  assumes xbot: "x =   P"
      and xzero: "x = 0  P"
      and xsuc: "n. x = n + 1  P"
  shows "P"
proof(cases x rule: Box.exhaust)
  case bottom with xbot show ?thesis by simp
next
  case (Box u) hence xu: "x = Boxu" and ubottom: "u  " .
  from ubottom obtain n where ndn: "u = up(Discr n)" apply (cases u) apply simp_all apply (case_tac x) apply simp done
  show ?thesis
  proof(cases n)
    case 0 with ndn xu xzero show ?thesis unfolding zero_Nat_def by (simp add: boxI zero_discr_def)
  next
    case (Suc m) with ndn xu xsuc[where n="box(Discr m)"]
    show ?thesis
      unfolding plus_Box_def unfolding one_Nat_def by (simp add: boxI one_discr_def)
  qed
qed

lemma cont_Nat_case[simp]:
  "cont (λx. f x); n. cont (λx. g xn)   cont (λx. Nat_case(f x)(g x)n)"
  apply (cases n rule: Nat_casedist)
    apply simp_all
  apply (case_tac na rule: Box.exhaust)
   apply simp_all
  done

lemma Nat_induct[case_names bottom zero Suc]:
  fixes P :: "Nat  bool"
  assumes xbot: "P "
      and xzero: "P 0"
      and xsuc: "n. n  ; P n   P (n + 1)"
  shows "P x"
proof(cases x rule: box_casedist)
  case bottom with xbot show ?thesis by simp
next
  case (Box u)
  then obtain n where un: "u = Discr n" by (cases u, simp)
  {
    fix n
    have "x. x = box(Discr n)  P x"
    proof(induct n)
      case 0 with xzero show ?case unfolding zero_Nat_def zero_discr_def by simp
    next
      case (Suc n)
      hence "P (box(Discr n))" by simp
      with xsuc[where n="box(Discr n)"] have "P (box(Discr n) + 1)" unfolding box_def by simp
      with Suc show ?case unfolding one_Nat_def one_discr_def plus_Box_def by simp
    qed
  }
  with un Box show ?thesis by simp
qed

lemma plus_commute: "(x :: Nat) + y = y + x"
proof -
  have dc: "u v. (undiscr (u::nat discr) + undiscr v) = (undiscr v + undiscr u)"
    by simp
  thus ?thesis
    apply (cases x)
     apply simp
    apply (cases y)
    by (simp_all add: dc plus_Box_def)
qed

lemma mult_unit: "(x::Nat) * 1 = x"
  unfolding times_Box_def one_Nat_def by (cases x, simp_all)

lemma mult_commute: "(x :: Nat) * y = y * x"
proof -
  have dc: "u v. (undiscr (u::nat discr) * undiscr v) = (undiscr v * undiscr u)"
    by simp
  show ?thesis
    apply (cases x)
     apply simp
    apply (cases y)
    by (simp_all add: dc)
qed

lemma mult_assoc: "((x :: Nat) * y) * z = x * (y * z)"
proof -
  have ac: "u v w. undiscr (u::nat discr) * (undiscr v * undiscr w)
                   =  (undiscr u * undiscr v) * undiscr w" by simp
  show ?thesis
    apply (cases x)
     apply simp
    apply (cases y)
     apply simp
    apply (cases z)
     apply simp
    by (simp add: ac)
qed

text‹Restore the HOLCF default sort.›

default_sort "domain"

(*<*)
end
(*>*)