module Realisability.PCA.Sugar {ℓ} (𝔸 : PCA ℓ) where
private variable
: Level
ℓ' ℓ''
open PCA 𝔸 public
Sugar for programming in PCAs🔗
This module defines useful metaprograms for writing values and programs in a partial combinatory algebra It is thus primarily interesting to the reader who cares about the details of the formalisation, rather than its extensional mathematical content.
We start by defining an overloaded version of the application operator on which supports any argument that can be coerced to a partial element of so we can write things like even when is a literal element, or is a total partial element of
_⋆_
: ∀ {X : Type ℓ'} {Y : Type ℓ''} ⦃ _ : To-part X ⌞ 𝔸 ⌟ ⦄ ⦃ _ : To-part Y ⌞ 𝔸 ⌟ ⦄
→ X → Y → ↯ ⌞ 𝔸 ⌟
= to-part f % to-part x where open To-part ⦃ ... ⦄ f ⋆ x
Parametric higher-order abstract syntax🔗
To conveniently use the abstraction elimination on we will define a parametric higher-order abstract syntax for terms in PHOAS is an approach to representing syntax with binding which parametrises over a type of variables, and represents object-level binders with meta-level binders.
data Termʰ (V : Type) : Type ℓ where
: V → Termʰ V
var : ↯⁺ ⌞ 𝔸 ⌟ → Termʰ V
const : Termʰ V → Termʰ V → Termʰ V
app : (V → Termʰ V) → Termʰ V lam
We will primarily use terms where the type of variables is taken to
be the natural numbers, standing for de Bruijn levels. Since we
can perform case analysis on natural numbers, not every PHOAS Termʰ
with natural-number values will
represent an actual Term
in the
language of
We introduce a well-foundedness predicate wf
on PHOAS terms, given a context size
which asserts that every variable is actually in scope (and thus can be
converted to a de Bruijn index in
private
: Nat → Termʰ Nat → Type
wf (var k) = Γ - suc k < Γ
wf Γ (const a) = ⊤
wf Γ (app f x) = wf Γ f × wf Γ x
wf Γ (lam b) = wf (suc Γ) (b Γ) wf Γ
Note that wf
is defined by
recursion, rather than by induction, so that all of its concrete
instances can be filled in by instance search. We can convert a wf
PHOAS term in
to a de Bruijn term in
We use levels rather than indices in the PHOAS representation so that
the case for abstractions can call the meta-level abstraction with the
length of the context.
: ∀ {Γ} (t : Termʰ Nat) → wf Γ t → Term ⌞ 𝔸 ⌟ Γ
from-wf {Γ} (var x) w = var (fin (Γ - suc x) ⦃ forget w ⦄)
from-wf (const x) w = const x
from-wf (app f x) (wf , wx) = app (from-wf f wf) (from-wf x wx)
from-wf {Γ} (lam f) w = abs (from-wf (f Γ) w) from-wf
Finally, we introduce another recursive predicate to be filled in by instance search indicating whether a term always denotes— these are the inclusions of elements from and top-level abstractions.
: ∀ {V} → Termʰ V → Type
always-denotes (var x) = ⊥
always-denotes (const x) = ⊤
always-denotes (app f x) = ⊥
always-denotes (lam x) = ⊤ always-denotes
To use PHOAS terms, we introduce notations for evaluating an
arbitrary expression and a term which always denotes, producing
a partial or total-partial element of
respectively. Note the parametricity in the argument: to
prevent us from writing ‘exotic’ values of Termʰ
, we must work against an arbitrary
choice of variable type. If Agda had internalised parametricity, this
would be enough to prove that the arguments to expr_
and val_
must be
well-formed; since we do not have parametricity we instead ask instance
search to fill in the well-formedness (and definedness) assumptions
instead.
_ : (t : ∀ {V} → Termʰ V) ⦃ _ : wf 0 t ⦄ → ↯ ⌞ 𝔸 ⌟
expr_ t ⦃ i ⦄ = eval {n = 0} (from-wf t i) []
expr
_
val: (t : ∀ {V} → Termʰ V) ⦃ _ : wf 0 t ⦄
→ ⦃ _ : always-denotes {Nat} t ⦄ → ↯⁺ ⌞ 𝔸 ⌟
_ t ⦃ i ⦄ =
valrecord
{ fst = eval {n = 0} (from-wf t i) []
; snd = d t
}
where abstract
: (t : Termʰ Nat) ⦃ i : wf 0 t ⦄ ⦃ _ : always-denotes t ⦄ → ⌞ eval {n = 0} (from-wf t i) [] ⌟
d (const x) = x .snd
d (lam x) = abs↓ (from-wf (x 0) _) [] d
Finally, we introduce a notation class To-term
to overload the construction of
applications and abstractions in PHOAS terms.
record To-term {ℓ'} (V : Type) (X : Type ℓ') : Type (ℓ ⊔ ℓ') where
field to : X → Termʰ V
instance
: ∀ {V} → To-term V V
var-to-term = record { to = var }
var-to-term
: ∀ {V} → To-term V ⌞ 𝔸 ⌟
const-to-term' = record { to = λ x → const (pure x , tt) }
const-to-term'
: ∀ {V} → To-term V (↯⁺ ⌞ 𝔸 ⌟)
const-to-term = record { to = const }
const-to-term
: ∀ {V} → To-term V (Termʰ V)
term-to-term = record { to = λ x → x }
term-to-term
_`·_
: ∀ {ℓ' ℓ''} {V : Type} {A : Type ℓ'} {B : Type ℓ''}
→ ⦃ _ : To-term V A ⦄ ⦃ _ : To-term V B ⦄
→ A → B → Termʰ V
= app (to f) (to x) where open To-term ⦃ ... ⦄
f `· x
: ∀ {ℓ} {V : Type} {A : Type ℓ} ⦃ _ : To-term V A ⦄ → (V → A) → Termʰ V
lam-syntax = lam λ x → to (f x) where open To-term ⦃ ... ⦄
lam-syntax f
syntax lam-syntax (λ x → e) = ⟨ x ⟩ e
infixl 25 _`·_
infixl 35 _⋆_