summaryrefslogtreecommitdiff
path: root/sec/systemt.ltx
diff options
context:
space:
mode:
Diffstat (limited to 'sec/systemt.ltx')
-rw-r--r--sec/systemt.ltx330
1 files changed, 249 insertions, 81 deletions
diff --git a/sec/systemt.ltx b/sec/systemt.ltx
index 12b1ed2..7881396 100644
--- a/sec/systemt.ltx
+++ b/sec/systemt.ltx
@@ -4,32 +4,23 @@
\section{System T}%
\label{sec:systemt}
-G\"odel's System~T extends the simply-typed lambda calculus by adding natural
-numbers and the primitiver recursor. The type formers are \(\nat\) for natural
-numbers and arrows \(A \to B\) for functions.
-
-\Cref{fig:systemt-type} shows the typing rules for System~T terms. On top of the
-usual abstraction and application rules, we can construct unary natural numbers
-using the operators \(\zero\) and \(\suc\). Natural numbers are eliminated using
-the primitive recursor \primreckw. Given a base case, update function and target
-\(n\), the primitive recursor applies the update function to the target \(n\)
-times. We will abuse notation and treat the \(\suc\) and \primreckw operators as
-functions.
+System~T is a simply-typed lambda calculus. The types are naturals \nat{} and
+functions \(A \to B\), and on top of function abstraction and application, we
+have \zero{} and \suc{} for zero and successor, as well as \primreckw{}, the
+primitive recursor. \Cref{fig:syst-typing} shows the typing judgements and
+\cref{fig:syst-eq} the equational theory.
\begin{figure}
\[
\begin{array}{ccc}
\begin{prooftree}
- \hypo{
- x : A \in \Gamma
- \vphantom{\judgement[T]{}{}{}} %% Spooky formatting phantom
- }
+ \hypo{x : A \in \Gamma}
\infer1{\judgement[T]{\Gamma}{x}{A}}
\end{prooftree}
&
\begin{prooftree}
- \hypo{\judgement[T]{\Gamma, x \colon A}{t}{B}}
- \infer1{\judgement[T]{\Gamma}{\lambda x.~t}{A \to B}}
+ \hypo{\judgement[T]{\Gamma, x : A}{t}{B}}
+ \infer1{\judgement[T]{\Gamma}{\lambda x. t}{A \to B}}
\end{prooftree}
&
\begin{prooftree}
@@ -37,11 +28,9 @@ functions.
\hypo{\judgement[T]{\Gamma}{t}{A}}
\infer2{\judgement[T]{\Gamma}{f~t}{B}}
\end{prooftree}
- \\
- \\
+ \\\\
\begin{prooftree}
- \hypo{\vphantom{\judgement[T]{}{}{}}} %% Spooky formatting phantom
- \infer1{\judgement[T]{\Gamma}{\zero}{\nat}}
+ \infer0{\judgement[T]{\Gamma}{\zero}{\nat}}
\end{prooftree}
&
\begin{prooftree}
@@ -50,81 +39,260 @@ functions.
\end{prooftree}
&
\begin{prooftree}
- \hypo{\judgement[T]{\Gamma}{z}{A}}
- \hypo{\judgement[T]{\Gamma}{s}{A \to A}}
\hypo{\judgement[T]{\Gamma}{t}{\nat}}
- \infer3{\judgement[T]{\Gamma}{\primrec{z}{s}{t}}{A}}
+ \hypo{\judgement[T]{\Gamma}{u}{A}}
+ \hypo{\judgement[T]{\Gamma, x : A}{v}{A}}
+ \infer3{\judgement[T]{\Gamma}{\primrec{t}{u}{(x.v)}}{A}}
\end{prooftree}
\end{array}
\]
- \caption{Typing judgements for System~T}%
- \label{fig:systemt-type}
+ \caption{Typing judgements for System~T}\label{fig:syst-typing}
\end{figure}
-The equational theory is defined in \cref{fig:systemt-eq}. We have the standard
-\(\beta\)-reduction for functions. When we apply the primitve recursor to target
-zero, we get the base case. When the recursor is applied to target \(\suc~t\),
-we apply the update function to the result of applying \primreckw{} recursively.
-An alternative theory would instead apply the update function directly to the
-base case instead of to the overall result of the recursion. Whilst this has
-little impact for System~T (and operationally corresponds to tail recursion) the
-alternative rule does not generalise to the more complex recursive types found
-in \lang{}.
-
\begin{figure}
\begin{align*}
- (\lambda x.~t)~u &= \sub{t}{x/u} &
- \primrec{z}{s}{\zero} &= z &
- \primrec{z}{s}{(\suc~t)} &= s~(\primrec{z}{x}{s}{t})
+ (\lambda x. t)~u &= \sub{t}{x/u} \\
+ \primrec{\zero}{u}{(x.v)} &= u \\
+ \primrec{(\suc~t)}{u}{(x.v)} &= \sub{v}{x/\primrec{t}{u}{(x.v)}}
\end{align*}
- \caption{Equational theory for System~T. \TODO{add eta for unions}}%
- \label{fig:systemt-eq}
+ \caption{Equational theory for System~T}\label{fig:syst-eq}
\end{figure}
-The primitive recursor is named as such because it generalises the first-order
-primitive recursive functions. For example, we can define addition as
-\[
-k + n = \primrec{n}{\suc}{k}.
-\]
-Despite its name, the primitive recursor can compute more than the primitive
-recursive functions. By exploiting the higher-order nature of System~T, one can
-define the Ackermann-P\'eter function~\cite{semantic-domain} as
+Types in System~T are usually presented as binary trees, with branches
+representing arrows and leaves being \(\nat\). An alternative presentation is
+\emph{argument form}~\cite{DBLP:books/sp/LongleyN15}. Notice that all types can
+be written in the form \(A_1 \to \cdots \to A_n \to \nat\) for some unique list of argument
+types \(A_i\). We can therefore represent a type by its list of arguments. For
+example, the type \(\nat\) has argument form \(\epsilon\), and the type \(\nat \to \nat \to
+(\nat \to \nat) \to \nat\) has argument form \([\epsilon, \epsilon, [\epsilon]]\).
+
+Argument form makes it obvious that all types in System~T are inhabited. For
+example, we can use the constantly zero function as an arbitrary inhabitant.
+
+\subsection{Strategies for Encoding Inductive Types}%
+\label{subsec:encoding-strategies}
+
+Inductive types are a useful programming abstraction that is missing from
+System~T. If we are to effectively encode an inductive type, there are three
+operations we would like to use:
+\begin{enumerate}
+ \item constructors
+ \item folding
+ \item pattern matching
+\end{enumerate}
+We need constructors else we cannot write values of the inductive type. We need
+folding so we can fully consume the inductive type in a total way. We need
+pattern matching so we can iterate over multiple values. As a concrete example,
+consider the pseudocode in \cref{lst:tree-eq} for comparing whether two binary
+trees are equal. We use a fold to create an equality predicate out of
+\systemtinline{tree1}. When \systemtinline{tree1} is a leaf, we pattern match on
+the other tree to see if it is also a leaf. If \systemtinline{tree1} is instead
+a branch, we obtain equality predicates for its two subtrees by its induction
+principal. We again pattern match on the other tree to see if it also a branch,
+and test equality recursively.
+
+\begin{listing}
+ \begin{systemt}
+ let equal tree1 tree2 =
+ let go = fold tree1 with
+ Leaf k => fun t => match t with
+ Leaf n => k == n
+ | Branch _ _ => false
+ | Branch eql eqr => fun t => match t with
+ Leaf _ => false
+ | Branch l r => eql l && eqr r
+ in
+ go tree2
+ \end{systemt}
+ \caption{Pseudocode for determining equality of two binary trees.}\label{lst:tree-eq}
+\end{listing}
+
+In the remainder of the section we outline four different strategies for
+encoding inductive types within System~T. We judge each encoding on whether it
+is a local, simply-typed transformation as well as how easy it is to hand write
+the three fundamental operators. As you can see from the summary in
+\cref{tbl:encodings}, each strategy is lacking in some regard.
+
+\begin{table}
+ \caption{Comparison of encoding strategies for inductive types within
+ System~T. The first column assesses if the strategy is a local, simply-typed
+ transformation. The remainder indicate whether it is ``easy'' to hand write
+ the constructors, fold and pattern matching
+ respectively.}\label{tbl:encodings}
+ \begin{tabular}{lcccc}
+ \toprule
+ Strategy & Local & Constructors & Fold & Pattern Match \\
+ \midrule
+ G\"odel & \ding{51} & \ding{51} & \ding{55} & \ding{51} \\
+ Church & \ding{55} & \ding{51} & \ding{51} & \ding{55} \\
+ Eliminator & \ding{51} & \ding{51} & \ding{55} & \ding{51} \\
+ Heap & \ding{51} & \ding{55} & \ding{51} & \ding{51} \\
+ \bottomrule
+ \end{tabular}
+\end{table}
+
+\subsubsection*{G\"odel Encodings}
+
+G\"odel encodings represent inductive data types as natural numbers. It is
+well-known that there are pairing functions, bijections from \(\mathbb{N} \times
+\mathbb{N}\) to \(\mathbb{N}\), that are primitive recursive. We can use these
+functions to encode inductive data types as pairs of tags and values. Writing
+\(\tuple{\cdot, \cdot}\) for the pairing function, we can encode the binary
+tree constructors as
+\begin{align*}
+ \mathsf{leaf}(n) &\coloneq \tuple{0, n} \\
+ \mathsf{branch}(l, r) &\coloneq \tuple{1, \tuple{l, r}}.
+\end{align*}
+Pattern matching on G\"odel encodings is achieved by using the unpairing part of
+the bijection to determine the specific constructor and its payload.
+
+One major limitation of G\"odel encodings is that they cannot represent
+higher-order data such as functions. The defining trait of G\"odel encodings is
+that they represent data by natural numbers, and as function types cannot be
+encoded as naturals, we cannot encode functions. We also cannot use a syntactic
+representation of functions; \textcite{Squid:unpublished/Bauer17} proves there
+is no System~T term that can recover a function from its syntax.
+
+Another difficulty with G\"odel encodings is implementing the fold operation.
+We require our choice of pairing function to be monotonic so that an encoded
+value is ``large enough'' to iterate over. The fold with motive type \(A\) then
+proceeds with a primitive recursion over the successor of the natural
+representing the target with motive type \(\nat \to A\). The intent is that the
+\((n+1)\)-th stage of recursion will correctly fold a target with encoding
+\(n\). There are no expectations for the base case for the recursion, so we can
+use an arbitrary value. For the successor case, we use pattern matching to
+deconstruct the input encoded value into its constructor and payload. We can use
+the inductive argument to fold over recursive values in the payload, and as the
+pairing function is monotonic, these values must be smaller than the current
+case, so the resulting value is correct. We then apply the appropriate case for
+the fold. Below is the fold over binary trees.
\[
-\lambda k, m.~\primrec{\suc}{(\lambda f.~\primrec{(\lambda n.~n)}{(\lambda g, n.~f~(g~n))})}{k}~m.
+ \dofoldmatch*[t]{t}{
+ \mathsf{leaf}(n). f(n);
+ \mathsf{branch}(x, y). g(x, y)
+ } \coloneq
+ \dolet{
+ go = \doprimrec*{t}{\arb}{h}{
+ \lambda i. \domatch*{i}{
+ \mathsf{leaf}(n). f(n);
+ \mathsf{branch}(l, r). g(h(l), h(r))}}
+ }*{go~t}
\]
-Any countable set can be encoded by natural numbers. For inductively defined
-sets, such as lists or abstract syntax trees for programs, one can use a G\"odel
-encoding to represent values as natural numbers. This depends on a pairing
-function \(\pairingkw : \nat \to \nat \to \nat\) that is a (curried)
-bijection \(\nat \times \nat \to
-\nat\)~\cites{DiscoverEd:book/oc/Cantor52}{szudzik}. As some examples, we can
-encode lists of natural numbers as
+\subsubsection*{Church Encodings}
+
+Typically used in untyped settings, Church encodings represent data by their
+fold operation. For example binary trees are Church encoded by the type \(\forall A.
+(\nat \to A) \to (A \to A \to A) \to A\). The first argument describes how to deconstruct
+leaves, and the second applies the inductive step on branches. This encoding
+makes implementing fold trivial. Constructors for the inductive types are also
+simple to encode, as demonstrated by the following example:
+\begin{align*}
+ \mathsf{leaf}(n) &\coloneq \lambda l, b.~l~n
+ &
+ \mathsf{branch}(x, y) &\coloneq \lambda l, b.~b~(x~l~b)~(y~l~b)
+\end{align*}
+
+Whilst Church encodings are great for representing folds and constructors,
+pattern matching becomes more challenging. The most general strategy is to use
+the fold to recover a sum over the possible constructors, and then eliminate
+this using the clauses of the pattern match. For example, we would fold a binary
+tree into the sum \(\nat + T \times T\), representing leaves and branches
+respectively, and then case split on this sum. We detail this in \cref{fig:church-pm}
+
+\begin{figure}
+ \[
+ \domatch*[t]{t}{
+ \mathsf{leaf}(n). f(n);
+ \mathsf{branch}(x,y). g(x, y)
+ } \coloneq
+ \dolet[t]{
+ \roll = \lambda x. \domatch*[t]{x}{
+ \mathsf{Left}(n). \mathsf{leaf}(n);
+ \mathsf{Right}(x, y). \mathsf{branch}(x, y)
+ }
+ }{
+ x = \dofoldmatch*{t}{
+ \mathsf{leaf}(n). \mathsf{Left}(n);
+ \mathsf{branch}(x, y). \mathsf{Right}(\roll~x, \roll~y)
+ }
+ }*{
+ \domatch*{x}{
+ \mathsf{Left}(n). f(n);
+ \mathsf{Right}(x, y). g(x, y)
+ }
+ }
+ \]
+ \caption{How to perform pattern matching on binary trees for Church encodings.
+ We assume we have an encoding for sums with pattern matching.}\label{fig:church-pm}
+\end{figure}
+
+You may have noticed that this encoding included a type quantification---in
+general, one must use polymorphism to fully represent Church encodings. To avoid
+polymorphism requires a global analysis, replacing the polymorphic bound with a
+(hopefully) finite product of possible consumers.
+
+\subsubsection*{Eliminator Encodings}
+
+This encoding strategy generalises Church encodings. Rather than encoding
+inductive types by their folds, eliminator encodings represent inductive types
+as a product of all their (contravariant) consumers. For example, if we know the
+only operations we will perform on a binary tree are finding the sum of all
+leaves and the depth of the tree. We can encode the binary tree as a pair \(\nat
+\times \nat\), where the first value is the sum and the second is the depth.
+
+In this encoding, the constructors eagerly compute the eliminated values. With
+our sum and depth example, we encode the constructors by
\begin{align*}
- \encode{\epsilon} &\coloneq \pairing{0}{0} &
- \encode{n, l} &\coloneq \pairing{1}{(\pairing{n}{\encode{l}})} \\
- \shortintertext{and System~T terms (using deBruijn indices) as}
- \encode{x_i} &\coloneq \pairing{0}{i} &
- \encode{\zero} &\coloneq \pairing{3}{0} \\
- \encode{\lambda~t} &\coloneq \pairing{1}{\encode{t}} &
- \encode{\suc~t} &\coloneq \pairing{4}{\encode{t}} \\
- \encode{t~u} &\coloneq \pairing{2}{(\pairing{\encode{t}}{\encode{u}})} &
- \encode{\primrec{z}{s}{t}} &\coloneq
- \pairing{5}{(\pairing{\encode{z}}{(\pairing{\encode{s}}{\encode{t}})})}.
+ \mathsf{leaf}(n) &\coloneq \tuple{n, 1}
+ &
+ \mathsf{branch}(x, y) &\coloneq \tuple{x.0 + y.0, \suc~(x.1 \sqcup y.1)}
\end{align*}
-In general, each constructor has a unique tag and this is paired with an
-encoding for each subterm. We can analyse an encoded term by reverting the
-pairing function and inspecting the tag. For an invalid tag (for our encoding of
-lists this is anything greater than 2) we can return an arbitrary value.
-Performing recursion on G\"odel encodings is difficult, particularly for values
-with more than one recursive argument.
-
-An alternative encoding strategy uses functions to encode data. One example we
-have already given is our encoding of binary trees, from the introduction.
-Another functional encoding is to represent lists as functions \(\nat \to
-\nat\). Giving the function \zero{} will return the length of the list, and the
-values are 1-indexed. This strategy doesn't require a pairing function, but
-values no longer have a unique representation. For example, the functions
-\(\lambda n.~n + n\) and \(\lambda n. \zero\) both represent the empty list.
+using \(k \sqcup n\) to compute the maximum of two naturals. We can easily encode the
+specified eliminators too; simply take the corresponding projection from the
+product.
+
+Another benefit of eliminator encodings are that the constructors are
+extensible. As no information about exactly which constructor was used remains
+in the encoded type, we are free to add some interesting constructors. For
+example, we can add the constructor \(\mathsf{double}\) that doubles the value
+of each leaf:
+\[
+\mathsf{double}(x) \coloneq \tuple{2 \cdot x.0, x.1}
+\]
+
+Eliminator encodings inherit the weaknesses of Church encodings. Defining an
+arbitrary fold requires polymorphism. Pattern matching is also impossible in the
+general case. With our sum and depth example, we have no way of knowing whether
+the value representing a tree came from a leaf or branch as we deliberately
+forget this information.
+
+\subsubsection*{Heap Encodings}
+
+This encoding strategy is a crude approximation of a heap of memory. Given an
+indexing type \(I\), an inductive type is a triple of a \emph{recursive depth},
+a \emph{root index}, and an \(I\)-indexed \emph{heap} of values. Instead of
+storing recursive values ``in-line'', one stores an index to its place in the heap.
+
+Consider the binary tree \(\mathsf{branch}(\mathsf{branch}(\mathsf{leaf}(1),
+\mathsf{leaf}(2)), \mathsf{branch}(\mathsf{leaf}(3),
+\mathsf{branch}(\mathsf{leaf}(4), \mathsf{leaf}(5))))\), depicted
+diagramatically in \cref{fig:box-pointer:tree}. The recursive depth of the tree
+is three, as there are at most three nested constructors (e.g.\ the path from
+root to the value four).
+
+
+%% This encoding strategy is a crude approximation of a heap of memory.
+%% Implementing this strategy requires choosing a (first-order) indexing type. Due
+%% to G\"odel encodings, \nat{} is sufficient. In \cref{subsec:encode-inductive} we
+%% use lists of naturals to better reflect the tree-like structure of inductive
+%% values.
+
+\TODO{
+ \begin{itemize}
+ \item describe with a box-and-pointer diagram
+ \item explain briefly how to fold and roll
+ \end{itemize}
+}
\end{document}