I have some issues with the dynamics presented in this chapter.
The rules that replace arguments to introduction forms with cell references, like (36.3c) and (36.7c), apply even when the arguments to the form are already cell references! This technically makes the dynamics nondeterministic, albeit not in a very interesting or important way. More to the point, it means that we have possible reduction sequences like
ν {s(z) || Ø} |->
ν a ~ nat {s(@a) || a -> z} |->
ν a ~ nat, b ~ nat {s(@b) || a -> z ⊗ b -> @a} |->
ν a ~ nat, b ~ nat, c ~ nat {s(@c) || a -> z ⊗ b -> @a ⊗ c -> @b} |->
...
λ (x : nat) foo(x)(3)
, will
create an unnecessary chain of cells.ν Σ {(λ (x : nat) e)(3) || μ} |->* ν Σ, a ~ nat {[@a/x]e || μ ⊗ a -> 3}
rather than simply ν Σ {(λ (x : nat) e)(3) || μ} |-> ν Σ {[3/x]e || μ}
.
(Actually, 3
isn’t a value in these dynamics, since s(e)
is only a
value if e
is a cell, and 2
is not a cell… Hmm.) The same phenomenon
takes place in rules for elimination forms like (36.3f) and (36.7e);
although they do not directly allocate cells, they still require it to
happen before they can be applied.It seems like (2)-(4) could all be fixed by introducing a judgment like “would benefit from being replaced with a cell” that excludes values and cells, and then modifying the dynamics to make use of this judgment. For the rules complained about in (2), we could add a premise that the argument should be replaced. For the one complained about in (3), we could split it into (a) ordinary β-rule but with a premise that the argument shouldn’t need to be replaced, and (b) a version of the current rule with a premise that the argument should be replaced. For the rules complained about at the end of (4), we could replace the requirement that the relevant subterm be a cell with the requirement that the relevant subterm would not benefit from being replaced with a cell.
Suppose we eliminate on ω
, like ifz ω {z -> e_0 | s(x) -> e_1}
.
Under the by-name interpretation discussed previously, we first reduce ω
in-place until it is a value. This takes one step: ω |-> s(ω)
, so
ifz ω {z -> e_0 | s(x) -> e_1} |-> ifz s(ω) {z -> e_0 | s(x) -> e_1
. Then
ifz s(ω) {z -> e_0 | s(x) -> e_1} |-> [ω/x]e_1
. Anywhere we make use of x
in e_1
will re-do the simplification of ω
.
Under the by-need interpretation, we have
ν Σ {ω || μ} |-> ν Σ, a ~ nat {@a || μ ⊗ a -> s(@a)}
, and hence
ν Σ {ifz ω {z -> e_0 | s(x) -> e_1} || μ} |->
ν Σ, a ~ nat {ifz @a {z -> e_0 | s(x) -> e_1} || μ ⊗ a -> s(@a)}.
Then,
ν Σ, a ~ nat {ifz @a {z -> e_0 | s(x) -> e_1} || μ ⊗ a -> s(@a)} |->
ν Σ, a ~ nat {ifz s(@a) {z -> e_0 | s(x) -> e_1} || μ ⊗ a -> s(@a)} |->
ν Σ, a ~ nat {[@a/x]e_1 || μ ⊗ a -> s(@a)}.
All instances of x
in e_1
now use @a
to refer to the shared unrolling of
ω
.
In this particular case, this saves essentially no work, but if the body of ω
had involved nontrivial computation before settling on being s(ω)
, there
would be significant savings.
(fix count_up : nat -> list is λ (n : nat) cons·(n, count_up(s(n))))(0)
I’ll follow the book’s lead, even though I have some concerns about some of how it presents the dynamics that I’ll voice elsewhere.
unit
is very simple and needs no real change from ordinary FPC
:
--------
<> val_Σ
void
is almost as simple:
ν Σ {e || μ} |-> ν Σ' {e' || μ'}
----------------------------------------------
ν Σ {abort(e) || μ} |-> ν Σ' {abort(e') || μ'}
Sums:
-------------------
l·@a val_{Σ, a ~ τ}
-------------------
r·@a val_{Σ, a ~ τ}
--------------------------------------------------
ν Σ {l·e || μ} |-> ν Σ, a ~ τ {l·@a || μ ⊗ a -> e}
--------------------------------------------------
ν Σ {r·e || μ} |-> ν Σ, a ~ τ {r·@a || μ ⊗ a -> e}
ν Σ {e || μ} |-> ν Σ' {e' || μ'}
----------------------------------------------------
ν Σ {case e {l·x_1 -> e_1 | r·x_2 -> e_2} || μ}
|->
ν Σ' {case e' {l·x_1 -> e_1 | r·x_2 -> e_2} || μ'}
ν Σ {e || μ} loops
-----------------------------------------------------
ν Σ {case e {l·x_1 -> e_1 | r·x_2 -> e_2} || μ} loops
---------------------------------------------------------
ν Σ, a ~ τ {case l·@a {l·x_1 -> e_1 | r·x_2 -> e_2} || μ}
|->
ν Σ, a ~ τ {[@a/x_1]e_1 || μ}
---------------------------------------------------------
ν Σ, a ~ τ {case r·@a {l·x_1 -> e_1 | r·x_2 -> e_2} || μ}
|->
ν Σ, a ~ τ {[@a/x_2]e_2 || μ}
Finally, recursive types:
-------------------
l·@a val_{Σ, a ~ τ}
-----------------------
fold(@a) val_{Σ, a ~ τ}
----------------------------------------------------------
ν Σ {fold(e) || μ} |-> ν Σ, a ~ τ {fold(@a) || μ ⊗ a -> e}
ν Σ {e || μ} |-> ν Σ' {e' || μ'}
------------------------------------------------
ν Σ {unfold(e) || μ} |-> ν Σ' {unfold(e') || μ'}
ν Σ {e || μ} loops
--------------------------
ν Σ {unfold(e) || μ} loops
-----------------------------------------------------------
ν Σ, a ~ τ {unfold(fold(@a)) || μ} |-> ν Σ, a ~ τ {@a || μ}
I’ll write <τ>
for the interpretation of the LFPC type τ
into SFPC, since
it’s not clear to me how to use Unicode to typeset a hat like in the book.
unit
has one value in LFPC, so its translation should also be unit
, the
SFPC type with one value.τ_1 × τ_2
is a pair of unevaluated subexpressions, so the
SFPC type with the appropriate values is <τ_1> susp × <τ_2> susp
.void
, so we set <void> := void
.τ_1 + τ_2 := <τ_1> susp + <τ_2> susp
.rec t is τ
is any expression of the form fold(e)
, where e
is an
unevaluated expression of type [rec t is τ/t]τ
. Thus, we would like an
SFPC value of <rec t is τ>
to be of the form fold(e)
, where e
is a
suspension of a <[rec t is τ/t]τ>
. The appropriate type is simply
rec t is <τ> susp
—the SFPC values of this type are of the form
fold(e)
, where e
is a suspension of a [<rec t is τ>/t]<τ>
. Then
because of how <_>
is defined, this is equal to <[rec t is τ/t]τ>
.