Merge pull request #389 from plfa/intrinsic

Change inherent to intrinsic
This commit is contained in:
Philip Wadler 2019-08-31 20:09:04 +01:00 committed by GitHub
commit 3ef66e5dbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 56 deletions

View file

@ -1,5 +1,5 @@
--- ---
title : "DeBruijn: Inherently typed de Bruijn representation" title : "DeBruijn: Intrinsically-typed de Bruijn representation"
layout : page layout : page
prev : /Properties/ prev : /Properties/
permalink : /DeBruijn/ permalink : /DeBruijn/
@ -16,17 +16,30 @@ separately from types. We began with that approach because it
is traditional, but it is not the one we recommend. This is traditional, but it is not the one we recommend. This
chapter presents an alternative approach, where named chapter presents an alternative approach, where named
variables are replaced by de Bruijn indices and terms are variables are replaced by de Bruijn indices and terms are
inherently typed. Our new presentation is more compact, using indexed by their types. Our new presentation is more compact, using
less than half the lines of code required previously to cover substantially fewer lines of code to cover the same ground.
the same ground.
The inherently typed representation was first proposed by There are two fundamental approaches to typed lambda calculi.
One approach, followed in the last two chapters,
is to first define terms and then define types.
Terms exist independent of types, and may have types assigned to them
by separate typing rules.
Another approach, followed in this chapter,
is to first define types and then define terms.
Terms and type rules are intertwined, and it makes no sense to talk
of a term without a type.
The two approaches are sometimes called _Curry style_ and _Church style_.
Following Reynolds, we will refer to them as _extrinsic_ and _intrinsic_.
The particular representation described here
was first proposed by
Thorsten Altenkirch and Bernhard Reus. Thorsten Altenkirch and Bernhard Reus.
The formalisation of renaming and substitution The formalisation of renaming and substitution
we use is due to Conor McBride. we use is due to Conor McBride.
Related work has been carried out by Related work has been carried out by
James Chapman, James McKinna, and many others. James Chapman, James McKinna, and many others.
## Imports ## Imports
``` ```
@ -99,22 +112,22 @@ unique representation, rather than being represented by the
equivalence class of terms under alpha renaming. equivalence class of terms under alpha renaming.
The other important feature of our chosen representation is The other important feature of our chosen representation is
that it is _inherently typed_. In the previous two chapters, that it is _intrinsically typed_. In the previous two chapters,
the definition of terms and the definition of types are the definition of terms and the definition of types are
completely separate. All terms have type `Term`, and nothing completely separate. All terms have type `Term`, and nothing
in Agda prevents one from writing a nonsense term such as in Agda prevents one from writing a nonsense term such as
`` `zero · `suc `zero `` which has no type. Such terms that `` `zero · `suc `zero `` which has no type. Such terms that
exist independent of types are sometimes called _preterms_ or exist independent of types are sometimes called _preterms_ or
_raw terms_. Here we are going to replace the type `Term` of _raw terms_. Here we are going to replace the type `Term` of
raw terms by the more sophisticated type `Γ ⊢ A` of inherently raw terms by the type `Γ ⊢ A` of intrinsically-typed terms
typed terms, which in context `Γ` have type `A`. which in context `Γ` have type `A`.
While these two choices fit well, they are independent. One While these two choices fit well, they are independent. One
can use de Bruijn indices in raw terms, or (with more can use de Bruijn indices in raw terms, or
difficulty) have inherently typed terms with names. In have intrinsically-typed terms with names. In
Chapter [Untyped]({{ site.baseurl }}/Untyped/), Chapter [Untyped]({{ site.baseurl }}/Untyped/),
we will introduce terms with de Bruijn indices that we will introduce terms with de Bruijn indices that
are inherently scoped but not typed. are intrinsically scoped but not typed.
## A second example ## A second example
@ -189,7 +202,7 @@ Bruijn indices it could be referred to as `# 2`.
## Order of presentation ## Order of presentation
In the current chapter, the use of inherently-typed terms In the current chapter, the use of intrinsically-typed terms
necessitates that we cannot introduce operations such as necessitates that we cannot introduce operations such as
substitution or reduction without also showing that they substitution or reduction without also showing that they
preserve types. Hence, the order of presentation must change. preserve types. Hence, the order of presentation must change.
@ -223,7 +236,7 @@ infix 9 S_
infix 9 #_ infix 9 #_
``` ```
Since terms are inherently typed, we must define types and Since terms are intrinsically typed, we must define types and
contexts before terms. contexts before terms.
### Types ### Types
@ -260,14 +273,16 @@ type `` ` ``.
### Variables and the lookup judgment ### Variables and the lookup judgment
Inherently typed variables correspond to the lookup judgment. Intrinsically-typed variables correspond to the lookup judgment.
They are represented by de Bruijn indices, and hence also They are represented by de Bruijn indices, and hence also
correspond to natural numbers. We write correspond to natural numbers. We write
Γ ∋ A Γ ∋ A
for variables which in context `Γ` have type `A`. Their for variables which in context `Γ` have type `A`.
formalisation looks exactly like the old lookup judgment, but The lookup judgement is formalised by a datatype indexed
by a context and a type.
It looks exactly like the old lookup judgment, but
with all variable names dropped: with all variable names dropped:
``` ```
data _∋_ : Context → Type → Set where data _∋_ : Context → Type → Set where
@ -294,7 +309,7 @@ judgments:
* `` ∅ , "s" ⦂ `` , "z" ⦂ ` ∋ "z" ⦂ ` `` * `` ∅ , "s" ⦂ `` , "z" ⦂ ` ∋ "z" ⦂ ` ``
* `` ∅ , "s" ⦂ `` , "z" ⦂ ` ∋ "s" ⦂ ` ⇒ ` `` * `` ∅ , "s" ⦂ `` , "z" ⦂ ` ∋ "s" ⦂ ` ⇒ ` ``
They correspond to the following inherently-typed variables: They correspond to the following intrinsically-typed variables:
``` ```
_ : ∅ , ` ⇒ ` , ` ∋ ` _ : ∅ , ` ⇒ ` , ` ∋ `
_ = Z _ = Z
@ -309,13 +324,15 @@ and `"s"` by `S Z`
### Terms and the typing judgment ### Terms and the typing judgment
Inherently typed terms correspond to the typing judgment. Intrinsically-typed terms correspond to the typing judgment.
We write We write
Γ ⊢ A Γ ⊢ A
for terms which in context `Γ` has type `A`. Their for terms which in context `Γ` has type `A`.
formalisation looks exactly like the old typing judgment, but The judgement is formalised by a datatype indexed
by a context and a type.
It looks exactly like the old typing judgment, but
with all terms and variable names dropped: with all terms and variable names dropped:
``` ```
data _⊢_ : Context → Type → Set where data _⊢_ : Context → Type → Set where
@ -362,8 +379,7 @@ structure of terms and the structure of a derivation showing
that it is well typed: now we use the derivation _as_ the that it is well typed: now we use the derivation _as_ the
term. term.
For example, consider the following old-style typing For example, consider the following old-style typing judgments:
judgments:
* `` ∅ , "s" ⦂ `` , "z" ⦂ `` "z" ⦂ ` `` * `` ∅ , "s" ⦂ `` , "z" ⦂ `` "z" ⦂ ` ``
* `` ∅ , "s" ⦂ `` , "z" ⦂ `` "s" ⦂ `` `` * `` ∅ , "s" ⦂ `` , "z" ⦂ `` "s" ⦂ `` ``
@ -372,7 +388,7 @@ judgments:
* `` ∅ , "s" ⦂ `` ⊢ (ƛ "z" ⇒ ` "s" · (` "s" · ` "z")) ⦂ ` ⇒ ` `` * `` ∅ , "s" ⦂ `` ⊢ (ƛ "z" ⇒ ` "s" · (` "s" · ` "z")) ⦂ ` ⇒ ` ``
* `` ∅ ⊢ ƛ "s" ⇒ ƛ "z" ⇒ ` "s" · (` "s" · ` "z")) ⦂ (` ⇒ `) ⇒ ` ⇒ ` `` * `` ∅ ⊢ ƛ "s" ⇒ ƛ "z" ⇒ ` "s" · (` "s" · ` "z")) ⦂ (` ⇒ `) ⇒ ` ⇒ ` ``
They correspond to the following inherently-typed terms: They correspond to the following intrinsically-typed terms:
``` ```
_ : ∅ , ` ⇒ ` , ` ⊢ ` _ : ∅ , ` ⇒ ` , ` ⊢ `
_ = ` Z _ = ` Z
@ -392,8 +408,7 @@ _ = ƛ (` S Z · (` S Z · ` Z))
_ : ∅ ⊢ (` ⇒ `) ⇒ ` ⇒ ` _ : ∅ ⊢ (` ⇒ `) ⇒ ` ⇒ `
_ = ƛ ƛ (` S Z · (` S Z · ` Z)) _ = ƛ ƛ (` S Z · (` S Z · ` Z))
``` ```
The final inherently-typed term represents the Church numeral The final term represents the Church numeral two.
two.
### Abbreviating de Bruijn indices ### Abbreviating de Bruijn indices
@ -482,7 +497,7 @@ contexts. While we are at it, we also generalise `twoᶜ` and
#### Exercise `mul` (recommended) #### Exercise `mul` (recommended)
Write out the definition of a lambda term that multiplies Write out the definition of a lambda term that multiplies
two natural numbers, now adapted to the inherently typed two natural numbers, now adapted to the intrinsically-typed
DeBruijn representation. DeBruijn representation.
``` ```
@ -585,13 +600,13 @@ unchanged.
We will see below that renaming by `S_` plays a key role in We will see below that renaming by `S_` plays a key role in
substitution. For traditional uses of de Bruijn indices substitution. For traditional uses of de Bruijn indices
without inherent typing, this is a little tricky. The code without intrinsic typing, this is a little tricky. The code
keeps count of a number where all greater indexes are free and keeps count of a number where all greater indexes are free and
all smaller indexes bound, and increment only indexes greater all smaller indexes bound, and increment only indexes greater
than the number. It's easy to have off-by-one errors. But than the number. It's easy to have off-by-one errors. But
it's hard to imagine an off-by-one error that preserves it's hard to imagine an off-by-one error that preserves
typing, and hence the Agda code for inherently-typed de Bruijn typing, and hence the Agda code for intrinsically-typed de Bruijn
terms is inherently reliable. terms is intrinsically reliable.
## Simultaneous Substitution ## Simultaneous Substitution
@ -1331,7 +1346,7 @@ Using the evaluator, confirm that two times two is four.
``` ```
## Inherently-typed is golden ## Intrinsic typing is golden
Counting the lines of code is instructive. While this chapter Counting the lines of code is instructive. While this chapter
covers the same formal development as the previous two covers the same formal development as the previous two
@ -1345,9 +1360,8 @@ number of lines of code is as follows:
DeBruijn 275 DeBruijn 275
The relation between the two approaches approximates the The relation between the two approaches approximates the
golden ratio: raw terms with a separate typing relation golden ratio: extrinsically-typed terms
require about 1.6 times as much code as inherently-typed terms require about 1.6 times as much code as intrinsicaly-typed.
with de Bruijn indices.
## Unicode ## Unicode

