module Data.Partial.Base where
private variable
: Level
o o' ℓ : Type ℓ A B C
The partiality monad🔗
The partiality monad is an approach to modelling partial computations in constructive type theory — computations which may fail. The meaning of failure depends on the specific computation: for example, it might be non-terminating, or undefined on a given input. The partiality monad abstracts away from these details to arrive at the general notion of partial elements.
A partial element of a type consists of a predicate and a function We refer to as the extent of definition of the partial element, since its function is to record precisely where a given element is defined. The function contained within produces an element on that extent.
record ↯ {ℓ} (A : Type ℓ) : Type ℓ where
no-eta-equality
field
: Ω
def : ∣ def ∣ → A elt
This notion of partial element is not to be confused with the partial elements which feature in the syntax of cubical type theory!
We note that, while each element of ↯
stores a proposition — and, therefore, a type — the
type
of partial
lives in the same universe level as
itself. This is because of our standing assumption of propositional resizing. Were it
not for this assumption, we would additionally need to quantify over a
level
bounding the size of the extents, generating an
type of
elements.
open ↯ public
instance
: Underlying (↯ A)
Underlying-Part = record { ℓ-underlying = lzero ; ⌞_⌟ = λ x → ⌞ x .def ⌟ }
Underlying-Part
abstract
: (x : ↯ A) {p q : ⌞ x ⌟} → x .elt p ≡ x .elt q
↯-indep = ap (x .elt) (x .def .is-tr _ _)
↯-indep x
Part-pathp: {x : ↯ A} {y : ↯ B} (p : A ≡ B) (q : x .def ≡ y .def)
→ PathP (λ i → ∣ q i ∣ → p i) (x .elt) (y .elt)
→ PathP (λ i → ↯ (p i)) x y
{x = x} {y = y} p q r i .def = q i
Part-pathp {x = x} {y = y} p q r i .elt = r i Part-pathp
Partial elements enjoy the following extensionality principle: whenever they are defined on equivalent extents, and they agree whenever defined.
part-ext: {x y : ↯ A} (to : ⌞ x ⌟ → ⌞ y ⌟) (of : ⌞ y ⌟ → ⌞ x ⌟)
→ (∀ xd yd → x .elt xd ≡ y .elt yd)
→ x ≡ y
to from p = Part-pathp refl
part-ext (Ω-ua to from) (funext-dep λ _ → p _ _)
To close the initial definitions, if we have a partial element and a total then we write for the relation is defined and :
_↓_ : ↯ A → A → Type _
= Σ[ d ∈ x ] (x .elt d ≡ y) x ↓ y
instance
: Membership A (↯ A) _
Membership-↯ = record { _∈_ = λ x p → p ↓ x } Membership-↯
abstract instance
: ∀ {A : Type ℓ} {n} ⦃ _ : 2 ≤ n ⦄ ⦃ _ : H-Level A n ⦄ → H-Level (↯ A) n
H-Level-↯ {n = suc (suc n)} ⦃ s≤s (s≤s p) ⦄ =
H-Level-↯ (2 + n) eqv
hlevel-instance $ Iso→is-hlevel! where unquoteDecl eqv = declare-record-iso eqv (quote ↯)
The information ordering🔗
The information ordering on partial elements embodies the idea that is a computation valued in If we have two such computations there is an emergent notion of one having made more progress than the other — for example, if we were to model them as Turing machines, one might have literally taken more steps than the other.
We write the information ordering as it holds whenever is more defined than and they agree on this common extent.
record _⊑_ {ℓ} {A : Type ℓ} (x y : ↯ A) : Type ℓ where
no-eta-equality
field
: ⌞ x ⌟ → ⌞ y ⌟
implies : ∀ xd → y .elt (implies xd) ≡ x .elt xd refines
open _⊑_ public
abstract instance
: ∀ {A : Type ℓ} {x y : ↯ A} {n} ⦃ _ : 1 ≤ n ⦄ ⦃ _ : H-Level A (suc n) ⦄ → H-Level (x ⊑ y) n
H-Level-⊑ {n = suc n} ⦃ s≤s p ⦄ = hlevel-instance $ Iso→is-hlevel! (suc n) eqv
H-Level-⊑ where unquoteDecl eqv = declare-record-iso eqv (quote _⊑_)
At the bottom element in the information
order, we have the computation which never
succeeds.
: ↯ A
never .def = ⊥Ω never
Monadic structure🔗
We claimed above that the type of partial elements is a
monad: equipped with the information order, it’s actually the
free pointed directed-continuous
partial order on a set. However, it’s also a monad in the looser
sense of supporting do
notation.
We can define the necessary components here, without getting out the entire adjoint functor machinery. First, we note that function composition lets us extend to
: (A → B) → ↯ A → ↯ B
part-map .def = x .def
part-map f x .elt = f ∘ x .elt part-map f x
Then, we can define the transformation
and the “extension” operator part-bind
. This latter definition
highlights an important feature of
it is closed under dependent sums. A type of propositions
closed under dependent sums is called a dominance.
: A → ↯ A
always .def = ⊤Ω
always a .elt _ = a
always a
: ↯ A → (A → ↯ B) → ↯ B
part-bind .def .∣_∣ = Σ[ px ∈ x ] (f ʻ x .elt px)
part-bind x f .def .is-tr = hlevel 1
part-bind x f .elt (px , pfx) = f (x .elt px) .elt pfx part-bind x f
We note that we can also lift the operation of function application to the application of partial functions to partial arguments: we get a result whenever they are both defined.
: ↯ (A → B) → ↯ A → ↯ B
part-ap .def .∣_∣ = ⌞ f ⌟ × ⌞ x ⌟
part-ap f x .def .is-tr = hlevel 1
part-ap f x .elt (pf , px) = f .elt pf (x .elt px) part-ap f x
: {A : Type ℓ} (a : ↯ A) (x : ⌞ a ⌟) → a ≡ always (a .elt x)
is-always = part-ext (λ _ → tt) (λ z → x) λ _ _ → ↯-indep a
is-always a x
: {A : Type ℓ} {x y : A} → always x ≡ always y → x ≡ y
always-injective = ap₂ unalways p q where
always-injective p : (x : ↯ A) → ⌞ x ⌟ → A
unalways = elt
unalways
: PathP (λ i → ⌞ p i ⌟) tt tt
q = is-prop→pathp (λ i → p i .def .is-tr) tt tt
q
instance
: Map (eff ↯)
↯-Map .Map.map = part-map
↯-Map
: Idiom (eff ↯)
↯-Idiom .Idiom.Map-idiom = ↯-Map
↯-Idiom .Idiom.pure = always
↯-Idiom .Idiom._<*>_ = part-ap
↯-Idiom
: Bind (eff ↯)
↯-Bind .Bind._>>=_ = part-bind
↯-Bind .Bind.Idiom-bind = ↯-Idiom
↯-Bind
-- This class lets us define syntax sugar for e.g. lifted binary
-- operators which automatically lifts values from the base type
record To-part {ℓ'} (X : Type ℓ') (A : Type ℓ) : Type (ℓ ⊔ ℓ') where
field to-part : X → ↯ A
instance
: To-part (↯ A) A
part-to-part = record { to-part = λ x → x }
part-to-part
: To-part A A
pure-to-part = record { to-part = always } pure-to-part
What about Maybe?🔗
In functional programming circles, it’s common to define a
partial function
to be a total function
The Maybe
type is certainly simpler
than our ↯
: so why not use
that?
The answer lies, as it so often does, in constructivism. The functions are precisely those for which it is decidable whether or not is defined, for each But if we do not accept the law of excluded middle, then using the proper partiality monad gains us actual generality.
We can prove that the type Maybe A
is equivalent to the subtype of ↯ A
with decidable extent, by writing simple
functions back and forth:
: ∀ {ℓ} → Type ℓ → Type ℓ
↯-Maybe = Σ[ p ∈ ↯ A ] Dec ⌞ p ⌟
↯-Maybe A
: Maybe A → ↯-Maybe A
maybe→↯-maybe = never , auto
maybe→↯-maybe nothing (just x) = always x , auto
maybe→↯-maybe
: ↯-Maybe A → Maybe A
↯-maybe→maybe (x , yes x-def) = just (x .elt x-def)
↯-maybe→maybe (x , no ¬x-def) = nothing
↯-maybe→maybe
: (x : Maybe A) → ↯-maybe→maybe (maybe→↯-maybe x) ≡ x
maybe→↯→maybe = refl
maybe→↯→maybe nothing (just x) = refl
maybe→↯→maybe
: (x : ↯-Maybe A) → maybe→↯-maybe (↯-maybe→maybe x) ≡ x
↯→maybe→↯ (x , yes a) = Σ-prop-path! $ part-ext (λ _ → a) (λ _ → tt)
↯→maybe→↯ (λ _ _ → ↯-indep x)
(x , no ¬a) = Σ-prop-path! $ part-ext (λ ()) ¬a λ () ↯→maybe→↯
However, we can not prove that this is a genuine generalisation. That is because constructive type theory is compatible with the law of excluded middle, but if we could internally demonstrate an undecidable proposition, it would contradict the law of excluded middle.