M-types
M types are potentially infinite tree-like structures. They are defined as the greatest fixpoint of a polynomial functor.
- continue : Π (F : pfunctor), pfunctor.approx.cofix_a F 0
- intro : Π (F : pfunctor) {n : ℕ} (a : F.A), (F.B a → pfunctor.approx.cofix_a F n) → pfunctor.approx.cofix_a F n.succ
cofix_a F n
is an n
level approximation of a M-type
default inhabitant of cofix_a
Equations
The label of the root of the tree for a non-trivial approximation of the cofix of a pfunctor.
Equations
for a non-trivial approximation, return all the subtrees of the root
Equations
- continue : ∀ {F : pfunctor} (x : pfunctor.approx.cofix_a F 0) (y : pfunctor.approx.cofix_a F 1), pfunctor.approx.agree x y
- intro : ∀ {F : pfunctor} {n : ℕ} {a : F.A} (x : F.B a → pfunctor.approx.cofix_a F n) (x' : F.B a → pfunctor.approx.cofix_a F (n + 1)), (∀ (i : F.B a), pfunctor.approx.agree (x i) (x' i)) → pfunctor.approx.agree (pfunctor.approx.cofix_a.intro a x) (pfunctor.approx.cofix_a.intro a x')
Relation between two approximations of the cofix of a pfunctor that state they both contain the same data until one of them is truncated
Given an infinite series of approximations approx
,
all_agree approx
states that they are all consistent with each other.
Equations
- pfunctor.approx.all_agree x = ∀ (n : ℕ), pfunctor.approx.agree (x n) (x n.succ)
truncate a
turns a
into a more limited approximation
s_corec f i n
creates an approximation of height n
of the final coalgebra of f
Equations
- pfunctor.approx.s_corec f j n.succ = pfunctor.approx.cofix_a.intro (f j).fst (λ (i : F.B (f j).fst), pfunctor.approx.s_corec f ((f j).snd i) n)
- pfunctor.approx.s_corec f _x 0 = pfunctor.approx.cofix_a.continue
path F
provides indices to access internal nodes in corec F
Equations
- pfunctor.approx.path F = list F.Idx
Equations
- approx : Π (n : ℕ), pfunctor.approx.cofix_a F n
- consistent : pfunctor.approx.all_agree c.approx
Internal definition for M
. It is needed to avoid name clashes
between M.mk
and M.cases_on
and the declarations generated for
the structure
Equations
- pfunctor.M.inhabited F = {default := {approx := λ (n : ℕ), inhabited.default (pfunctor.approx.cofix_a F n), consistent := _}}
Equations
- pfunctor.M_intl.inhabited F = show inhabited F.M, from pfunctor.M.inhabited F
Corecursor for the M-type defined by F
.
Equations
- pfunctor.M.corec f i = {approx := pfunctor.approx.s_corec f i, consistent := _}
given a tree generated by F
, head
gives us the first piece of data
it contains
Equations
- x.head = pfunctor.approx.head' (x.approx 1)
return all the subtrees of the root of a tree x : M F
Equations
- x.children i = let H : ∀ (n : ℕ), pfunctor.approx.head' (x.approx n.succ) = pfunctor.approx.head' (x.approx 1) := _ in {approx := λ (n : ℕ), pfunctor.approx.children' (x.approx n.succ) (cast _ i), consistent := _}
select a subtree using a i : F.Idx
or return an arbitrary tree if
i
designates no subtree of x
generates the approximations needed for M.mk
Equations
- pfunctor.M.approx.s_mk x n.succ = pfunctor.approx.cofix_a.intro x.fst (λ (i : F.B x.fst), (x.snd i).approx n)
- pfunctor.M.approx.s_mk x 0 = pfunctor.approx.cofix_a.continue
constructor for M-types
Equations
- pfunctor.M.mk x = {approx := pfunctor.M.approx.s_mk x, consistent := _}
- trivial : ∀ {F : pfunctor} (x y : F.M), pfunctor.M.agree' 0 x y
- step : ∀ {F : pfunctor} {n : ℕ} {a : F.A} (x y : F.B a → F.M) {x' y' : F.M}, x' = pfunctor.M.mk ⟨a, x⟩ → y' = pfunctor.M.mk ⟨a, y⟩ → (∀ (i : F.B a), pfunctor.M.agree' n (x i) (y i)) → pfunctor.M.agree' n.succ x' y'
agree' n
relates two trees of type M F
that
are the same up to dept n
destructor for M-types
Equations
- pfunctor.M.cases f x = (λ (this : r (pfunctor.M.mk x.dest)), _.mpr this) (f x.dest)
destructor for M-types
Equations
- x.cases_on f = pfunctor.M.cases f x
destructor for M-types, similar to cases_on
but also
gives access directly to the root and subtrees on an M-type
- nil : ∀ {F : pfunctor} (x : F.M), pfunctor.M.is_path list.nil x
- cons : ∀ {F : pfunctor} (xs : pfunctor.approx.path F) {a : F.A} (x : F.M) (f : F.B a → F.M) (i : F.B a), x = pfunctor.M.mk ⟨a, f⟩ → pfunctor.M.is_path xs (f i) → pfunctor.M.is_path (⟨a, i⟩ :: xs) x
is_path p x
tells us if p
is a valid path through x
follow a path through a value of M F
and return the subtree
found at the end of the path if it is a valid path for that value and
return a default tree
Equations
- pfunctor.M.isubtree (⟨a, i⟩ :: ps) x = x.cases_on' (λ (a' : F.A) (f : F.B a' → F.M), dite (a = a') (λ (h : a = a'), pfunctor.M.isubtree ps (f (cast _ i))) (λ (h : ¬a = a'), inhabited.default F.M))
- pfunctor.M.isubtree list.nil x = x
similar to isubtree
but returns the data at the end of the path instead
of the whole subtree
Equations
- pfunctor.M.iselect ps = λ (x : F.M), (pfunctor.M.isubtree ps x).head
- head : ∀ {a a' : F.A} {f : F.B a → F.M} {f' : F.B a' → F.M}, R (pfunctor.M.mk ⟨a, f⟩) (pfunctor.M.mk ⟨a', f'⟩) → a = a'
- tail : ∀ {a : F.A} {f f' : F.B a → F.M}, R (pfunctor.M.mk ⟨a, f⟩) (pfunctor.M.mk ⟨a, f'⟩) → ∀ (i : F.B a), R (f i) (f' i)
Bisimulation is the standard proof technique for equality between infinite tree-like structures
corecursor for M F
with swapped arguments
Equations
- pfunctor.M.corec_on x₀ f = pfunctor.M.corec f x₀
corecursor where the state of the computation can be sent downstream in the form of a recursive call
Equations
- pfunctor.M.corec₁ F = pfunctor.M.corec (F α id)
corecursor where it is possible to return a fully formed value at any point of the computation
Equations
- pfunctor.M.corec' F x = pfunctor.M.corec₁ (λ (X : Type u) (rec : P.M ⊕ α → X) (a : P.M ⊕ α), let y : P.M ⊕ P.obj X := a >>= F (rec ∘ sum.inr) in pfunctor.M.corec'._match_1 X rec y) (sum.inr x)
- pfunctor.M.corec'._match_1 X rec (sum.inr y) = y
- pfunctor.M.corec'._match_1 X rec (sum.inl y) = (rec ∘ sum.inl) <$> y.dest