View file

@ -13,22 +13,23 @@ module plfa.part2.Inference where
So far in our development, type derivations for the corresponding So far in our development, type derivations for the corresponding
term have been provided by fiat. term have been provided by fiat.
In Chapter [Lambda]({{ site.baseurl }}/Lambda/) In Chapter [Lambda]({{ site.baseurl }}/Lambda/)
type derivations were given separately from the term, while type derivations are extrinsic to the term, while
in Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/) in Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/)
the type derivation was inherently part of the term. type derivations are intrinsic to the term,
but in both we have written out the type derivations in full.
In practice, one often writes down a term with a few decorations and In practice, one often writes down a term with a few decorations and
applies an algorithm to _infer_ the corresponding type derivation. applies an algorithm to _infer_ the corresponding type derivation.
Indeed, this is exactly what happens in Agda: we specify the types for Indeed, this is exactly what happens in Agda: we specify the types for
top-level function declarations, and type information for everything top-level function declarations, and type information for everything
else is inferred from what has been given. The style of inference else is inferred from what has been given. The style of inference
used is based on a technique called _bidirectional_ type Agda uses is based on a technique called _bidirectional_ type
inference, which will be presented in this chapter. inference, which will be presented in this chapter.
This chapter ties our previous developments together. We begin with This chapter ties our previous developments together. We begin with
a term with some type annotations, quite close to the raw terms of a term with some type annotations, close to the raw terms of
Chapter [Lambda]({{ site.baseurl }}/Lambda/), Chapter [Lambda]({{ site.baseurl }}/Lambda/),
and from it we compute a term with inherent types, in the style of and from it we compute an intrinsically-typed term, in the style of
Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/). Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/).
## Introduction: Inference rules as algorithms {#algorithms} ## Introduction: Inference rules as algorithms {#algorithms}
@ -257,7 +258,7 @@ open import Relation.Nullary using (¬_; Dec; yes; no)
``` ```
Once we have a type derivation, it will be easy to construct Once we have a type derivation, it will be easy to construct
from it the inherently typed representation. In order that we from it the intrinsically-typed representation. In order that we
can compare with our previous development, we import can compare with our previous development, we import
module `pfla.DeBruijn`: module `pfla.DeBruijn`:
@ -268,9 +269,7 @@ import plfa.part2.DeBruijn as DB
The phrase `as DB` allows us to refer to definitions The phrase `as DB` allows us to refer to definitions
from that module as, for instance, `DB._⊢_`, which is from that module as, for instance, `DB._⊢_`, which is
invoked as `Γ DB.⊢ A`, where `Γ` has type invoked as `Γ DB.⊢ A`, where `Γ` has type
`DB.Context` and `A` has type `DB.Type`. We also import `DB.Context` and `A` has type `DB.Type`.
`Type` and its constructors directly, so the latter may
also be referred to as just `Type`.
## Syntax ## Syntax
@ -1002,11 +1001,11 @@ _ = refl
## Erasure ## Erasure
From the evidence that a decorated term has the correct type it is From the evidence that a decorated term has the correct type it is
easy to extract the corresponding inherently typed term. We use the easy to extract the corresponding intrinsically-typed term. We use the
name `DB` to refer to the code in name `DB` to refer to the code in
Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/). Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/).
It is easy to define an _erasure_ function that takes evidence of a It is easy to define an _erasure_ function that takes an extrinsic
type judgment into the corresponding inherently typed term. type judgment into the corresponding intrinsically-typed term.
First, we give code to erase a type: First, we give code to erase a type:
``` ```
@ -1056,7 +1055,7 @@ constructors that correspond to switching from synthesized
to inherited or vice versa are dropped. to inherited or vice versa are dropped.
We confirm that the erasure of the type derivations in We confirm that the erasure of the type derivations in
this chapter yield the corresponding inherently typed terms this chapter yield the corresponding intrinsically-typed terms
from the earlier chapter: from the earlier chapter:
``` ```
_ : ∥ ⊢2+2 ∥⁺ ≡ DB.2+2 _ : ∥ ⊢2+2 ∥⁺ ≡ DB.2+2
@ -1068,7 +1067,7 @@ _ = refl
Thus, we have confirmed that bidirectional type inference Thus, we have confirmed that bidirectional type inference
converts decorated versions of the lambda terms from converts decorated versions of the lambda terms from
Chapter [Lambda]({{ site.baseurl }}/Lambda/) Chapter [Lambda]({{ site.baseurl }}/Lambda/)
to the inherently typed terms of to the intrinsically-typed terms of
Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/). Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/).

