Course Webpage for B522 Programming Language Foundations, Spring 2020, Indiana University
module ScottNumeralsPlus where
open import Data.List using (List; []; _∷_; _++_; length)
open import Data.List.Properties using (length-++)
open import Data.Nat using (ℕ; zero; suc; s≤s; z≤n; _+_; _<_; _≤_; _≟_)
open import Data.Nat.Properties
using (≤∧≢⇒<; ≤-pred; <-trans; ≤-refl; +-comm; +-suc; suc-injective;
≤-trans; ≤-reflexive; ≤-step)
open import Data.Product using (_×_; Σ; Σ-syntax; ∃; ∃-syntax; proj₁; proj₂)
renaming (_,_ to ⟨_,_⟩)
open import Relation.Binary.PropositionalEquality
using (_≡_; refl; sym; cong; cong₂; trans)
open import Relation.Nullary using (Dec; yes; no)
open import plfa.part3.Denotational
using (Value; _↦_; _⊔_; ⊥; _⊑_; _⊢_↓_; Env; `∅; _`,_;
↦-intro; ↦-elim; var; sub; ⊥-intro; ⊔-intro;
⊑-conj-R2; ⊑-fun; ⊑-refl; ⊑-conj-R1; ⊑-trans)
open import plfa.part2.Untyped
using (`suc_; `zero; μ_; case; plus; ★; ∅; _,_; _⊢_; `_; _·_; ƛ_; #_)
We need the following corollary of ⊑-refl
. (Perhaps it should
defined in the Denotational chapter.)
⊑-reflexive : ∀{v w : Value} → v ≡ w → v ⊑ w
⊑-reflexive refl = ⊑-refl
Recall that the Scott numerals are terms in the lambda calculus that represent the natural numbers:
zero = ƛ s ⇒ ƛ z ⇒ z
one = ƛ s ⇒ ƛ z ⇒ s · zero
two = ƛ s ⇒ ƛ z ⇒ s · one
The below scott
function generates the nth Scott numeral.
scott : (n : ℕ) → ∀{Γ} → Γ ⊢ ★
scott 0 = ƛ ƛ (# 0)
scott (suc n) = ƛ ƛ (# 1) · (scott n)
The denotation Dˢ
of the nth Scott numeral is defined below. Like
the Church numerals, the Scott numerals are more general than natural
numbers. The Scott numeral for n represents taking n steps along a
path. In the definition of Dˢ
, the function f
represents the
path.
Dˢ : (n : ℕ) → (f : ℕ → Value) → Value
Dˢ zero f = ⊥ ↦ (f 0) ↦ (f 0)
Dˢ (suc n) f =
let D[n] = Dˢ n f in
(D[n] ↦ f (suc n)) ↦ ⊥ ↦ f (suc n)
The Scott numeral for 0
is a function that ignores its first
argument and returns its second argument, i.e., the first element
of the path.
The Scott numeral for n + 1
is a function whose first argument is
a funny kind of successor function, one that maps the Scott numeral
for n
to the n + 1
element of the path. The second argument is
ignored, so its value is ⊥
. The result is the n + 1
element of
the path.
A straightforward proof by induction on n verifies that Dˢ n f
is
the denotation of scott n
for any path f
.
denot-scott : ∀{n : ℕ}{f : ℕ → Value}{Γ}{γ : Env Γ}
→ γ ⊢ scott n ↓ (Dˢ n f)
denot-scott {zero} {f} = ↦-intro (↦-intro var)
denot-scott {suc n} {f} = ↦-intro (↦-intro (↦-elim var denot-scott))
The successor function for Scott numerals does indeed produce the
Scott numeral for suc n
when given the Scott numeral for n
.
denot-suc : ∀{Γ}{M : Γ ⊢ ★}{n}{f : ℕ → Value}{γ : Env Γ}
→ γ ⊢ M ↓ Dˢ n f
→ γ ⊢ `suc M ↓ Dˢ (suc n) f
denot-suc {n = n} {f} M↓D[n] =
↦-elim (↦-intro (↦-intro (↦-intro (↦-elim var var)))) M↓D[n]
Recall that in the Untyped chapter, addition of Scott numerals is defined as a recursive function using the Y combinator. (Shown here with variable names instead of de Bruijn indices.)
plus = Y · (ƛ r ⇒ ƛ m ⇒ ƛ n ⇒ case m n (ƛ m' ⇒ `suc (r · m' · n)))
We shall prove that the denotation of plus
applied to the Scott numerals
for m
and n
is Dˢ (m + n) g
for any path g
.
plus[m,n] : ∀{m n : ℕ}{g : ℕ → Value}
→ `∅ ⊢ (plus · scott m) · (scott n) ↓ Dˢ (m + n) g
In the plus
function, the recursion is on m
, i.e., the base case
is when m
is zero and the recursion is on m' = m - 1
.
So let’s think about the denotation of plus
when m
is 0
and
then 1
on the way to identifying its denotation for arbitrary m
.
When m
is 0
, the denotation of plus
looks like
(⊥ ↦ Dˢ n g ↦ Dˢ n g) ↦ Dˢ n g ↦ Dˢ n g
Let’s see why this works by analyzing the denotation of the term
ƛ m ⇒ ƛ n ⇒ case m n ...
. So the parameter m
and n
have the
following denotations.
⟦m₀⟧ = ⊥ ↦ Dˢ n g ↦ Dˢ n g
⟦n⟧ = Dˢ n g
Recall that case
on a Scott numeral is defined as follows.
case L M N = L · (ƛ pˢ ⇒ N) · M
So for the plus
function, the case m n ...
is really
m · (ƛ m' ⇒ `suc (r · m' · n)) · n
Guided by the value for parameter m
, the term (ƛ m' ⇒ ...)
should have value ⊥
. Indeed, any term can have value ⊥
. Next, n
should have value Dˢ n g
, which indeed it does. The result of the
application is Dˢ n g
, which matches the expected result
Dˢ (0 + n) g
.
Next consider the meaning of plus
when m
is 1
.
((⟦m₀⟧ ↦ Dˢ (1 + n) g) ↦ ⊥ ↦ Dˢ (1 + n) g)
↦ Dˢ n g
↦ Dˢ (1 + n) g
Again we analyze the term ƛ m ⇒ ƛ n ⇒ case m n ...
.
So the parameters m
and n
have the following denotations.
⟦m₁⟧ = (⟦m₀⟧ ↦ Dˢ (1 + n) g) ↦ ⊥ ↦ Dˢ (1 + n) g
⟦n⟧ = Dˢ n g
Next we analyze the application of m
to its two arguments.
This time we must check that the argument (ƛ m' ⇒ ...)
produces the value
⟦m₀⟧ ↦ Dˢ (1 + n) g
So parameter m’ has the denotation ⟦m₀⟧
and we need to show that suc (r · m' · n)
produces Dˢ (1 + n) g
.
This can be obtained from the denot-suc
lemma,
so long as r · m' · n
produces Dˢ n g
.
This will be the case so long as r
has the value
⟦m₀⟧ ↦ Dˢ n g ↦ Dˢ n g
which was the denotation of plus for m
at zero.
Thus, we have finished analyzing the term (ƛ m' ⇒ ...)
.
The second argument in the application of m
, which
is n
, must have value ⊥
, which of course it does.
Thus, the result of applying m
to (ƛ m' ⇒ ...)
and n
is indeed Dˢ (1 + n) g
when m
is 1.
We have seen the denotation of plus
for m
at zero and one, and now
we need to generalize to arbitrary numbers. So let us review
the denotations that we obtained for parameter m
:
⟦m₀⟧ = ⊥ ↦ Dˢ n g ↦ Dˢ n g
⟦m₁⟧ = (⟦m₀⟧ ↦ Dˢ (1 + n) g) ↦ ⊥ ↦ Dˢ (1 + n) g
Indeed, these are denotations of a Scott numeral, but over an interesting choice of path:
Dˢ n g, Dˢ (1 + n) g, Dˢ (2 + n) g, ..., Dˢ (m + n) g
We define the following function ms
to produce this path.
ms : (n : ℕ) → (ℕ → Value) → (ℕ → Value)
ms n g i = Dˢ (i + n) g
The denotations for each mᵢ
can now be expressed using Dˢ
and ms
:
⟦mᵢ⟧ = Dˢ i (ms n g)
Now that we have a succinct way to write the denotation of the m
parameter, we can write down the denotation of plus
.
plusᵐ : (m n : ℕ) → (ℕ → Value) → Value
plusᵐ m n g = Dˢ m (ms n g) ↦ Dˢ n g ↦ Dˢ (m + n) g
Recall that the denotation of the r
parameter needs to be the
denotation of plus
for the previous m
. Also, when m is zero we did
not use parameter r
, so its denotation can be ⊥
. So the following
function gives the denotation for plus
at m - 1
.
prev-plusᵐ : (m n : ℕ) → (ℕ → Value) → Value
prev-plusᵐ 0 n g = ⊥
prev-plusᵐ (suc m) n g = plusᵐ m n g
We are now ready to formally verify the denotation of the expression
case m n (ƛ m' ⇒ `suc (r · m' · n))
which translates to de Bruijn notation as follows.
CASE : ∅ , ★ , ★ , ★ ⊢ ★
CASE = case (# 1) (# 0) (`suc (# 3 · # 0 · # 1))
We show that the denotation of CASE
is Dˢ (m + n) g
under the assumptions that the denotation
of parameter r
is prev-plusᵐ m n g
,
parameter m
is Dˢ m (ms n g)
, and parameter n
is Dˢ n g
.
We proceed by induction on m
.
CASE↓ : (m n : ℕ) (g : ℕ → Value)
→ `∅ `, prev-plusᵐ m n g `, Dˢ m (ms n g) `, Dˢ n g
⊢ CASE ↓ Dˢ (m + n) g
CASE↓ zero n g = ↦-elim (↦-elim var ⊥-intro) var
CASE↓ (suc m') n g = ↦-elim (↦-elim var nz-branch) ⊥-intro
where
γ = `∅ `, prev-plusᵐ (suc m') n g `, Dˢ (suc m') (ms n g) `, Dˢ n g
nz-branch : γ ⊢ ƛ (`suc ((# 3) · (# 0) · (# 1)))
↓ Dˢ m' (ms n g) ↦ Dˢ (suc m' + n) g
nz-branch = ↦-intro (denot-suc (↦-elim (↦-elim var var) var))
When m ≡ 0
, the denotation of m is ⊥ ↦ Dˢ n g ↦ Dˢ n g
.
The non-zero branch has value ⊥
by ⊥-intro
, and n
has value Dˢ n g
, so the result of the case is Dˢ n g
.
When m ≡ suc m'
, the denotation of m is
⟦m⟧ = Dˢ (suc m') (ms n g)
= (Dˢ m' (ms n g) ↦ Dˢ (suc m' + n) g) ↦ ⊥ ↦ Dˢ (suc m' + n) g
so we need to show that the non-zero branch has the value
Dˢ m' (ms n g) ↦ Dˢ (suc m' + n) g
Going under the ƛ
, we need to show that
suc (r · m' · n)
produces Dˢ (suc m' + n) g
.
Working backwards using the denot-suc
lemma,
it suffices to show that r · m' · n
produces Dˢ (m' + n) g
.
The denotation of r
is plusᵐ m' n g
, so applying it
to m'
and n
indeed produces Dˢ (m' + n) g
.
Moving outwards, we now analyze the expression
ƛ r ⇒ ƛ m ⇒ ƛ n ⇒ case m n (ƛ m' ⇒ `suc (r · m' · n))
We shall show that is has the denotation
(prev-plusᵐ 0 n g ↦ plusᵐ 0 n g)
⊔ (prev-plusᵐ 1 n g ↦ plusᵐ 1 n g)
⊔ (prev-plusᵐ 2 n g ↦ plusᵐ 2 n g)
...
which we construct with the following Pᵐ
function (P for plus).
Pᵐ : (m n : ℕ) → (ℕ → Value) → Value
Pᵐ 0 n g = ⊥ ↦ plusᵐ 0 n g
Pᵐ (suc m') n g = Pᵐ m' n g ⊔ (plusᵐ m' n g ↦ plusᵐ (suc m') n g)
We proceed by induction, using the CASE↓
lemma to analyze the CASE
expression and using the induction hypothesis to accumulate all of the
smaller denotations for plus.
P↓Pᵐ : ∀ m n → (g : ℕ → Value) → `∅ ⊢ ƛ (ƛ (ƛ CASE)) ↓ Pᵐ m n g
P↓Pᵐ zero n g = ↦-intro (↦-intro (↦-intro (CASE↓ 0 n g)))
P↓Pᵐ (suc m') n g =
⊔-intro (P↓Pᵐ m' n g)
(↦-intro (↦-intro (↦-intro (CASE↓ (suc m') n g))))
We shall need the following lemma, which extracts a single entry from Pᵐ
.
prev↦plus⊑Pᵐ : (m k n : ℕ) → (g : ℕ → Value)
→ prev-plusᵐ m n g ↦ plusᵐ m n g ⊑ Pᵐ (m + k) n g
prev↦plus⊑Pᵐ zero zero n g = ⊑-refl
prev↦plus⊑Pᵐ (suc m') zero n g =
⊑-conj-R2 (⊑-fun (⊑-reflexive (cong (λ □ → plusᵐ □ n g) (+-comm m' 0)))
(⊑-reflexive (cong (λ □ → plusᵐ (suc □) n g) (+-comm 0 m'))))
prev↦plus⊑Pᵐ m' (suc k) n g =
let IH = prev↦plus⊑Pᵐ m' k n g in
⊑-trans IH (⊑-trans (⊑-conj-R1 ⊑-refl)
(⊑-reflexive (cong (λ □ → Pᵐ □ n g) (sym (+-suc m' k)))))
The final step of this proof is to analyze the denotation of the Y combinator:
M = ƛ x ⇒ f · (x · x)
Y = ƛ f ⇒ M · M
In de Bruijn notation, we define Y as follows.
M : ∅ , ★ ⊢ ★
M = ƛ (# 1 · (# 0 · # 0))
Y : ∅ ⊢ ★
Y = ƛ M · M
The M is short for Matryoshka dolls (aka russian doll), which are
wooden dolls that nest smaller and smaller copies inside themselves.
In particular, for each m, the term M is a function from all the
previous denotations for M to plusᵐ m n g
.
⟦M₀⟧ = ⊥
⟦M₁⟧ = (⟦M₀⟧) ↦ plusᵐ 0 n g
⟦M₂⟧ = (⟦M₀⟧ ⊔ ⟦M₁⟧) ↦ plusᵐ 1 n g
⟦M₃⟧ = (⟦M₀⟧ ⊔ ⟦M₁⟧ ⊔ ⟦M₂⟧) ↦ plusᵐ 2 n g
...
We define the function Mᵐ
to compute the above denotations
for M, using the auxiliary function Ms
to join all the
previous denotations of M.
Mᵐ : (m n : ℕ) → (ℕ → Value) → Value
Ms : (m n : ℕ) → (ℕ → Value) → Value
Mᵐ 0 n g = ⊥
Mᵐ (suc m') n g = (Ms m' n g) ↦ plusᵐ m' n g
Ms zero n g = ⊥
Ms (suc m') n g = Ms m' n g ⊔ Mᵐ (suc m') n g
We prove by cases that the term M has
the denotation Mᵐ i n g
for any i from 0 to m + 1,
assuming the f
parameter has the denotation Pᵐ m n g
.
M↓ : (i k m n : ℕ) (g : ℕ → Value) {lt : suc m ≡ i + k}
→ `∅ `, Pᵐ m n g ⊢ M ↓ Mᵐ i n g
M↓ zero k m n g = ⊥-intro
M↓ (suc i) k m n g {lt} = ↦-intro (↦-elim (sub var H) x·x↓)
where
H : prev-plusᵐ i n g ↦ plusᵐ i n g ⊑ Pᵐ m n g
H = ⊑-trans (prev↦plus⊑Pᵐ i k n g)
(⊑-reflexive (cong (λ □ → Pᵐ □ n g) (sym (suc-injective lt))))
x·x↓ : ∀{i} → `∅ `, Pᵐ m n g `, Ms i n g ⊢ (# 0) · (# 0) ↓ prev-plusᵐ i n g
x·x↓ {zero} = ⊥-intro
x·x↓ {suc i} = ↦-elim (sub var (⊑-conj-R2 ⊑-refl))
(sub var (⊑-conj-R1 ⊑-refl))
The zero case is trivial: we apply ⊥-intro
to show that
M
has the value ⊥
.
For the non-zero case, we need to show that M has the value
Ms i n g ↦ plusᵐ i n g
Going under the ƛ x
, we need to show that f · (x · x)
has the value plusᵐ i n g
. The value of x · x
is prev-plusᵐ i n g
, which we prove by induction on i.
The case for 0 is trivial because prev-plusᵐ 0 n g ≡ ⊥
.
In the case for suc i
, we need to show that x · x
produces plusᵐ i n g
. Now the first x
has the value
Ms (suc i) n g
, so by the sub
rule and ⊑-conj-R2
,
it also has the value Mᵐ (suc m') n g
, which is equivalent to
Ms m' n g ↦ plusᵐ m' n g
The second x
, of course, has the value
Ms (suc i) n g
, so by the sub
rule and ⊑-conj-R1
,
is has the value
Ms m' n g
So indeed, apply x
to itself produces the value plusᵐ m' n g
.
With the lemma M↓
in hand, we prove that the term M also produces
the value Ms i n g
for each i from 0 to m + 1. We prove this by a
straightforward induction on i.
M↓Ms : (i k m n : ℕ) (g : ℕ → Value) {lt : suc m ≡ i + k}
→ `∅ `, Pᵐ m n g ⊢ M ↓ Ms i n g
M↓Ms zero k m n g {lt} = ⊥-intro
M↓Ms (suc i) k m n g {lt} =
⊔-intro (M↓Ms i (suc k) m n g {trans lt (sym (+-suc i k))})
(M↓ (suc i) k m n g {lt})
Now for the meaning of the Y combinator. It takes the table of the
plus functions (Pᵐ m n g
), the meaning of the Scott numeral for m
(Dˢ m (ms n g)
), and the meaning of n (Dˢ n g
), and returns the
meaning of the Scott numeral for m + n
(Dˢ (m + n) g
).
Y↓ : ∀ m n (g : ℕ → Value)
→ `∅ ⊢ Y ↓ Pᵐ m n g ↦ Dˢ m (ms n g) ↦ Dˢ n g ↦ Dˢ (m + n) g
Y↓ m n g = ↦-intro (↦-elim M↓₁ M↓₂)
where
M↓₁ : `∅ `, Pᵐ m n g ⊢ M
↓ Ms m n g ↦ Dˢ m (ms n g) ↦ Dˢ n g ↦ Dˢ (m + n) g
M↓₁ = (M↓ (suc m) 0 m n g {cong suc (+-comm 0 m)})
M↓₂ : `∅ `, Pᵐ m n g ⊢ M ↓ Ms m n g
M↓₂ = (M↓Ms m 1 m n g {+-comm 1 m})
The proof is as follows. We first go under the ƛ f
with ↦-intro
,
so f
has the value Pᵐ m n g
. We then show that M applied to
itself has the value
Dˢ m (ms n g) ↦ Dˢ n g ↦ Dˢ (m + n) g
We apply the lemma M↓
as suc m
to show that the first M
produces
Ms m n g ↦ Dˢ m (ms n g) ↦ Dˢ n g ↦ Dˢ (m + n) g
We apply the lemma M↓Ms
to show that the second M produces
Ms m n g
So putting the above two together, the application M · M
produces the following, as desired.
Dˢ m (ms n g) ↦ Dˢ n g ↦ Dˢ (m + n) g
We now arrive at the finish line: the addition of two Scott numerals produces the Scott numeral of the sum.
plus[m,n] : ∀{m n : ℕ}{g : ℕ → Value}
→ `∅ ⊢ (plus · scott m) · (scott n) ↓ Dˢ (m + n) g
plus[m,n] {m}{n}{g} =
↦-elim (↦-elim (↦-elim (Y↓ m n g) (P↓Pᵐ m n g)) (denot-scott{m}{ms n g}))
(denot-scott{n}{g})
The plus
function is the Y combinator applied to ƛ r ⇒ ƛ m ⇒ ƛ n ...
,
So we obtain its meaning using ↦-elim
and the lemmas Y↓
and P↓Pᵐ
.
We conclude by appling the result to the denotations of the Scott numerals
for m
and n
.