|
|
|
@ -15027,7 +15027,7 @@ third-order function. |
|
|
|
\[ \Count_n : ((\Nat \to \Bool) \to \Bool) \to \Nat \] |
|
|
|
% |
|
|
|
A \naive implementation strategy is simply to apply $P$ to each of the |
|
|
|
$2^n$ vectors in turn. However, one can do better with an arcane |
|
|
|
$2^n$ vectors in turn. However, one can do better with a curious |
|
|
|
approach due to \citet{Berger90}, which achieves the effect of `pruned |
|
|
|
search' where the predicate allows it. This should be taken as a |
|
|
|
warning that counter-intuitive phenomena can arise in this territory. |
|
|
|
@ -15054,43 +15054,44 @@ asymptotic gain of a factor of $n$. |
|
|
|
% $\BigO(2^n)$ runtime for generic count with effect handlers also |
|
|
|
% transfers to generic search. |
|
|
|
|
|
|
|
The idea behind the speedup is readily explained and will already be |
|
|
|
familiar, at least informally, to programmers who have worked with |
|
|
|
multi-shot continuations. |
|
|
|
% |
|
|
|
Suppose for example $n=3$, and suppose that the predicate $P$ always |
|
|
|
inspects the components of its argument in the order $0,1,2$. |
|
|
|
% |
|
|
|
A \naive implementation of $\Count_3$ might start by applying the given |
|
|
|
$P$ to $q_0 = (\True,\True,\True)$, and then to |
|
|
|
$q_1 = (\True,\True,\False)$. Clearly there is some duplication here: |
|
|
|
the computations of $P\,q_0$ and $P\,q_1$ will proceed identically up |
|
|
|
to the point where the value of the final component is requested. What |
|
|
|
we would like to do, then, is to record the state of the computation |
|
|
|
of $P\,q_0$ at just this point, so that we can later resume this |
|
|
|
The key to enabling the speedup is \emph{backtracking} via multi-shot |
|
|
|
resumptions. The idea is to memorise the control state at each |
|
|
|
component inspection to make it possible to quickly backtrack to a |
|
|
|
prior inspection and make a different decision as soon as one |
|
|
|
possible result has been computed. |
|
|
|
% |
|
|
|
Concretely, suppose for example $n = 3$, and suppose that the predicate |
|
|
|
$P$ always inspects the components of its argument in the order |
|
|
|
$0,1,2$. |
|
|
|
% |
|
|
|
A \naive implementation of $\Count_3$ might start by applying the |
|
|
|
given predicate $P$ to $q_0 = (\True,\True,\True)$, and then to |
|
|
|
$q_1 = (\True,\True,\False)$. Note that there is some duplication |
|
|
|
here: the computations of $P\,q_0$ and $P\,q_1$ will proceed |
|
|
|
identically up to the point where the value of the final component is |
|
|
|
requested. Ideally, we would record the state of the computation of |
|
|
|
$P\,q_0$ at just this point, so that we can later resume this |
|
|
|
computation with $\False$ supplied as the final component value in |
|
|
|
order to obtain the value of $P\,q_1$. (Similarly for all other |
|
|
|
internal nodes in the evident binary tree of boolean vectors.) Of |
|
|
|
course, this `backup' approach would be standardly applied if one were |
|
|
|
implementing a bespoke search operation for some \emph{particular} |
|
|
|
choice of $P$ (corresponding, say, to the $n$-queens problem); but to |
|
|
|
apply this idea of resuming previous subcomputations in the |
|
|
|
\emph{generic} setting (that is, uniformly in $P$) requires some |
|
|
|
special language feature such as effect handlers or multi-shot |
|
|
|
continuations. |
|
|
|
% |
|
|
|
One could also obviate the need for such a feature by choosing to |
|
|
|
present the predicate $P$ in some other way, but from our present |
|
|
|
perspective this would be to move the goalposts: our intention is |
|
|
|
precisely to show that our languages differ in an essential way |
|
|
|
\emph{as regards their power to manipulate data of type} $(\Nat \to |
|
|
|
\Bool) \to \Bool$. |
|
|
|
|
|
|
|
This idea of using first-class control to achieve backtracking has |
|
|
|
been exploited before and is fairly widely known (see |
|
|
|
e.g. \citep{KiselyovSFA05}), and there is a clear programming |
|
|
|
intuition that this yields a speedup unattainable in languages without |
|
|
|
such control features. % Our main contribution in this paper is to |
|
|
|
order to obtain the value of $P\,q_1$. Of course, a bespoke search |
|
|
|
function implementation would apply this backtracking behaviour in a |
|
|
|
standard manner for some \emph{particular} choice of $P$ (e.g. the |
|
|
|
$n$-queens problem); but to apply this idea of resuming previous |
|
|
|
subcomputations in the \emph{generic} setting (i.e. uniformly in $P$) |
|
|
|
requires some special control feature such as effect handlers with |
|
|
|
multi-shot resumptions. |
|
|
|
% |
|
|
|
Obviously, one can remove the need a special control feature by a |
|
|
|
change of type for the predicate $P$, but this such a change shifts |
|
|
|
the perspective. The intention is precisely to show that the languages |
|
|
|
differ in an essential way as regards to their power to manipulate |
|
|
|
data of type $(\Nat \to \Bool) \to \Bool$. |
|
|
|
|
|
|
|
The idea of using first-class control achieve backtracking is fairly |
|
|
|
well-known in the literature~\cite{KiselyovSFA05}, and there is a |
|
|
|
clear programming intuition that this yields a speedup unattainable in |
|
|
|
languages without such control features. |
|
|
|
|
|
|
|
% Our main contribution in this paper is to |
|
|
|
% provide, for the first time, a precise mathematical theorem that pins |
|
|
|
% down this fundamental efficiency difference, thus giving formal |
|
|
|
% substance to this intuition. Since our goal is to give a realistic |
|
|
|
@ -15189,8 +15190,7 @@ Fine-grain call-by-value is similar to A-normal |
|
|
|
form~\cite{FlanaganSDF93} in that every intermediate computation is |
|
|
|
named, but unlike A-normal form is closed under reduction. |
|
|
|
|
|
|
|
The syntax of $\BPCF$ is as follows. |
|
|
|
{\noindent |
|
|
|
\begin{figure} |
|
|
|
\begin{syntax} |
|
|
|
\slab{Types} &A,B,C,D\in\TypeCat &::= & \Nat \mid \One \mid A \to B \mid A \times B \mid A + B \\ |
|
|
|
\slab{Type\textrm{ }environments} &\Gamma\in\TyEnvCat &::= & \cdot \mid \Gamma, x:A \\ |
|
|
|
@ -15203,7 +15203,12 @@ The syntax of $\BPCF$ is as follows. |
|
|
|
& &\mid&\Case \; V \;\{ \Inl \; x \mapsto M; \Inr \; y \mapsto N\}\\ |
|
|
|
& &\mid& \Return\; V |
|
|
|
\mid \Let \; x \revto M \; \In \; N \\ |
|
|
|
\end{syntax}}% |
|
|
|
\end{syntax} |
|
|
|
\caption{Syntax of $\BPCF$.}\label{fig:bpcf} |
|
|
|
\end{figure} |
|
|
|
% |
|
|
|
Figure~\ref{fig:bpcf} depicts the type syntax, type environment |
|
|
|
syntax, and term syntax of $\BPCF$. |
|
|
|
% |
|
|
|
The ground types are $\Nat$ and $\One$ which classify natural number |
|
|
|
values and the unit value, respectively. The function type $A \to B$ |
|
|
|
@ -15213,7 +15218,7 @@ whose first and second components have types $A$ and $B$ |
|
|
|
respectively. The sum type $A + B$ classifies tagged values of either |
|
|
|
type $A$ or $B$. |
|
|
|
% |
|
|
|
Type environments $\Gamma$ map term variables to their types. |
|
|
|
Type environments $\Gamma$ map variables to their types. |
|
|
|
|
|
|
|
We let $k$ range over natural numbers and $c$ range over primitive |
|
|
|
operations on natural numbers ($+, -, =$). |
|
|
|
@ -15224,17 +15229,17 @@ For convenience, we also use $f$, $g$, and $h$ for variables of |
|
|
|
function type, $i$ and $j$ for variables of type $\Nat$, and $r$ to |
|
|
|
denote resumptions. |
|
|
|
% |
|
|
|
The value terms are standard. |
|
|
|
% Value terms comprise variables ($x$), the unit value ($\Unit$), |
|
|
|
% natural number literals ($n$), primitive constants ($c$), lambda |
|
|
|
% abstraction ($\lambda x^A . \, M$), recursion |
|
|
|
% ($\Rec \; f^{A \to B}\, x.M$), pairs ($\Record{V, W}$), left |
|
|
|
% ($(\Inl~V)^B$) and right $((\Inr~W)^A)$ injections. |
|
|
|
% The value terms are standard. |
|
|
|
Value terms comprise variables ($x$), the unit value ($\Unit$), |
|
|
|
natural number literals ($n$), primitive constants ($c$), lambda |
|
|
|
abstraction ($\lambda x^A . \, M$), recursion |
|
|
|
($\Rec \; f^{A \to B}\, x.M$), pairs ($\Record{V, W}$), left |
|
|
|
($(\Inl~V)^B$) and right $((\Inr~W)^A)$ injections. |
|
|
|
|
|
|
|
% |
|
|
|
We will occasionally blur the distinction between object and meta |
|
|
|
language by writing $A$ for the meta level type of closed value terms |
|
|
|
of type $A$. |
|
|
|
% We will occasionally blur the distinction between object and meta |
|
|
|
% language by writing $A$ for the meta level type of closed value terms |
|
|
|
% of type $A$. |
|
|
|
% |
|
|
|
All elimination forms are computation terms. Abstraction is eliminated |
|
|
|
using application ($V\,W$). |
|
|
|
|