View file

@ -32,11 +32,12 @@ of variants of lambda calculus.
Be aware that the approach we take here is _not_ our recommended Be aware that the approach we take here is _not_ our recommended
approach to formalisation. Using de Bruijn indices and approach to formalisation. Using de Bruijn indices and
inherently-typed terms, as we will do in intrinsically-typed terms, as we will do in
Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/), Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/),
leads to a more compact formulation. Nonetheless, we begin with named leads to a more compact formulation. Nonetheless, we begin with named
variables, partly because such terms are easier to read and partly variables and extrinsically-typed terms,
because the development is more traditional. partly because names are easier than indices to read,
and partly because the development is more traditional.
The development in this chapter was inspired by the corresponding The development in this chapter was inspired by the corresponding
development in Chapter _Stlc_ of _Software Foundations_ development in Chapter _Stlc_ of _Software Foundations_

View file

@ -32,10 +32,10 @@ the rest as an exercise for the reader.
Our informal descriptions will be in the style of Our informal descriptions will be in the style of
Chapter [Lambda]({{ site.baseurl }}/Lambda/), Chapter [Lambda]({{ site.baseurl }}/Lambda/),
using named variables and a separate type relation, using extrinsically-typed terms,
while our formalisation will be in the style of while our formalisation will be in the style of
Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/), Chapter [DeBruijn]({{ site.baseurl }}/DeBruijn/),
using de Bruijn indices and inherently typed terms. using intrinsically-typed terms.
By now, explaining with symbols should be more concise, more precise, By now, explaining with symbols should be more concise, more precise,
and easier to follow than explaining in prose. and easier to follow than explaining in prose.

View file

@ -12,8 +12,8 @@ module plfa.part2.Untyped where
In this chapter we play with variations on a theme: In this chapter we play with variations on a theme:
* Previous chapters consider inherently-typed calculi; * Previous chapters consider intrinsically-typed calculi;
here we consider one that is untyped but inherently scoped. here we consider one that is untyped but intrinsically scoped.
* Previous chapters consider call-by-value calculi; * Previous chapters consider call-by-value calculi;
here we consider call-by-name. here we consider call-by-name.
@ -128,7 +128,7 @@ Show that `Context` is isomorphic to ``.
## Variables and the lookup judgment ## Variables and the lookup judgment
Inherently typed variables correspond to the lookup judgment. The Intrinsically-scoped variables correspond to the lookup judgment. The
rules are as before: rules are as before:
``` ```
data _∋_ : Context → Type → Set where data _∋_ : Context → Type → Set where
@ -153,7 +153,7 @@ binds two variables.
## Terms and the scoping judgment ## Terms and the scoping judgment
Inherently typed terms correspond to the typing judgment, but with Intrinsically-scoped terms correspond to the typing judgment, but with
`★` as the only type. The result is that we check that terms are `★` as the only type. The result is that we check that terms are
well scoped — that is, that all variables they mention are in scope — well scoped — that is, that all variables they mention are in scope —
but not that they are well typed: but not that they are well typed: