From 83b5f7db99e5c2fca5dd7ee7142b3622e07ce6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Wed, 24 Nov 2021 15:20:50 +0000 Subject: [PATCH] Start implementing the corrections. --- thesis.tex | 26747 +++++++++++++++++++++++++-------------------------- 1 file changed, 13238 insertions(+), 13509 deletions(-) diff --git a/thesis.tex b/thesis.tex index f9f181c..8617f1c 100644 --- a/thesis.tex +++ b/thesis.tex @@ -1072,6 +1072,7 @@ $\runState$ function to apply the $\incrEven$ function. % \subsection{Monadic epoch} \subsection{Monadic state} +\label{sec:monadic-state} During the late 80s and early 90s monads rose to prominence as a practical programming idiom for structuring effectful programming~\cite{Moggi89,Moggi91,Wadler92,Wadler92b,JonesW93,Wadler95,JonesABBBFHHHHJJLMPRRW99}. @@ -2040,14 +2041,9 @@ handlers extension to Prolog; and \citet{Leijen17b} has an imperative take on effect handlers in C. \section{Contributions} -The key contributions of this dissertation are spread across the four +The key contributions of this dissertation are spread across the three main parts. The following listing summarises the contributions of each part. -\paragraph{Background} -\begin{itemize} - \item A comprehensive operational characterisation of various - notions of continuations and first-class control phenomena. -\end{itemize} \paragraph{Programming} \begin{itemize} \item A practical design for a programming language equipped with a @@ -2081,25 +2077,16 @@ part. can improve the asymptotic runtime of certain classes of programs. \end{itemize} +Another contribution worth noting is the continuation literature +review in Appendix~\ref{ch:continuation}, which provides a +comprehensive operational characterisation of various notions of +continuations and first-class control phenomena. + \section{Structure of this dissertation} The following is a summary of the chapters belonging to each part of this dissertation. -\paragraph{Background} -\begin{itemize} - \item Chapter~\ref{ch:maths-prep} defines some basic mathematical -notation and constructions that are they pervasively throughout this -dissertation. - - \item Chapter~\ref{ch:continuations} presents a literature survey of -continuations and first-class control. I classify continuations -according to their operational behaviour and provide an overview of -the various first-class sequential control operators that appear in -the literature. The application spectrum of continuations is discussed -as well as implementation strategies for first-class control. -\end{itemize} - \paragraph{Programming} \begin{itemize} \item Chapter~\ref{ch:base-language} introduces a polymorphic fine-grain @@ -2159,272 +2146,297 @@ implementation of generic count than any $\BPCF$ implementation. \item Chapter~\ref{ch:conclusions} concludes and discusses future work. \end{itemize} +\paragraph{Appendices} +\begin{itemize} +% \item Chapter~\ref{ch:maths-prep} defines some basic mathematical +% notation and constructions that are they pervasively throughout this +% dissertation. -\part{Background} -\label{p:background} + \item Appendix~\ref{ch:continuations} presents a literature survey of +continuations and first-class control. I classify continuations +according to their operational behaviour and provide an overview of +the various first-class sequential control operators that appear in +the literature. The application spectrum of continuations is discussed +as well as implementation strategies for first-class control. -\chapter{Mathematical preliminaries} -\label{ch:maths-prep} +\item Appendix~\ref{ch:get-get} presents a small proof for the claim + made in Section~\ref{sec:monadic-state}, that the state equation + ``Get after get'' is redundant. -Only a modest amount of mathematical proficiency should be necessary -to be able to wholly digest this dissertation. -% -This chapter introduces some key mathematical concepts that will -either be used directly or indirectly throughout this dissertation. -% +\item Appendix~\ref{sec:proofs-cps-gen-cont} contains the proof + details for the proof of correctness of the higher-order + continuation-passing style translation developed in Chapter~\ref{ch:cps}. -I assume familiarity with basic programming language theory including -structural operational semantics~\cite{Plotkin04a} and System F type -theory~\cite{Girard72}. For a practical introduction to programming -language theory I recommend consulting \citeauthor{Pierce02}'s -excellent book \emph{Types and Programming - Languages}~\cite{Pierce02}. For the more theoretical inclined I -recommend \citeauthor{Harper16}'s book \emph{Practical Foundations for - Programming Languages}~\cite{Harper16} (do not let the ``practical'' -qualifier deceive you) --- the two books complement each other nicely. +\item \dhil{TODO inline Appendix~\ref{sec:berger-count} into Chapter~\ref{ch:handlers-efficiency}?}\end{itemize} -\section{Relations} -\label{sec:relations} -Relations feature prominently in the design and understanding of the -static and dynamic properties of programming languages. The interested -reader is likely to already be familiar with the basic concepts of -relations, although this section briefly introduces the concepts, its -real purpose is to introduce the notation that I am using pervasively -throughout this dissertation. -% +\dhil{TODO introduce relation notation} -I assume familiarity with basic set theory. +% \part{Background} +% \label{p:background} -\begin{definition} - The Cartesian product of two sets $A$ and $B$, written $A \times B$, - is the set of all ordered pairs $(a, b)$, where $a$ is drawn from - $A$ and $b$ is drawn from $B$, i.e. - % - \[ - A \times B \defas \{ (a, b) \mid a \in A, b \in B \} - \] - % -\end{definition} -% -Since the Cartesian product is itself a set, we can take the Cartesian -product of it with another set, e.g. $A \times B \times C$. However, -this raises the question in which order the product operator -($\times$) is applied. In this dissertation the product operator is -taken to be right associative, meaning -$A \times B \times C = A \times (B \times C)$. -% -%\dhil{Define tuples (and ordered pairs)?} -% -% To make the notation more compact for the special case of $n$-fold -% product of some set $A$ with itself we write -% $A^n \defas A \underbrace{\times \cdots \times}_{n \text{ times}} A$. -% +% \chapter{Mathematical preliminaries} +% \label{ch:maths-prep} +% Only a modest amount of mathematical proficiency should be necessary +% to be able to wholly digest this dissertation. +% % +% This chapter introduces some key mathematical concepts that will +% either be used directly or indirectly throughout this dissertation. +% % -\begin{definition} - A relation $R$ is a subset of the Cartesian product of two sets $A$ - and $B$, i.e. $R \subseteq A \times B$. - % - An element $a \in A$ is related to an element $b \in B$ if - $(a, b) \in R$, sometimes written using infix notation $a\,R\,b$. - % - If $A = B$ then $R$ is said to be a \emph{homogeneous} relation. -\end{definition} -% +% I assume familiarity with basic programming language theory including +% structural operational semantics~\cite{Plotkin04a} and System F type +% theory~\cite{Girard72}. For a practical introduction to programming +% language theory I recommend consulting \citeauthor{Pierce02}'s +% excellent book \emph{Types and Programming +% Languages}~\cite{Pierce02}. For the more theoretical inclined I +% recommend \citeauthor{Harper16}'s book \emph{Practical Foundations for +% Programming Languages}~\cite{Harper16} (do not let the ``practical'' +% qualifier deceive you) --- the two books complement each other nicely. + +% \section{Relations} +% \label{sec:relations} + +% Relations feature prominently in the design and understanding of the +% static and dynamic properties of programming languages. The interested +% reader is likely to already be familiar with the basic concepts of +% relations, although this section briefly introduces the concepts, its +% real purpose is to introduce the notation that I am using pervasively +% throughout this dissertation. +% % -\begin{definition} - For any two relations $R \subseteq A \times B$ and - $S \subseteq B \times C$ their composition is defined as follows. - % - \[ - S \circ R \defas \{ (a,c) \mid (a,b) \in R, (b, c) \in S \} - \] -\end{definition} +% I assume familiarity with basic set theory. -The composition operator ($\circ$) is associative, meaning -$(T \circ S) \circ R = T \circ (S \circ R)$. -% -For $n \in \N$ the $n$th relational power of a relation $R$, written -$R^n$, is defined inductively. -\[ - R^0 \defas \emptyset, \quad\qquad R^1 \defas R, \quad\qquad R^{1 + n} \defas R \circ R^n. -\] -% -Homogeneous relations play a prominent role in the operational -understanding of programming languages as they are used to give -meaning to program reductions. There are two particular properties and -associated closure operations of homogeneous relations that reoccur -throughout this dissertation. -% -\begin{definition} - A homogeneous relation $R \subseteq A \times A$ is said to be - reflexive and transitive if its satisfies the following criteria, - respectively. - \begin{itemize} - \item Reflexive: $\forall a \in A$ it holds that $a\,R\,a$. - \item Transitive: $\forall a,b,c \in A$ if $a\,R\,b$ and $b\,R\,c$ - then $a\,R\,c$. - \end{itemize} -\end{definition} +% \begin{definition} +% The Cartesian product of two sets $A$ and $B$, written $A \times B$, +% is the set of all ordered pairs $(a, b)$, where $a$ is drawn from +% $A$ and $b$ is drawn from $B$, i.e. +% % +% \[ +% A \times B \defas \{ (a, b) \mid a \in A, b \in B \} +% \] +% % +% \end{definition} +% % +% Since the Cartesian product is itself a set, we can take the Cartesian +% product of it with another set, e.g. $A \times B \times C$. However, +% this raises the question in which order the product operator +% ($\times$) is applied. In this dissertation the product operator is +% taken to be right associative, meaning +% $A \times B \times C = A \times (B \times C)$. +% % +% %\dhil{Define tuples (and ordered pairs)?} +% % +% % To make the notation more compact for the special case of $n$-fold +% % product of some set $A$ with itself we write +% % $A^n \defas A \underbrace{\times \cdots \times}_{n \text{ times}} A$. +% % -\begin{definition}[Closure operations] - Let $R \subseteq A \times A$ denote a homogeneous relation. The - reflexive closure $R^{=}$ of $R$ is the smallest reflexive relation - over $A$ containing $R$ - % - \[ - R^{=} \defas \{ (a, a) \mid a \in A \} \cup R. - \] - % - The transitive closure $R^+$ of $R$ is the smallest transitive - relation over $A$ containing $R$ - % - \[ - R^+ \defas \displaystyle\bigcup_{n \in \N} R^n. - \] - % - The reflexive and transitive closure $R^\ast$ of $R$ is the smallest - reflexive and transitive relation over $A$ containing $R$ - % - \[ - R^\ast \defas (R^+)^{=}. - \] -\end{definition} -% -\begin{definition} - A relation $R \subseteq A \times B$ is functional and serial if it - satisfies the following criteria, respectively. - % - \begin{itemize} - \item Functional: $\forall a \in A, b,b' \in B$ if $a\,R\,b$ and $a\,R\,b'$ then $b = b'$. - \item Serial: $\forall a \in A,\exists b \in B$ such that - $a\,R\,b$. - \end{itemize} -\end{definition} -% -The functional property guarantees that every $a \in A$ is at most -related to one $b \in B$. Note this does not mean that every $a$ -\emph{is} related to some $b$. The serial property guarantees that -every $a \in A$ is related to one or more elements in $B$. -% +% \begin{definition} +% A relation $R$ is a subset of the Cartesian product of two sets $A$ +% and $B$, i.e. $R \subseteq A \times B$. +% % +% An element $a \in A$ is related to an element $b \in B$ if +% $(a, b) \in R$, sometimes written using infix notation $a\,R\,b$. +% % +% If $A = B$ then $R$ is said to be a \emph{homogeneous} relation. +% \end{definition} +% % -\section{Functions} -\label{sec:functions} -We define partial and total functions in terms of relations. -% -\begin{definition} - A partial function $f : A \pto B$ is a functional relation - $f \subseteq A \times B$. - % - A total function $f : A \to B$ is a functional and serial relation - $f \subseteq A \times B$. -\end{definition} -% -A total function is also simply called a `function'. Throughout this -dissertation the terms (partial) mapping and (partial) function are -synonymous. -% +% \begin{definition} +% For any two relations $R \subseteq A \times B$ and +% $S \subseteq B \times C$ their composition is defined as follows. +% % +% \[ +% S \circ R \defas \{ (a,c) \mid (a,b) \in R, (b, c) \in S \} +% \] +% \end{definition} -For a function $f : A \to B$ (or partial function $f : A \pto B$) we -write $f(a) = b$ to mean $(a, b) \in f$, and say that $f$ applied to -$a$ returns $b$. We write $f(P) \defas M$ for the definition of a -function with pattern $P$ and expression $M$, and sometimes we will -use the anonymous notation $P \mapsto M$ to mean $f(P) \defas M$ for -some fresh $f$. The notation $f(a)$ means the application of $f$ to -$a$, and we say that $f(a)$ is defined whenever $f(a) = b$ for some -$b$. -% -The domain of a function is a set, $\dom(-)$, consisting of all the -elements for which it is defined. Thus the domain of a total function -is its domain of definition, e.g. $\dom(f : A \to B) = A$. -% -For a partial function $f$ its domain is a proper subset of the domain -of definition. -% -\[ - \dom(f : A \pto B) \defas \{ a \mid a \in A,\, f(a) \text{ is defined} \} \subset A. -\] -% -The codomain of a total function $f : A \to B$ (or partial function -$f : A \pto B$) is $B$, written $\dec{cod}(f) = B$. A related notion -is that of \emph{image}. The image of a total or partial function $f$, -written $\dec{Im}(f)$, is the set of values that it can return, i.e. -% -\[ - \dec{Im}(f) \defas \{\, f(a) \mid a \in \dom(f) \}. -\] +% The composition operator ($\circ$) is associative, meaning +% $(T \circ S) \circ R = T \circ (S \circ R)$. +% % +% For $n \in \N$ the $n$th relational power of a relation $R$, written +% $R^n$, is defined inductively. +% \[ +% R^0 \defas \emptyset, \quad\qquad R^1 \defas R, \quad\qquad R^{1 + n} \defas R \circ R^n. +% \] +% % +% Homogeneous relations play a prominent role in the operational +% understanding of programming languages as they are used to give +% meaning to program reductions. There are two particular properties and +% associated closure operations of homogeneous relations that reoccur +% throughout this dissertation. +% % +% \begin{definition} +% A homogeneous relation $R \subseteq A \times A$ is said to be +% reflexive and transitive if its satisfies the following criteria, +% respectively. +% \begin{itemize} +% \item Reflexive: $\forall a \in A$ it holds that $a\,R\,a$. +% \item Transitive: $\forall a,b,c \in A$ if $a\,R\,b$ and $b\,R\,c$ +% then $a\,R\,c$. +% \end{itemize} +% \end{definition} -\begin{definition} - A function $f : A \to B$ is injective and surjective if it satisfies - the following criteria, respectively. - \begin{itemize} - \item Injective: $\forall a,a' \in A$ if $f(a) = f(a')$ then $a = a'$. - \item Surjective: $\forall b \in B,\exists a \in A$ such that $f(a) = b$. - \end{itemize} - If a function is both injective and surjective, then it is said to - be a bijective. -\end{definition} -% -An injective function guarantees that each element in its image is -uniquely determined by some element of its domain. -% -A surjective function guarantees that its domain covers the codomain, -meaning that the codomain and image coincide. -% -A partial function $f$ is injective, surjective, and bijective -whenever the function $f' : \dom(f) \to \dec{cod}(f)$, obtained by -restricting $f$ to its domain, is injective, surjective, and bijective -respectively. +% \begin{definition}[Closure operations] +% Let $R \subseteq A \times A$ denote a homogeneous relation. The +% reflexive closure $R^{=}$ of $R$ is the smallest reflexive relation +% over $A$ containing $R$ +% % +% \[ +% R^{=} \defas \{ (a, a) \mid a \in A \} \cup R. +% \] +% % +% The transitive closure $R^+$ of $R$ is the smallest transitive +% relation over $A$ containing $R$ +% % +% \[ +% R^+ \defas \displaystyle\bigcup_{n \in \N} R^n. +% \] +% % +% The reflexive and transitive closure $R^\ast$ of $R$ is the smallest +% reflexive and transitive relation over $A$ containing $R$ +% % +% \[ +% R^\ast \defas (R^+)^{=}. +% \] +% \end{definition} +% % -\section{Asymptotic notation} -\label{sec:asymp-not} +% \begin{definition} +% A relation $R \subseteq A \times B$ is functional and serial if it +% satisfies the following criteria, respectively. +% % +% \begin{itemize} +% \item Functional: $\forall a \in A, b,b' \in B$ if $a\,R\,b$ and $a\,R\,b'$ then $b = b'$. +% \item Serial: $\forall a \in A,\exists b \in B$ such that +% $a\,R\,b$. +% \end{itemize} +% \end{definition} +% % +% The functional property guarantees that every $a \in A$ is at most +% related to one $b \in B$. Note this does not mean that every $a$ +% \emph{is} related to some $b$. The serial property guarantees that +% every $a \in A$ is related to one or more elements in $B$. +% % -Asymptotic notation is a compact notational framework for comparing -the order of growth of functions, which abstracts away any constants -involved~\cite{Bachmann94}. We will use the asymptotic notation for -both runtime and space analyses of functions. +% \section{Functions} +% \label{sec:functions} +% We define partial and total functions in terms of relations. +% % +% \begin{definition} +% A partial function $f : A \pto B$ is a functional relation +% $f \subseteq A \times B$. +% % +% A total function $f : A \to B$ is a functional and serial relation +% $f \subseteq A \times B$. +% \end{definition} +% % +% A total function is also simply called a `function'. Throughout this +% dissertation the terms (partial) mapping and (partial) function are +% synonymous. +% % -\begin{definition}[Upper bound] - We say that a function $f : \N \to \R$ is of order \emph{at most} - $g : \N \to \R$, and write $f = \BigO(g)$, if there is a positive - constant $c \in \R$ and a positive natural number $n_0 \in \N$ such that - % - \[ - f(n) \leq c \cdot g(n),\quad \text{for all}~ n \geq n_0. - \] - % -\end{definition} -% -We will extend the notation to permit $f(n) = \BigO(g(n))$. -% -We will often abuse notation and use the body of an anonymous function -in place of $g$, e.g. $\BigO(\log~n)$ means $\BigO(n \mapsto \log~n)$ -and stands for a function whose values are bounded above by a -logarithmic factor (in this dissertation logarithms are always in base -$2$). If $f = \BigO(\log~n)$ then we say that the order of growth of -$f$ is logarithmic; if $f = \BigO(n)$ we say that its order of growth -is linear; if $f = \BigO(n^k)$ for some $k \in \N$ we say that the -order of growth is polynomial; and if $f = \BigO(2^n)$ then the order -of growth of $f$ is exponential. -% -It is important to note, though, that we write $f = \BigO(1)$ to mean -that the values of $f$ are bounded above by some constant, meaning -$1 \not\in \N$, but rather $1$ denotes a family of constant functions -of type $\N \to \R$. So if $f = \BigO(1)$ then we say that the order -of growth of $f$ is constant. - -\begin{definition}[Lower bound] - We say that a function $f : \N \to \R$ is of order \emph{at least} - $g : \N \to \R$, and write $f = \Omega(g)$, if there is a positive - constant $c \in \R$ and a positive natural number $n_0 \in \N$ such - that - % - \[ - f(n) \geq c \cdot g(n),\quad \text{for all}~n \geq n_0. - \] -\end{definition} +% For a function $f : A \to B$ (or partial function $f : A \pto B$) we +% write $f(a) = b$ to mean $(a, b) \in f$, and say that $f$ applied to +% $a$ returns $b$. We write $f(P) \defas M$ for the definition of a +% function with pattern $P$ and expression $M$, and sometimes we will +% use the anonymous notation $P \mapsto M$ to mean $f(P) \defas M$ for +% some fresh $f$. The notation $f(a)$ means the application of $f$ to +% $a$, and we say that $f(a)$ is defined whenever $f(a) = b$ for some +% $b$. +% % +% The domain of a function is a set, $\dom(-)$, consisting of all the +% elements for which it is defined. Thus the domain of a total function +% is its domain of definition, e.g. $\dom(f : A \to B) = A$. +% % +% For a partial function $f$ its domain is a proper subset of the domain +% of definition. +% % +% \[ +% \dom(f : A \pto B) \defas \{ a \mid a \in A,\, f(a) \text{ is defined} \} \subset A. +% \] +% % +% The codomain of a total function $f : A \to B$ (or partial function +% $f : A \pto B$) is $B$, written $\dec{cod}(f) = B$. A related notion +% is that of \emph{image}. The image of a total or partial function $f$, +% written $\dec{Im}(f)$, is the set of values that it can return, i.e. +% % +% \[ +% \dec{Im}(f) \defas \{\, f(a) \mid a \in \dom(f) \}. +% \] + +% \begin{definition} +% A function $f : A \to B$ is injective and surjective if it satisfies +% the following criteria, respectively. +% \begin{itemize} +% \item Injective: $\forall a,a' \in A$ if $f(a) = f(a')$ then $a = a'$. +% \item Surjective: $\forall b \in B,\exists a \in A$ such that $f(a) = b$. +% \end{itemize} +% If a function is both injective and surjective, then it is said to +% be a bijective. +% \end{definition} +% % +% An injective function guarantees that each element in its image is +% uniquely determined by some element of its domain. +% % +% A surjective function guarantees that its domain covers the codomain, +% meaning that the codomain and image coincide. +% % +% A partial function $f$ is injective, surjective, and bijective +% whenever the function $f' : \dom(f) \to \dec{cod}(f)$, obtained by +% restricting $f$ to its domain, is injective, surjective, and bijective +% respectively. + +% \section{Asymptotic notation} +% \label{sec:asymp-not} + +% Asymptotic notation is a compact notational framework for comparing +% the order of growth of functions, which abstracts away any constants +% involved~\cite{Bachmann94}. We will use the asymptotic notation for +% both runtime and space analyses of functions. + +% \begin{definition}[Upper bound] +% We say that a function $f : \N \to \R$ is of order \emph{at most} +% $g : \N \to \R$, and write $f = \BigO(g)$, if there is a positive +% constant $c \in \R$ and a positive natural number $n_0 \in \N$ such that +% % +% \[ +% f(n) \leq c \cdot g(n),\quad \text{for all}~ n \geq n_0. +% \] +% % +% \end{definition} +% % +% We will extend the notation to permit $f(n) = \BigO(g(n))$. +% % +% We will often abuse notation and use the body of an anonymous function +% in place of $g$, e.g. $\BigO(\log~n)$ means $\BigO(n \mapsto \log~n)$ +% and stands for a function whose values are bounded above by a +% logarithmic factor (in this dissertation logarithms are always in base +% $2$). If $f = \BigO(\log~n)$ then we say that the order of growth of +% $f$ is logarithmic; if $f = \BigO(n)$ we say that its order of growth +% is linear; if $f = \BigO(n^k)$ for some $k \in \N$ we say that the +% order of growth is polynomial; and if $f = \BigO(2^n)$ then the order +% of growth of $f$ is exponential. +% % +% It is important to note, though, that we write $f = \BigO(1)$ to mean +% that the values of $f$ are bounded above by some constant, meaning +% $1 \not\in \N$, but rather $1$ denotes a family of constant functions +% of type $\N \to \R$. So if $f = \BigO(1)$ then we say that the order +% of growth of $f$ is constant. + +% \begin{definition}[Lower bound] +% We say that a function $f : \N \to \R$ is of order \emph{at least} +% $g : \N \to \R$, and write $f = \Omega(g)$, if there is a positive +% constant $c \in \R$ and a positive natural number $n_0 \in \N$ such +% that +% % +% \[ +% f(n) \geq c \cdot g(n),\quad \text{for all}~n \geq n_0. +% \] +% \end{definition} % \section{Typed programming languages} % \label{sec:pls} @@ -2647,2826 +2659,2964 @@ of growth of $f$ is constant. % % \end{enumerate} % % \end{definition} +\part{Programming} +\label{p:design} -% % \chapter{State of effectful programming} -% % \label{ch:related-work} - -% % % \section{Type and effect systems} -% % % \section{Monadic programming} -% % \section{Golden age of impurity} -% % \section{Monadic enlightenment} -% % \dhil{Moggi's seminal work applies the notion of monads to effectful -% % programming by modelling effects as monads. More importantly, -% % Moggi's work gives a precise characterisation of what's \emph{not} -% % an effect} -% % \section{Direct-style revolution} +\chapter{Composing \UNIX{} with effect handlers} +\label{ch:ehop} -% % \subsection{Monadic reflection: best of both worlds} +\dhil{TODO rewrite this introduction. Streamline the subsequent sections to not assume prior experience with effect handlers} -\chapter{Continuations} -\label{ch:continuations} +There are several analogies for effect handlers, e.g. as interpreters +for effects, folds over computation trees, etc. A particularly +compelling programmatic analogy for effect handlers is \emph{effect + handlers as composable operating systems}. Effect handlers and +operating systems share operational characteristics: an operating +system interprets a set of system commands performed via system calls, +which is similar to how an effect handler interprets a set of abstract +operations performed via operation invocations. This analogy was +suggested to me by James McKinna (personal communication, 2017). +% +The compelling aspect of this analogy is that we can understand a +monolithic and complex operating system like \UNIX{}~\cite{RitchieT74} +as a collection of effect handlers, or alternatively, a collection of +tiny operating systems, that when composed yield a semantics for +\UNIX{}. -A continuation represents the control state of computation at a given -point during evaluation. The control state contains the necessary -operational information for evaluation to continue. As such, -continuations drive computation. % Continuations are a ubiquitous -% phenomenon as they exist both semantically and programmatically. +In this section we will take this reading of effect handlers +literally, and demonstrate how we can harness the power of (deep) +effect handlers to implement a \UNIX{}-style operating system with +multiple user sessions, time-sharing, and file i/o. We dub the system +\OSname{}. % -Continuations are one of those canonical ideas, that have been -discovered multiple times and whose definition predates their -use~\cite{Reynolds93}. The term `continuation' first appeared in the -literature in 1974, when \citet{StracheyW74} used continuations to -give a denotational semantics to programming languages with -unrestricted jumps~\cite{StracheyW00}. +It is a case study that demonstrates the versatility of effect +handlers, and shows how standard computational effects such as +\emph{exceptions}, \emph{dynamic binding}, \emph{nondeterminism}, and +\emph{state} make up the essence of an operating system. -The inaugural use of continuations came well before -\citeauthor{StracheyW00}'s definition. About a decade earlier -continuation passing style had already been conceived, if not in name -then in spirit, as a compiler transformation for eliminating labels -and goto statements~\cite{Reynolds93}. In the mid 1960s -\citet{Landin98} introduced the J operator as a programmatic mechanism -for manipulating continuations. +For the sake of clarity, we will occasionally make some blatant +simplifications, nevertheless the resulting implementation will +capture the essence of a \UNIX{}-like operating system. +% +The implementation will be composed of several small modular effect +handlers, that each handles a particular set of system commands. In +this respect, we will truly realise \OSname{} in the spirit of the +\UNIX{} philosophy~\cite[Section~1.6]{Raymond03}. -\citeauthor{Landin98}'s J operator is an instance of a first-class -control operator, which is a mechanism that lets programmers reify -continuations as first-class objects, that can be invoked, discarded, -or stored for later use. There exists a wide variety of control -operators, which expose continuations of varying extent and behaviour. +% This case study demonstrates that we can decompose a monolithic +% operating system like \UNIX{} into a collection of specialised effect +% handlers. Each handler interprets a particular system command. -The purpose of this chapter is to examine control operators and their -continuations in -programming. Section~\ref{sec:classifying-continuations} examines -different notions of continuations by characterising their extent and -behaviour operationally. Section~\ref{sec:controlling-continuations} -contains a detailed overview of various control operators that appear -in programming languages and in the -literature. Section~\ref{sec:programming-continuations} summarises -some applications of continuations, whilst -Section~\ref{sec:constraining-continuations} contains a brief summary -of ideas for constraining the power of continuations. Lastly, -Section~\ref{sec:implementing-continuations} outlines some -implementation strategies for continuations. - -% A lot of literature has been devoted to study continuations. Whilst -% the literature recognises the importance and significance of -% continuations - -% continuations are widely recognised as important in the programming -% language literature, - -% The concrete structure and behaviour of continuations differs - -% Continuations, when exposed programmatically, imbue programmers with -% the power to take control of programs. This power enables programmers -% to implement their own control idioms as user-definable libraries. - -% The significance of continuations in the programming languages -% literature is inescapable as continuations have found widespread use . -% - -% A continuation is an abstract data structure that captures the -% remainder of the computation from some given point in the computation. -% % -% The exact nature of the data structure and the precise point at which -% the remainder of the computation is captured depends largely on the -% exact notion of continuation under consideration. -% % -% It can be difficult to navigate the existing literature on -% continuations as sometimes the terminologies for different notions of -% continuations are overloaded or even conflated. -% % -% As there exist several notions of continuations, there exist several -% mechanisms for programmatic manipulation of continuations. These -% mechanisms are known as control operators. +% A systems software engineering reading of effect handlers may be to +% understand them as modular ``tiny operating systems''. Operationally, +% how an \emph{operating system} interprets a set of \emph{system +% commands} performed via \emph{system calls} is similar to how an +% effect handler interprets a set of abstract operations performed via +% operation invocations. % % -% A substantial amount of existing literature has been devoted to -% understand how to program with individual control operators, and to a -% lesser extent how the various operators compare. - -% The purpose of this chapter is to provide a contemporary and -% unambiguous characterisation of the notions of continuations in -% literature. This characterisation is used to classify and discuss a -% wide range of control operators from the literature. - -% % Undelimited control: Landin's J~\cite{Landin98}, Reynolds' -% % escape~\cite{Reynolds98a}, Scheme75's catch~\cite{SussmanS75} --- -% % which was based the less expressive MacLisp catch~\cite{Moon74}, -% % callcc is a procedural variation of catch. It was invented in -% % 1982~\cite{AbelsonHAKBOBPCRFRHSHW85}. - -% A full formal comparison of the control operators is out of scope of -% this chapter. The literature contains comparisons of various control -% operators along various dimensions, e.g. +% This reading was suggested to me by James McKinna (personal +% communication). In this section I will take this reading literally, +% and demonstrate how we can use the power of (deep) effect handlers to +% implement a tiny operating system that supports multiple users, +% time-sharing, and file i/o. % % -% \citet{Thielecke02} studies a handful of operators via double -% barrelled continuation passing style. \citet{ForsterKLP19} compare the -% relative expressiveness of untyped and simply-typed variations of -% effect handlers, shift/reset, and monadic reflection by means of -% whether they are macro-expressible. Their work demonstrates that in an -% untyped setting each operator is macro-expressible, but in most cases -% the macro-translations do not preserve typeability, for instance the -% simple type structure is insufficient to type the image of -% macro-translation between effect handlers and shift/reset. +% The operating system will be a variation of \UNIX{}~\cite{RitchieT74}, +% which we will call \OSname{}. % % -% However, \citet{PirogPS19} show that with a polymorphic type system -% the translation preserve typeability. +% To make the task tractable we will occasionally jump some hops and +% make some simplifying assumptions, nevertheless the resulting +% implementation will capture the essence of a \UNIX{}-like operating +% system. % % -% \citet{Shan04} shows that dynamic delimited control and static -% delimited control is macro-expressible in an untyped setting. - -\section{Classifying continuations} -\label{sec:classifying-continuations} - -The term `continuation' is really an umbrella term that covers several -distinct notions of continuations. It is common in the literature to -find the word `continuation' accompanied by a qualifier such as full, -partial, abortive, escape, undelimited, delimited, composable, or -functional (in Chapter~\ref{ch:cps} I will extend this list by three -new ones). Some of these notions of continuations are synonymous, -whereas others have distinct meanings. Common to all notions of -continuations is that they represent the control state. However, the -extent and behaviour of continuations differ widely from notion to -notion. The essential notions of continuations are -undelimited/delimited and abortive/composable. To tell them apart, we -will classify them according to their operational behaviour. +% The implementation will be composed of several small modular effect +% handlers, that each handles a particular set of system commands. In +% this respect, we will truly realise \OSname{} in the spirit of the +% \UNIX{} philosophy~\cite[Section~1.6]{Raymond03}. The implementation of +% the operating system will showcase several computational effects in +% action including \emph{dynamic binding}, \emph{nondeterminism}, and +% \emph{state}. -The extent and behaviour of a continuation in programming are -determined by its introduction and elimination forms, -respectively. Programmatically, a continuation is introduced via a -control operator, which reifies the control state as a first-class -object, e.g. a function, that can be eliminated via some form of -application. +\section{Basic i/o} +\label{sec:tiny-unix-bio} -\subsection{Introduction of continuations} -% -The extent of a continuation determines how much of the control state -is contained with the continuation. +The file system is a cornerstone of \UNIX{} as the notion of \emph{file} +in \UNIX{} provides a unified abstraction for storing text, interprocess +communication, and access to devices such as terminals, printers, +network, etc. % -The extent can be either undelimited or delimited, and it is -determined at the point of capture by the control operator. +Initially, we shall take a rather basic view of the file system. In +fact, our initial system will only contain a single file, and +moreover, the system will only support writing operations. This system +hardly qualifies as a \UNIX{} file system. Nevertheless, it serves a +crucial role for development of \OSname{}, because it provides the +only means for us to be able to observe the effects of processes. % +We defer development of a more advanced file system to +Section~\ref{sec:tiny-unix-io}. -We need some notation for control operators in order to examine the -introduction of continuations operationally. We will use the syntax -$\CC~k.M$ to denote a control operator, or control reifier, which that -reifies the control state and binds it to $k$ in the computation -$M$. Here the control state will simply be an evaluation context. We -will denote continuations by a special value $\cont_{\EC}$, which is -indexed by the reified evaluation context $\EC$ to make it -notationally convenient to reflect the context again. To characterise -delimited continuations we also need a control delimiter. We will -write $\Delim{M}$ to denote a syntactic marker that delimits some -computation $M$. - -\paragraph{Undelimited continuation} The extent of an undelimited -continuation is indefinite as it ranges over the entire remainder of -computation. +Much like \UNIX{} we shall model a file as a list of characters, that is +$\UFile \defas \List~\Char$. For convenience we will use the same +model for strings, $\String \defas \List~\Char$, such that we can use +string literal notation to denote the $\strlit{contents of a file}$. % -In functional programming languages undelimted control operators most -commonly expose the \emph{current} continuation, which is the -precisely continuation following the control operator. The following -is the characteristic reduction for the introduction of the current -continuation. -% The indefinite extents means that undelimited continuation capture can -% only be understood in context. The characteristic reduction is as -% follows. +The signature of the basic file system will consist of a single +operation $\Write$ for writing a list of characters to the file. % \[ - \EC[\CC~k.M] \reducesto \EC[M[\cont_{\EC}/k]] + \BIO \defas \{\Write : \Record{\UFD;\String} \opto \UnitType\} \] % -The evaluation context $\EC$ is the continuation of $\CC$. The -evaluation context on the left hand side gets reified as a -continuation object, which is accessible inside of $M$ via $k$. On the -right hand side the entire context remains in place after -reification. Thus, the current continuation is evaluated regardless of -whether the continuation object is invoked. This is an instance of -non-abortive undelimited control. Alternatively, the control operator -can abort the current continuation before proceeding as $M$, i.e. +The operation is parameterised by a $\UFD$ and a character +sequence. We will leave the $\UFD$ type abstract until +Section~\ref{sec:tiny-unix-io}, however, we shall assume the existence +of a term $\stdout : \UFD$ such that we can perform invocations of +$\Write$. +% +Let us define a suitable handler for this operation. % \[ - \EC[\CC~k.M] \reducesto M[\cont_{\EC}/k] + \bl + \basicIO : (\UnitType \to \alpha \eff \BIO) \to \Record{\alpha; \UFile}\\ + \basicIO~m \defas + \ba[t]{@{~}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& \Record{res;\nil}\\ + \OpCase{\Write}{\Record{\_;cs}}{resume} &\mapsto& + \ba[t]{@{}l} + \Let\; \Record{res;file} = resume\,\Unit\;\In\\ + \Record{res; cs \concat file} + \ea + \ea + \ea + \el \] % -This is the characteristic reduction rule for abortive undelimited -control. The rule is nearly the same as the previous, except that on -the right hand side the evaluation context $\EC$ is discarded after -reification. Now, the programmer has control over the whole -continuation, since it is entirely up to the programmer whether $\EC$ -gets evaluated. - -Imperative statement-oriented programming languages commonly expose -the \emph{caller} continuation, typically via a return statement. The -caller continuation is the continuation of the invocation context of -the control operator. Characterising undelimited caller continuations -is slightly more involved as we have to remember the continuation of -the invocation context. We will use a bold lambda $\llambda$ as a -syntactic runtime marker to remember the continuation of an -application. In addition we need three reduction rules, where the -first is purely administrative, the second is an extension of regular -application, and the third is the characteristic reduction rule for -undelimited control with caller continuations. +The handler takes as input a computation that produces some value +$\alpha$, and in doing so may perform the $\BIO$ effect. % -\begin{reductions} - & \llambda.V &\reducesto& V\\ - & (\lambda x.N)\,V &\reducesto& \llambda.N[V/x]\\ - & \EC[\llambda.\EC'[\CC~k.M]] &\reducesto& \EC[\llambda.\EC'[M[\cont_{\EC}/k]]], \quad \text{where $\EC'$ contains no $\llambda$} -\end{reductions} +The handler ultimately returns a pair consisting of the return value +$\alpha$ and the final state of the file. % -The first rule accounts for the case where $\llambda$ marks a value, -in which case the marker is eliminated. The second rule marks inserts -a marker after an application such that this position can be recalled -later. The third rule is the interesting rule. Here an occurrence of -$\CC$ reifies $\EC$, the continuation of some application, rather than -its current continuation $\EC'$. The side condition ensures that $\CC$ -reifies the continuation of the inner most application. This rule -characterises a non-abortive control operator as both contexts, $\EC$ -and $\EC'$, are left in place after reification. It is straightforward -to adapt this rule to an abortive operator. Although, there is no -abortive undelimited control operator that captures the caller -continuation in the literature. +The $\Return$-case pairs the result $res$ with the empty file $\nil$ +which models the scenario where the computation $m$ performed no +$\Write$-operations, e.g. +$\basicIO\,(\lambda\Unit.\Unit) \reducesto^+ +\Record{\Unit;\strlit{}}$. +% +The $\Write$-case extends the file by first invoking the resumption, +whose return type is the same as the handler's return type, thus it +returns a pair containing the result of $m$ and the file state. The +file gets extended with the character sequence $cs$ before it is +returned along with the original result of $m$. +% +Intuitively, we may think of this implementation of $\Write$ as a +peculiar instance of buffered writing, where the contents of the +operation are committed to the file when the computation $m$ finishes. -It is worth noting that the two first rules can be understood locally, -that is without mentioning the enclosing context, whereas the third -rule must be understood globally. - -In the literature an undelimited continuation is also known as a -`full' continuation. - -\paragraph{Delimited continuation} A delimited continuation is in some -sense a refinement of a undelimited continuation as its extent is -definite. A delimited continuation ranges over some designated part of -computation. A delimited continuation is introduced by a pair -operators: a control delimiter and a control reifier. The control -delimiter acts as a barrier, which prevents the reifier from reaching -beyond it, e.g. -% -\begin{reductions} - & \Delim{V} &\reducesto& V\\ - & \Delim{\EC[\CC~k.M]} &\reducesto& \Delim{\EC[M[\cont_{\EC}/k]]} -\end{reductions} -% -The first rule applies whenever the control delimiter delimits a -value, in which case the delimiter is eliminated. The second rule is -the characteristic reduction rule for a non-abortive delimited control -reifier. It reifies the context $\EC$ up to the control delimiter, and -then continues as $M$ under the control delimiter. Note that the -continuation of $\keyw{del}$ is invisible to $\CC$, and thus, the -behaviour of $\CC$ can be understood locally. +Let us define an auxiliary function that writes a string to the +$\stdout$ file. % -Most commonly, the control reifier is abortive, i.e. -\begin{reductions} - & \Delim{\EC[\CC~k.M]} &\reducesto& \Delim{M[\cont_{\EC}/k]}. -\end{reductions} +\[ + \bl + \echo : \String \to \UnitType \eff\, \BIO\\%\{\Write : \Record{\UFD;\String} \opto \UnitType\}\\ + \echo~cs \defas \Do\;\Write\,\Record{\stdout;cs} + \el +\] % -The design space of delimited control is somewhat richer than that of -undelimited control, as the control delimiter may remain in place -after reification, as above, be discarded, be included in the -continuation, or a combination. Similarly, the control reifier may -reify the continuation up to and including the delimiter or, as above, -without the delimiter. +The function $\echo$ is a simple wrapper around an invocation of +$\Write$. % -\citet{DybvigJS07} use a taxonomy for delimited abortive control -reifiers, which classifies them according to how they interact with -their respective control delimiter. -% the introduction of delimited -% continuations, which classifies according to how their control reifiers interact their -% respective control delimiter. -They identify four variations. +We can now write some contents to the file and observe the effects. % -\begin{description} -\item[\CCpp] The control reifier includes a copy of the control delimiter in the - reified context, and leaves the original in place, i.e. \[ - \Delim{\EC[\CC~k.M]} \reducesto \Delim{M[\cont_{\Delim{\EC}}/k]} + \ba{@{~}l@{~}l} + &\basicIO\,(\lambda\Unit. \echo~\strlit{Hello}; \echo~\strlit{World})\\ + \reducesto^+& \Record{\Unit;\strlit{HelloWorld}} : \Record{\UnitType;\UFile} + \ea \] -\item[\CCpm] The control delimiter remains in place after - reification as the control reifier reifies the context up to, but - not including, the delimiter, i.e. - \[ - \Delim{\EC[\CC~k.M]} \reducesto \Delim{M[\cont_{\EC}/k]} - \] -\item[\CCmp] The control reifier includes a copy of the control - delimiter in the reified context, but discards the original - instance, i.e. - \[ - \Delim{\EC[\CC~k.M]} \reducesto M[\cont_{\EC}/k] - \] -\item[\CCmm] The control reifier reifies the context up to, but not - including, the delimiter and subsequently discards the delimiter, i.e. - \[ - \Delim{\EC[\CC~k.M]} \reducesto M[\cont_{\EC}/k] - \] -\end{description} - -% % - +\section{Exceptions: non-local exits} +\label{sec:tiny-unix-exit} +A process may terminate successfully by running to completion, or it +may terminate with success or failure in the middle of some +computation by performing an \emph{exit} system call. The exit system +call is typically parameterised by an integer value intended to +indicate whether the exit was due to success or failure. By +convention, \UNIX{} interprets the integer zero as success and any +nonzero integer as failure, where the specific value is supposed to +correspond to some known error code. % -In the literature a delimited continuation is also known as a -`partial' continuation. - -\subsection{Elimination of continuations} -The purpose of continuation application is to reinstall the captured -context. +We can model the exit system call by way of a single operation +$\Exit$. % -However, a continuation application may affect the control state in -various ways. The literature features two distinct behaviours of -continuation application: abortive and composable. We need some -notation for application of continuations in order to characterise -abortive and composable behaviours. We will write -$\Continue~\cont_{\EC}~V$ to denote the application of some -continuation object $\cont$ to some value $V$. - -\paragraph{Abortive continuation} Upon invocation an abortive -continuation discards the entire evaluation context before -reinstalling the captured context. In other words, an abortive -continuation replaces the current context with its captured context, -i.e. +\[ + \Status \defas \{\Exit : \Int \opto \ZeroType\} +\] +% +The operation is parameterised by an integer value, however, an +invocation of $\Exit$ can never return, because the type $\ZeroType$ is +uninhabited. Thus $\Exit$ acts like an exception. +% +It is convenient to abstract invocations of $\Exit$ to make it +possible to invoke the operation in any context. % \[ - \EC[\Continue~\cont_{\EC'}~V] \reducesto \EC'[V] + \bl + \exit : \Int \to \alpha \eff \Status\\ + \exit~n \defas \Absurd\;(\Do\;\Exit~n) + \el \] % -The current context $\EC$ is discarded in favour of the captured -context $\EC'$ (whether the two contexts coincide depends on the -control operator). Abortive continuations are a global phenomenon due -to their effect on the current context. However, in conjunction with a -control delimiter the behaviour of an abortive continuation can be -localised, i.e. +The $\Absurd$ computation term is used to coerce the return type +$\ZeroType$ of $\Fail$ into $\alpha$. This coercion is safe, because +$\ZeroType$ is an uninhabited type. +% +An interpretation of $\Exit$ amounts to implementing an exception +handler. % \[ - \Delim{\EC[\Continue~\cont_{\EC'}~V]} \reducesto \EC'[V] + \bl + \status : (\UnitType \to \alpha \eff \Status) \to \Int\\ + \status~m \defas + \ba[t]{@{~}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;\_ &\mapsto& 0\\ + \ExnCase{\Exit}{n} &\mapsto& n + \ea + \ea + \el \] % -Here, the behaviour of continuation does not interfere with the -context of $\keyw{del}$, and thus, the behaviour can be understood and -reasoned about locally with respect to $\keyw{del}$. +Following the \UNIX{} convention, the $\Return$-case interprets a +successful completion of $m$ as the integer $0$. The operation case +returns whatever payload the $\Exit$ operation was carrying. As a +consequence, outside of $\status$, an invocation of $\Exit~0$ in $m$ +is indistinguishable from $m$ returning normally, e.g. +$\status\,(\lambda\Unit.\exit~0) = \status\,(\lambda\Unit.\Unit)$. -A key characteristic of an abortive continuation is that composition -is meaningless. For example, composing an abortive continuation with -itself have no effect. +To illustrate $\status$ and $\exit$ in action consider the following +example, where the computation gets terminated mid-way. % \[ - \EC[\Continue~\cont_{\EC'}~(\Continue~\cont_{\EC'}~V)] \reducesto \EC'[V] + \ba{@{~}l@{~}l} + &\bl + \basicIO\,(\lambda\Unit. + \status\,(\lambda\Unit. + \echo~\strlit{dead};\exit~1;\echo~\strlit{code})) + \el\\ + \reducesto^+& \Record{1;\strlit{dead}} : \Record{\Int;\UFile} + \ea \] % -The innermost application erases the outermost application term, -consequently only the first application of $\cont$ occurs during -runtime. It is as if the first application occurred in tail position. +The (delimited) continuation of $\exit~1$ is effectively dead code. +% +Here, we have a choice as to how we compose the handlers. Swapping the +order of handlers would cause the whole computation to return just +$1 : \Int$, because the $\status$ handler discards the return value of +its computation. Thus with the alternative layering of handlers the +system would throw away the file state after the computation +finishes. However, in this particular instance the semantics the +(local) behaviour of the operations $\Write$ and $\Exit$ would be +unaffected if the handlers were swapped. In general the behaviour of +operations may be affected by the order of handlers. The canonical +example of this phenomenon is the composition of nondeterminism and +state, which we will discuss in Section~\ref{sec:tiny-unix-io}. -The continuations introduced by the early control operators were all -abortive, since they were motivated by modelling unrestricted jumps -akin to $\keyw{goto}$ in statement-oriented programming languages. +\section{Dynamic binding: user-specific environments} +\label{sec:tiny-unix-env} -An abortive continuation is also known as an `escape' continuation in -the literature. +When a process is run in \UNIX{}, the operating system makes available +to the process a collection of name-value pairs called the +\emph{environment}. +% +The name of a name-value pair is known as an \emph{environment + variable}. +% +During execution the process may perform a system call to ask the +operating system for the value of some environment variable. +% +The value of environment variables may change throughout process +execution, moreover, the value of some environment variables may vary +according to which user asks the environment. +% +For example, an environment may contain the environment variable +\texttt{USER} that is bound to the name of the enquiring user. -\paragraph{Composable continuation} A composable continuation splices -its captured context with the its invocation context, i.e. +An environment variable can be viewed as an instance of dynamic +binding. The idea of dynamic binding as a binding form in programming +dates back as far as the original implementation of +Lisp~\cite{McCarthy60}, and still remains an integral feature in +successors such as Emacs Lisp~\cite{LewisLSG20}. It is well-known that +dynamic binding can be encoded as a computational effect by using +delimited control~\cite{KiselyovSS06}. +% +Unsurprisingly, we will use this insight to simulate user-specific +environments using effect handlers. + +For simplicity we fix the users of the operating system to be root, +Alice, and Bob. % \[ - \Continue~\cont_{\EC}~V \reducesto \EC[V] + \User \defas [\Alice;\Bob;\Root] \] + +Our environment will only support a single environment variable +intended to store the name of the current user. The value of this +variable can be accessed via an operation $\Ask : \UnitType \opto \String$. % -The application of a composable continuation can be understood -locally, because it has no effect on its invocation context. A -composable continuation behaves like a function in the sense that it -returns to its caller, and thus composition is well-defined, e.g. +% \[ +% \EnvE \defas \{\Ask : \UnitType \opto \String\} +% \] +% +Using this operation we can readily implement the \emph{whoami} +utility from the GNU coreutils~\cite[Section~20.3]{MacKenzieMPPBYS20}, +which returns the name of the current user. % \[ - \Continue~\cont_{\EC}~(\Continue~\cont_{\EC}~V) \reducesto \Continue~\cont_{\EC}~\EC[V] + \bl + \whoami : \UnitType \to \String \eff \{\Ask : \UnitType \opto \String\}\\ + \whoami~\Unit \defas \Do\;\Ask~\Unit + \el \] % -The innermost application composes the captured context with the -outermost application. Thus, the outermost application occurs when -$\EC[V]$ has been reduced to a value. - -In the literature, virtually every delimited control operator provides -composable continuations. However, the notion of composable -continuation is not intimately connected to delimited control. It is -perfect possible to conceive of a undelimited composable continuation, -just as a delimited abortive continuation is conceivable. - -A composable continuation is also known as a `functional' continuation -in the literature. - -\section{Controlling continuations} -\label{sec:controlling-continuations} -As suggested in the previous section, the design space for -continuation is rich. This richness is to an extent reflected by the -large amount of control operators that appear in the literature -and in practice. -% -The purpose of this section is to survey a considerable subset of the -first-class \emph{sequential} control operators that occur in the -literature and in practice. Control operators for parallel programming -will not be considered here. +The following handler implements the environment. % -Tables~\ref{tbl:classify-ctrl-undelimited} and -\ref{tbl:classify-ctrl-delimited} provide classifications of some of -the undelimited control operators and delimited control operators, -respectively, that appear in the literature. - -Note that a \emph{first-class} control operator is typically not -itself a first-class citizen, rather, the label `first-class' means -that the reified continuation is a first-class object. Control -operators that reify the current continuation can be made first-class -by enclosing them in a $\lambda$-abstraction. Obviously, this trick -does not work for operators that reify the caller continuation. - -To study the control operators we will make use of a small base -language. +\[ + \bl + \environment : \Record{\User;\UnitType \to \alpha \eff \{\Ask : \UnitType \opto \String\}} \to \alpha\\ + \environment~\Record{user;m} \defas + \ba[t]{@{~}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& res\\ + \OpCase{\Ask}{\Unit}{resume} &\mapsto& + \bl + \Case\;user\,\{ + \ba[t]{@{}l@{~}c@{~}l} + \Alice &\mapsto& resume~\strlit{alice}\\ + \Bob &\mapsto& resume~\strlit{bob}\\ + \Root &\mapsto& resume~\strlit{root}\} + \ea + \el + \ea + \ea + \el +\] % -\begin{table} - \centering - \begin{tabular}{| l | l | l |} - \hline - \multicolumn{1}{| l |}{\textbf{Name}} & \multicolumn{1}{l |}{\textbf{Continuation behaviour}} & \multicolumn{1}{l |}{\textbf{Canonical reference}}\\ - \hline - J & Abortive & \citet{Landin98}\\ - \hline - escape & Abortive & \citet{Reynolds98a}\\ - \hline - catch & Abortive & \citet{SussmanS75} \\ - \hline - callcc & Abortive & \citet{AbelsonHAKBOBPCRFRHSHW85} \\ - \hline - F & Composable & \citet{FelleisenFDM87}\\ - \hline - C & Abortive & \citet{FelleisenF86} \\ - \hline - \textCallcomc{} & Composable & \citet{Flatt20}\\ - \hline - \end{tabular} - \caption{Classification of first-class undelimited control operators - (listed in chronological - order).}\label{tbl:classify-ctrl-undelimited} -\end{table} +The handler takes as input the current $user$ and a computation that +may perform the $\Ask$ operation. When an invocation of $\Ask$ occurs +the handler pattern matches on the $user$ parameter and resumes with a +string representation of the user. With this implementation we can +interpret an application of $\whoami$. % -\begin{table} - \centering - \begin{tabular}{| l | l | l | l |} - \hline - \multicolumn{1}{| l |}{\textbf{Name}} & \multicolumn{1}{l |}{\textbf{Taxonomy}} & \multicolumn{1}{l |}{\textbf{Continuation behaviour}} & \multicolumn{1}{l |}{\textbf{Canonical reference}}\\ - \hline - control/prompt & \CCpm & Composable & \citet{Felleisen88}\\ - \hline - shift/reset & \CCpp & Composable & \citet{DanvyF90}\\ - \hline - spawn & \CCmp & Composable & \citet{HiebD90}\\ - \hline - splitter & \CCmm & Abortive, composable & \citet{QueinnecS91}\\ - \hline - fcontrol & \CCmm & Composable & \citet{Sitaram93} \\ - \hline - cupto & \CCmm & Composable & \citet{GunterRR95}\\ - \hline - catchcont & \CCmm & Composable & \citet{Longley09}\\ - \hline - effect handlers & \CCmp & Composable & \citet{PlotkinP13} \\ - \hline - \end{tabular} - \caption{Classification of first-class delimited control operators (listed in chronological order).}\label{tbl:classify-ctrl-delimited} -\end{table} +\[ + \environment~\Record{\Root;\whoami} \reducesto^+ \strlit{root} : \String +\] % +It is not difficult to extend this basic environment model to support +an arbitrary number of variables. This can be done by parameterising +the $\Ask$ operation by some name representation (e.g. a string), +which the environment handler can use to index into a list of string +values. In case the name is unbound the environment, the handler can +embrace the laissez-faire attitude of \UNIX{} and resume with the +empty string. -\paragraph{A small calculus for control} +\paragraph{User session management} % -To look at control we will use a simply typed fine-grain call-by-value -calculus. Although, we will sometimes have to discard the types, as -many of the control operators were invented and studied in a untyped -setting. The calculus is essentially the same as the one used in -Chapter~\ref{ch:handlers-efficiency}, except that here we will have an -explicit invocation form for continuations. Although, in practice most -systems disguise continuations as first-class functions, but for a -theoretical examination it is convenient to treat them specially such -that continuation invocation is a separate reduction rule from -ordinary function application. Figure~\ref{fig:pcf-lang-control} -depicts the syntax of types and terms in the calculus. +It is somewhat pointless to have multiple user-specific environments, +if the system does not support some mechanism for user session +handling, such as signing in as a different user. % -\begin{figure} - \centering -\begin{syntax} - \slab{Types} & A,B \in \TypeCat &::=& \UnitType \mid A \to B \mid A \times B \mid \Cont\,\Record{A;B} \mid A + B \smallskip\\ - \slab{Values} & V,W \in \ValCat &::=& \Unit \mid \lambda x^A.M \mid \Record{V;W} \mid \cont_\EC \mid \Inl~V \mid \Inr~W \mid x\\ - \slab{Computations} & M,N \in \CompCat &::=& \Return\;V \mid \Let\;x \revto M \;\In\;N \mid \Let\;\Record{x;y} = V \;\In\; N \\ - & &\mid& V\,W \mid \Continue~V~W \smallskip\\ - \slab{Evaluation\textrm{ }contexts} & \EC \in \CatName{Ctx} &::=& [\,] \mid \Let\;x \revto \EC \;\In\;N -\end{syntax} -\caption{Types and term syntax}\label{fig:pcf-lang-control} -\end{figure} +In \UNIX{} the command \emph{substitute user} (su) enables the invoker +to impersonate another user account, provided the invoker has +sufficient privileges. % -The types are the standard simple types with the addition of the -continuation object type $\Cont\,\Record{A;B}$, which is parameterised -by an argument type and a result type, respectively. The static -semantics is standard as well, except for the continuation invocation -primitive $\Continue$. +We will implement su as an operation $\Su : \User \opto \UnitType$ +which is parameterised by the user to be impersonated. % -\begin{mathpar} - \inferrule* - {\typ{\Gamma}{V : A} \\ \typ{\Gamma}{W : \Cont\,\Record{A;B}}} - {\typ{\Gamma}{\Continue~W~V : B}} -\end{mathpar} +To model the security aspects of su, we will use the weakest possible +security model: unconditional trust. Put differently, we will not +bother with security at all to keep things relatively simple. % -Although, it is convenient to treat continuation application specially -for operational inspection, it is rather cumbersome to do so when -studying encodings of control operators. Therefore, to obtain the best -of both worlds, the control operators will reify their continuations -as first-class functions, whose body is $\Continue$-expression. To -save some ink, we will use the following notation. +Consequently, anyone can impersonate anyone else. + +The session signature consists of two operations, $\Ask$, which we +used above, and $\Su$, for switching user. % \[ - \qq{\cont_{\EC}} \defas \lambda x. \Continue~\cont_{\EC}~x + \EnvE \defas \{\Ask : \UnitType \opto \String;\Su : \User \opto \UnitType\} \] % -We will permit ourselves various syntactic sugar to keep the examples -relative concise, e.g. we write the examples in ordinary call-by-value. - -\subsection{Undelimited control operators} +As usual, we define a small wrapper around invocations of $\Su$. % -The early inventions of undelimited control operators were driven by -the desire to provide a `functional' equivalent of jumps as provided -by the infamous goto in imperative programming. +\[ + \bl + \su : \User \to \UnitType \eff \{\Su : \User \opto \UnitType\}\\ + \su~user \defas \Do\;\Su~user + \el +\] % -In 1965 Peter \citeauthor{Landin65} unveiled \emph{first} first-class -control operator: the J -operator~\cite{Landin65,Landin65a,Landin98}. Later in 1972 influenced -by \citeauthor{Landin65}'s J operator John \citeauthor{Reynolds98a} -designed the escape operator~\cite{Reynolds98a}. Influenced by escape, -\citeauthor{SussmanS75} designed, implemented, and standardised the -catch operator in Scheme in 1975. A while thereafter the perhaps most -famous undelimited control operator appeared: callcc. It initially -designed in 1982 and was standardised in 1985 as a core feature of -Scheme. Later another batch of control operators based on callcc -appeared. A common characteristic of the early control operators is -that their capture mechanisms were abortive and their captured -continuations were abortive, save for one, namely, -\citeauthor{Felleisen88}'s F operator. Later a non-abortive and -composable variant of callcc appeared. Moreover, every operator, -except for \citeauthor{Landin98}'s J operator, capture the current -continuation. - -\paragraph{\citeauthor{Reynolds98a}' escape} The escape operator was introduced by -\citeauthor{Reynolds98a} in 1972~\cite{Reynolds98a} to make -statement-oriented control mechanisms such as jumps and labels -programmable in an expression-oriented language. +The intended operational behaviour of an invocation of $\Su~user$ is +to load the environment belonging to $user$ and continue the +continuation under this environment. % -The operator introduces a new computation form. +We can achieve this behaviour by defining a handler for $\Su$ that +invokes the provided resumption under a fresh instance of the +$\environment$ handler. % \[ - M, N \in \CompCat ::= \cdots \mid \Escape\;k\;\In\;M + \bl + \sessionmgr : \Record{\User; \UnitType \to \alpha \eff \EnvE} \to \alpha\\ + \sessionmgr\,\Record{user;m} \defas + \environment\langle{}user;(\lambda\Unit. + \ba[t]{@{}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& res\\ + \OpCase{\Su}{user'}{resume} &\mapsto& \environment\Record{user';resume})\rangle + \ea + \ea + \el \] % -The variable $k$ is called the \emph{escape variable} and it is bound -in $M$. The escape variable exposes the current continuation of the -$\Escape$-expression to the programmer. The captured continuation is -abortive, thus an invocation of the escape variable in the body $M$ -has the effect of performing a non-local exit. +The function $\sessionmgr$ manages a user session. It takes two +arguments: the initial user ($user$) and the computation ($m$) to run +in the current session. An initial instance of $\environment$ is +installed with $user$ as argument. The computation argument is a +handler for $\Su$ enclosing the computation $m$. The $\Su$-case +installs a new instance of $\environment$, which is the environment +belonging to $user'$, and runs the resumption $resume$ under this +instance. +% +The new instance of $\environment$ shadows the initial instance, and +therefore it will intercept and handle any subsequent invocations of +$\Ask$ arising from running the resumption. A subsequent invocation of +$\Su$ will install another environment instance, which will shadow +both the previously installed instance and the initial instance. % -In terms of jumps and labels the $\Escape$-expression can be -understood as corresponding to a kind of label and an application of -the escape variable $k$ can be understood as corresponding to a jump -to the label. - -\citeauthor{Reynolds98a}' original treatise of escape was untyped, and -as such, the escape variable could escape its captor, e.g. +To make this concrete, let us plug together the all components of our +system we have defined thus far. % \[ - \Let\;k \revto (\Escape\;k\;\In\;k)\;\In\; N + \ba{@{~}l@{~}l} + &\bl + \basicIO\,(\lambda\Unit.\\ + \qquad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ + \qquad\qquad\status\,(\lambda\Unit. + \ba[t]{@{}l@{~}l} + \su~\Alice;&\echo\,(\whoami\,\Unit);~\echo~\strlit{ };\\ + \su~\Bob; &\echo\,(\whoami\,\Unit);~\echo~\strlit{ };\\ + \su~\Root; &\echo\,(\whoami\,\Unit))}) + \ea + \el \smallskip\\ + \reducesto^+& \Record{0;\strlit{alice bob root}} : \Record{\Int;\UFile} + \ea \] % -Here the current continuation, $N$, gets bound to $k$ in the -$\Escape$-expression, which returns $k$ as-is, and thus becomes -available for use within $N$. \citeauthor{Reynolds98a} recognised the -power of this idiom and noted that it could be used to implement -coroutines and backtracking~\cite{Reynolds98a}. +The session manager ($\sessionmgr$) is installed in between the basic +IO handler ($\basicIO$) and the process status handler +($\status$). The initial user is $\Root$, and thus the initial +environment is the environment that belongs to the root user. Main +computation signs in as $\Alice$ and writes the result of the system +call $\whoami$ to the global file, and then repeats these steps for +$\Bob$ and $\Root$. +% +Ultimately, the computation terminates successfully (as indicated by +$0$ in the first component of the result) with global file containing +the three user names. % -\citeauthor{Reynolds98a} did not develop the static semantics for -$\Escape$, however, it is worth noting that this idiom require -recursive types to type check. Even in a language without recursive -types, the continuation may propagate outside its binding -$\Escape$-expression if the language provides an escape hatch such as -mutable references. -% In our simply-typed setting it is not possible for the continuation to -% propagate outside its binding $\Escape$-expression as it would require -% the addition of either recursive types or some other escape hatch like -% mutable reference cells. -% % -% The typing of $\Escape$ and $\Continue$ reflects that the captured -% continuation is abortive. -% % -% \begin{mathpar} -% \inferrule* -% {\typ{\Gamma,k : \Cont\,\Record{A;\Zero}}{M : A}} -% {\typ{\Gamma}{\Escape\;k\;\In\;M : A}} +The above example demonstrates that we now have the basic building +blocks to build a multi-user system. +% +%\dhil{Remark on the concrete layering of handlers.} -% % \inferrule* -% % {\typ{\Gamma}{V : A} \\ \typ{\Gamma}{W : \Cont\,\Record{A;\Zero}}} -% % {\typ{\Gamma}{\Continue~W~V : \Zero}} -% \end{mathpar} -% % -% The return type of the continuation object can be taken as a telltale -% sign that an invocation of this object never returns, since there are -% no inhabitants of the empty type. +\section{Nondeterminism: time sharing} +\label{sec:tiny-unix-time} + +Time sharing is a mechanism that enables multiple processes to run +concurrently, and hence, multiple users to work concurrently. % -An invocation of the continuation discards the invocation context and -plugs the argument into the captured context. +Thus far in our system there is exactly one process. % -\begin{reductions} - \slab{Capture} & \EC[\Escape\;k\;\In\;M] &\reducesto& \EC[M[\qq{\cont_{\EC}}/k]]\\ - \slab{Resume} & \EC[\Continue~\cont_{\EC'}~V] &\reducesto& \EC'[V] -\end{reductions} +In \UNIX{} there exists only a single process whilst the system is +bootstrapping itself into operation. After bootstrapping is complete +the system duplicates the initial process to start running user +managed processes, which may duplicate themselves to create further +processes. % -The \slab{Capture} rule leaves the context intact such that if the -body $M$ does not invoke $k$ then whatever value $M$ reduces is -plugged into the context. The \slab{Resume} discards the current -context $\EC$ and installs the captured context $\EC'$ with the -argument $V$ plugged in. - -\paragraph{\citeauthor{SussmanS75}'s catch} +The process duplication primitive in \UNIX{} is called +\emph{fork}~\cite{RitchieT74}. % -In 1975 \citet{SussmanS75} designed and implemented the catch operator -in Scheme. It is a more powerful variant of the catch operator in -MacLisp~\cite{Moon74}. The MacLisp catch operator had a companion -throw operation, which would unwind the evaluation stack until it was -caught by an instance of catch. \citeauthor{SussmanS75}'s catch -operator dispenses with the throw operation and instead provides the -programmer with access to the current continuation. Their operator is -identical to \citeauthor{Reynolds98a}' escape operator, save for the -syntax. +The fork-invoking process is typically referred to as the parent +process, whilst its clone is referred to as the child process. +% +Following an invocation of fork, the parent process is provided with a +nonzero identifier for the child process and the child process is +provided with the zero identifier. This enables processes to determine +their respective role in the parent-child relationship, e.g. % \[ - M,N \in \CompCat ::= \cdots \mid \Catch~k.M + \bl + \Let\;i\revto fork~\Unit\;\In\\ + \If\;i = 0\;\Then\; + ~\textit{child's code}\\ + \Else\;~\textit{parent's code} + \el \] % -Although, their syntax differ, their dynamic semantics are the same. +In our system, we can model fork as an effectful operation, that +returns a boolean to indicate the process role; by convention we will +interpret the return value $\True$ to mean that the process assumes +the role of parent. % -% \begin{mathpar} -% \inferrule* -% {\typ{\Gamma,k : \Cont\,\Record{A;\Zero}}{M : A}} -% {\typ{\Gamma}{\Catch~k.M : A}} - -% % \inferrule* -% % {\typ{\Gamma}{V : A} \\ \typ{\Gamma}{W : \Cont\,\Record{A;B}}} -% % {\typ{\Gamma}{\Continue~W~V : B}} -% \end{mathpar} +\[ + \bl + \fork : \UnitType \to \Bool \eff \{\Fork : \UnitType \opto \Bool\}\\ + \fork~\Unit \defas \Do\;\Fork~\Unit + \el +\] % -\begin{reductions} - \slab{Capture} & \EC[\Catch~k.M] &\reducesto& \EC[M[\qq{\cont_{\EC}}/k]]\\ - \slab{Resume} & \EC[\Continue~\cont_{\EC'}~V] &\reducesto& \EC'[V] -\end{reductions} +In \UNIX{} the parent process \emph{continues} execution after the +fork point, and the child process \emph{begins} its execution after +the fork point. % -As an aside it is worth to mention that \citet{CartwrightF92} used a -variation of $\Catch$ to show that control operators enable programs -to observe the order of evaluation. - -\paragraph{Call-with-current-continuation} In 1982 the Scheme -implementors observed that they could dispense of the special syntax -for $\Catch$ in favour of a higher-order function that would apply its -argument to the current continuation, and thus callcc was born (callcc -is short for -call-with-current-continuation)~\cite{AbelsonHAKBOBPCRFRHSHW85}. +Thus, operationally, we may understand fork as returning twice to its +invocation site. We can implement this behaviour by invoking the +resumption arising from an invocation of $\Fork$ twice: first with +$\True$ to continue the parent process, and subsequently with $\False$ +to start the child process (or the other way around if we feel +inclined). % - -Unlike the previous operators, callcc augments the syntactic -categories of values. +The following handler implements this behaviour. % \[ - V,W \in \ValCat ::= \cdots \mid \Callcc + \bl + \nondet : (\UnitType \to \alpha \eff \{\Fork:\UnitType \opto \Bool\}) \to \List~\alpha\\ + \nondet~m \defas + \ba[t]{@{}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& [res]\\ + \OpCase{\Fork}{\Unit}{resume} &\mapsto& resume~\True \concat resume~\False + \ea + \ea + \el \] % -The value $\Callcc$ is essentially a hard-wired function name. Being a -value means that the operator itself is a first-class entity which -entails it can be passed to functions, returned from functions, and -stored in data structures. Operationally, $\Callcc$ captures the -current continuation and aborts it before applying it on its argument. +The $\Return$-case returns a singleton list containing a result of +running $m$. % -% The typing rule for $\Callcc$ testifies to the fact that it is a -% particular higher-order function. +The $\Fork$-case invokes the provided resumption $resume$ twice. Each +invocation of $resume$ effectively copies $m$ and runs each copy to +completion. Each copy returns through the $\Return$-case, hence each +invocation of $resume$ returns a list of the possible results obtained +by interpreting $\Fork$ first as $\True$ and subsequently as +$\False$. The results are joined by list concatenation ($\concat$). % -% \begin{mathpar} -% \inferrule* -% {~} -% {\typ{\Gamma}{\Callcc : (\Cont\,\Record{A;\Zero} \to A) \to A}} +Thus the handler returns a list of all the possible results of $m$. +% +In fact, this handler is exactly the standard handler for +nondeterministic choice, which satisfies the standard semi-lattice +equations~\cite{PlotkinP09,PlotkinP13}. +% \dhil{This is an instance of non-blind backtracking~\cite{FriedmanHK84}} -% % \inferrule* -% % {\typ{\Gamma}{V : A} \\ \typ{\Gamma}{W : \Cont\,\Record{A;B}}} -% % {\typ{\Gamma}{\Continue~W~V : B}} -% \end{mathpar} -% % -% An invocation of $\Callcc$ returns a value of type $A$. This value can -% be produced in one of two ways, either the function argument returns -% normally or it applies the provided continuation object to a value -% that then becomes the result of $\Callcc$-application. +Let us consider $\nondet$ together with the previously defined +handlers. But first, let us define two computations. % -\begin{reductions} - \slab{Capture} & \EC[\Callcc~V] &\reducesto& \EC[V~\qq{\cont_{\EC}}]\\ - \slab{Resume} & \EC[\Continue~\cont_{\EC'}~V] &\reducesto& \EC'[V] -\end{reductions} +\[ + \bl + \quoteRitchie,\;\quoteHamlet : \UnitType \to \UnitType \eff \{\Write: \Record{\UFD;\String} \opto \UnitType\} \smallskip\\ + \quoteRitchie\,\Unit \defas + \ba[t]{@{~}l} + \echo~\strlit{UNIX is basically };\\ + \echo~\strlit{a simple operating system, };\\ + \echo~\strlit{but };\\ + \echo~\texttt{"} + \ba[t]{@{}l} + \texttt{you have to be a genius }\\ + \texttt{to understand the simplicity.\nl{}"} + \ea + \ea \smallskip\\ + \quoteHamlet\,\Unit \defas + \ba[t]{@{}l} + \echo~\strlit{To be, or not to be, };\\ + \echo~\strlit{that is the question:\nl};\\ + \echo~\strlit{Whether 'tis nobler in the mind to suffer\nl} + \ea + \el +\] % -From the dynamic semantics it is evident that $\Callcc$ is a -syntax-free alternative to $\Catch$ (although, it is treated as a -special value form here; in actual implementation it suffices to -recognise the object name of $\Callcc$). They are trivially -macro-expressible. +The computation $\quoteRitchie$ writes a quote by Dennis Ritchie to +the file, whilst the computation $\quoteHamlet$ writes a few lines of +William Shakespeare's \emph{The Tragedy of Hamlet, Prince of Denmark}, +Act III, Scene I~\cite{Shakespeare6416} to the file. % -\begin{equations} - \sembr{\Catch~k.M} &\defas& \Callcc\,(\lambda k.\sembr{M})\\ - \sembr{\Callcc} &\defas& \lambda f. \Catch~k.f\,k -\end{equations} - -\paragraph{Call-with-composable-continuation} A variation of callcc is -call-with-composable-continuation, abbreviated \textCallcomc{}. +Using $\nondet$ and $\fork$ together with the previously defined +infrastructure, we can fork the initial process such that both of the +above computations are run concurrently. % -As the name suggests the captured continuation is composable rather -than abortive. It was introduced by \citet{FlattYFF07} in 2007, and -implemented in November 2006 according to the history log of Racket -(Racket was then known as MzScheme, version 360)~\cite{Flatt20}. The -history log classifies it as a delimited control operator. -% -Truth to be told nowadays in Racket virtually all control operators -are delimited, even callcc, because they are parameterised by an -optional prompt tag. If the programmer does not supply a prompt tag at -invocation time then the optional parameter assume the actual value of -the top-level prompt, effectively making the extent of the captured -continuation undelimited. +\[ + \ba{@{~}l@{~}l} + &\bl + \basicIO\,(\lambda\Unit.\\ + \qquad\nondet\,(\lambda\Unit.\\ + \qquad\qquad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ + \qquad\qquad\qquad\status\,(\lambda\Unit. + \ba[t]{@{}l} + \If\;\fork\,\Unit\;\Then\; + \su~\Alice;\, + \quoteRitchie~\Unit\\ + \Else\; + \su~\Bob;\, + \quoteHamlet~\Unit)})) + \ea + \el \smallskip\\ + \reducesto^+& + \Record{ + \ba[t]{@{}l} + [0, 0]; + \texttt{"}\ba[t]{@{}l} + \texttt{UNIX is basically a simple operating system, but }\\ + \texttt{you have to be a genius to understand the simplicity.\nl}\\ + \texttt{To be, or not to be, that is the question:\nl}\\ + \texttt{Whether 'tis nobler in the mind to suffer\nl"}} : \Record{\List~\Int; \UFile} + \ea + \ea + \ea +\] % -In other words its default mode of operation is undelimited, hence the -justification for categorising it as such. +The computation running under the $\status$ handler immediately +performs an invocation of fork, causing $\nondet$ to explore both the +$\Then$-branch and the $\Else$-branch. In the former, $\Alice$ signs +in and quotes Ritchie, whilst in the latter Bob signs in and quotes a +Hamlet. % +Looking at the output there is supposedly no interleaving of +computation, since the individual writes have not been +interleaved. From the stack of handlers, we \emph{know} that there has +been no interleaving of computation, because no handler in the stack +handles interleaving. Thus, our system only supports time sharing in +the extreme sense: we know from the $\nondet$ handler that every +effect of the parent process will be performed and handled before the +child process gets to run. In order to be able to share time properly +amongst processes, we must be able to interrupt them. -Like $\Callcc$ this operator is a value. +\paragraph{Interleaving computation} +% +We need an operation for interruptions and corresponding handler to +handle interrupts in order for the system to support interleaving of +processes. % \[ - V,W \in \ValCat ::= \cdots \mid \Callcomc + \bl + \interrupt : \UnitType \to \UnitType \eff \{\Interrupt : \UnitType \opto \UnitType\}\\ + \interrupt~\Unit \defas \Do\;\Interrupt~\Unit + \el \] % -% Unlike $\Callcc$, the continuation returns, which the typing rule for -% $\Callcomc$ reflects. -% % -% \begin{mathpar} -% \inferrule* -% {~} -% {\typ{\Gamma}{\Callcomc : (\Cont\,\Record{A;A} \to A) \to A}} - -% \inferrule* -% {\typ{\Gamma}{V : A} \\ \typ{\Gamma}{W : \Cont\,\Record{A;B}}} -% {\typ{\Gamma}{\Continue~W~V : B}} -% \end{mathpar} -% % -% Both the domain and codomain of the continuation are the same as the -% body type of function argument. -Unlike $\Callcc$, captured continuations behave as functions. +The intended behaviour of an invocation of $\Interrupt$ is to suspend +the invoking computation in order to yield time for another +computation to run. % -\begin{reductions} - \slab{Capture} & \EC[\Callcomc~V] &\reducesto& \EC[V~\qq{\cont_{\EC}}]\\ - \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V] -\end{reductions} +We can achieve this behaviour by reifying the process state. For the +purpose of interleaving processes via interruptions it suffices to +view a process as being in either of two states: 1) it is done, that +is it has run to completion, or 2) it is paused, meaning it has +yielded to provide room for another process to run. % -The capture rule for $\Callcomc$ is identical to the rule for -$\Callcc$, but the resume rule is different. +We can model the state using a recursive variant type parameterised by +some return value $\alpha$ and a set of effects $\varepsilon$ that the +process may perform. % -The effect of continuation invocation can be understood locally as it -does not erase the global evaluation context, but rather composes with -it. +\[ + \Pstate~\alpha~\varepsilon~\theta \defas + \ba[t]{@{}l@{}l} + [&\Done:\alpha;\\ + &\Suspended:\UnitType \to \Pstate~\alpha~\varepsilon~\theta \eff \{\Interrupt:\theta;\varepsilon\} ] + \ea +\] % -To make this more tangible consider the following example reduction -sequence. +This data type definition is an instance of the \emph{resumption + monad}~\cite{Papaspyrou01}. The $\Done$-tag simply carries the +return value of type $\alpha$. The $\Suspended$-tag carries a +suspended computation, which returns another instance of $\Pstate$, +and may or may not perform any further invocations of +$\Interrupt$. Payload type of $\Suspended$ is precisely the type of a +resumption originating from a handler that handles only the operation +$\Interrupt$ such as the following handler. % -\begin{derivation} - &1 + \Callcomc\,(\lambda k. \Continue~k~(\Continue~k~0))\\ -\reducesto^+ & \reason{\slab{Capture} $\EC = 1 + [~]$}\\ -% &1 + ((\lambda k. \Continue~k~(\Continue~k~0))\,\cont_\EC)\\ -% \reducesto & \reason{$\beta$-reduction}\\ - &1 + (\Continue~\cont_\EC~(\Continue~\cont_\EC~0))\\ -\reducesto^+ & \reason{\slab{Resume} with $\EC[0]$}\\ - &1 + (\Continue~\cont_\EC~1)\\ -\reducesto^+ & \reason{\slab{Resume} with $\EC[1]$}\\ - &1 + 2 \reducesto 3 -\end{derivation} +\[ + \bl + \reifyP : (\UnitType \to \alpha \eff \{\Interrupt: \UnitType \opto \UnitType;\varepsilon\}) \to \Pstate~\alpha~\varepsilon\\ + \reifyP~m \defas + \ba[t]{@{}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& \Done~res\\ + \OpCase{\Interrupt}{\Unit}{resume} &\mapsto& \Suspended~resume + \ea + \ea + \el +\] % -The operator reifies the current evaluation context as a continuation -object and passes it to the function argument. The evaluation context -is left in place. As a result an invocation of the continuation object -has the effect of duplicating the context. In this particular example -the context has been duplicated twice to produce the result $3$. +This handler tags and returns values with $\Done$. It also tags and +returns the resumption provided by the $\Interrupt$-case with +$\Suspended$. % -Contrast this result with the result obtained by using $\Callcc$. +This particular implementation is amounts to a handler-based variation +of \citeauthor{Harrison06}'s non-reactive resumption +monad~\cite{Harrison06}. % -\begin{derivation} - &1 + \Callcc\,(\lambda k. \Absurd\;\Continue~k~(\Absurd\;\Continue~k~0))\\ -\reducesto^+ & \reason{\slab{Capture} $\EC = 1 + [~]$}\\ -% &1 + ((\lambda k. \Continue~k~(\Continue~k~0))\,\cont_\EC)\\ -% \reducesto & \reason{$\beta$-reduction}\\ - &1 + (\Absurd\;\Continue~\cont_\EC~(\Absurd\;\Continue~\cont_\EC~0))\\ -\reducesto & \reason{\slab{Resume} with $\EC[0]$}\\ - &1\\ -\end{derivation} +If we compose this handler with the nondeterminism +handler, then we obtain a term with the following type. % -The second invocation of $\cont_\EC$ never enters evaluation position, -because the first invocation discards the entire evaluation context. +\[ + \nondet\,(\lambda\Unit.\reifyP~m) : \List~(\Pstate~\alpha~\{\Fork: \UnitType \opto \Bool;\varepsilon\}) +\] % -Our particular choice of syntax and static semantics already makes it -immediately obvious that $\Callcc$ cannot be directly substituted for -$\Callcomc$, and vice versa, in a way that preserves operational -behaviour. % The continuations captured by the two operators behave -% differently. - -An interesting question is whether $\Callcc$ and $\Callcomc$ are -interdefinable. Presently, the literature does not seem to answer to -this question. I conjecture that the operators exhibit essential -differences, meaning they cannot encode each other. +for some $m : \UnitType \to \{\Proc;\varepsilon\}$ where +$\Proc \defas \{\Fork: \UnitType \opto \Bool;\Interrupt: \UnitType +\opto \UnitType\}$. % -The intuition behind this conjecture is that for any encoding of -$\Callcomc$ in terms of $\Callcc$ must be able to preserve the current -evaluation context, e.g. using a state cell akin to how -\citet{Filinski94} encodes composable continuations using abortive -continuations and state. +The composition yields a list of process states, some of which may be +in suspended state. In particular, the suspended computations may have +unhandled instances of $\Fork$ as signified by it being present in the +effect row. The reason for this is that in the above composition when +$\reifyP$ produces a $\Suspended$-tagged resumption, it immediately +returns through the $\Return$-case of $\nondet$, meaning that the +resumption escapes the $\nondet$. Recall that a resumption is a +delimited continuation that captures the extent from the operation +invocation up to and including the nearest enclosing suitable +handler. In this particular instance, it means that the $\nondet$ +handler is part of the extent. % -The other way around also appears to be impossible, because neither -the base calculus nor $\Callcomc$ has the ability to discard an -evaluation context. +We ultimately want to return just a list of $\alpha$s to ensure every +process has run to completion. To achieve this, we need a function +that keeps track of the state of every process, and in particular it +must run each $\Suspended$-tagged computation under the $\nondet$ +handler to produce another list of process state, which must be +handled recursively. % -% \dhil{Remark that $\Callcomc$ was originally obtained by decomposing -% $\fcontrol$ a continuation composing primitive and an abortive -% primitive. Source: Matthew Flatt, comp.lang.scheme, May 2007} - -\paragraph{\FelleisenC{} and \FelleisenF{}} +\[ + \bl + \schedule : \List~(\Pstate~\alpha~\{\Fork:\Bool;\varepsilon\}~\theta) \to \List~\alpha \eff \varepsilon\\ + \schedule~ps \defas + \ba[t]{@{}l} + \Let\;run \revto + \Rec\;sched\,\Record{ps;done}.\\ + \qquad\Case\;ps\;\{ + \ba[t]{@{}r@{~}c@{~}l} + \nil &\mapsto& done\\ + (\Done~res) \cons ps' &\mapsto& sched\,\Record{ps';res \cons done}\\ + (\Suspended~m) \cons ps' &\mapsto& sched\,\Record{ps' \concat (\nondet~m);\, done} \} + \ea\\ + \In\;run\,\Record{ps;\nil} + \ea + \el +\] % -The C operator is a variation of callcc that provides control over the -whole continuation as it aborts the current continuation after -capture, whereas callcc implicitly invokes the current continuation on -the value of its argument. The C operator was introduced by -\citeauthor{FelleisenFKD86} in two papers during -1986~\cite{FelleisenF86,FelleisenFKD86}. The following year, -\citet{FelleisenFDM87} introduced the F operator which is a variation -of C, whose captured continuation is composable. +The function $\schedule$ implements a process scheduler. It takes as +input a list of process states, where $\Suspended$-tagged computations +may perform the $\Fork$ operation. Locally it defines a recursive +function $sched$ which carries a list of active processes $ps$ and the +results of completed processes $done$. The function inspects the +process list $ps$ to test whether it is empty or nonempty. If it is +empty it returns the list of results $done$. Otherwise, if the head is +$\Done$-tagged value, then the function is recursively invoked with +tail of processes $ps'$ and the list $done$ augmented with the value +$res$. If the head is a $\Suspended$-tagged computation $m$, then +$sched$ is recursively invoked with the process list $ps'$ +concatenated with the result of running $m$ under the $\nondet$ +handler. -In our framework both operators are value forms. % -\[ - V,W \in \ValCat ::= \cdots \mid \FelleisenC \mid \FelleisenF -\] +Using the above machinery, we can define a function which adds +time-sharing capabilities to the system. % -% The static semantics of $\FelleisenC$ are the same as $\Callcc$, -% whilst the static semantics of $\FelleisenF$ are the same as -% $\Callcomc$. -% \begin{mathpar} -% \inferrule* -% {~} -% {\typ{\Gamma}{\FelleisenC : (\Cont\,\Record{A;\Zero} \to A) \to A}} - -% \inferrule* -% {~} -% {\typ{\Gamma}{\FelleisenF : (\Cont\,\Record{A;A} \to A) \to A}} -% \end{mathpar} +\[ + \bl + \timeshare : (\UnitType \to \alpha \eff \Proc) \to \List~\alpha\\ + \timeshare~m \defas \schedule\,[\Suspended\,(\lambda\Unit.\reifyP~m)] + \el +\] % -The dynamic semantics of $\FelleisenC$ and $\FelleisenF$ are as -follows. +The function $\timeshare$ handles the invocations of $\Fork$ and +$\Interrupt$ in some computation $m$ by starting it in suspended state +under the $\reifyP$ handler. The $\schedule$ actually starts the +computation, when it runs the computation under the $\nondet$ handler. % -\begin{reductions} - \slab{C\textrm{-}Capture} & \EC[\FelleisenC\,V] &\reducesto& V~\qq{\cont_{\EC}}\\ - \slab{C\textrm{-}Resume} & \EC[\Continue~\cont_{\EC'}~V] &\reducesto& \EC'[V] \medskip\\ - \slab{F\textrm{-}Capture} & \EC[\FelleisenF\,V] &\reducesto& V~\qq{\cont_{\EC}}\\ - \slab{F\textrm{-}Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V] -\end{reductions} + +The question remains how to inject invocations of $\Interrupt$ such +that computation gets interleaved. + +\paragraph{Interruption via interception} % -Their capture rules are identical. Both operators abort the current -continuation upon capture. This is what set $\FelleisenF$ apart from -the other composable control operator $\Callcomc$. +To implement process preemption operating systems typically to rely on +the underlying hardware to asynchronously generate some kind of +interruption signals. These signals can be caught by the operating +system's process scheduler, which can then decide to which processes +to suspend and continue. +% +If our core calculi had an integrated notion of asynchrony and effects +along the lines of \citeauthor{AhmanP21}'s core calculus +$\lambda_{\text{\ae}}$~\cite{AhmanP21}, then we could potentially +treat interruption signals as asynchronous effectful operations, which +can occur spontaneously and, as suggested by \citet{DolanEHMSW17} and +realised by \citet{Poulson20}, be handled by a user-definable handler. % -The resume rules of $\FelleisenC$ and $\FelleisenF$ show the -difference between the two operators. The $\FelleisenC$ operator -aborts the current continuation and reinstall the then-current -continuation just like $\Callcc$, whereas the resumption of a -continuation captured by $\FelleisenF$ composes the current -continuation with the then-current continuation. -\citet{FelleisenFDM87} show that $\FelleisenF$ can simulate -$\FelleisenC$. +In the absence of asynchronous effects we have to inject synchronous +interruptions ourselves. +% +One extreme approach is to trust the user to perform invocations of +$\Interrupt$ periodically. +% +Another approach is based on the fact that every effect (except for +divergence) occurs via some operation invocation, and every-so-often +the user is likely to perform computational effect, thus the basic +idea is to bundle $\Interrupt$ with invocations of other +operations. For example, we can insert an instance of $\Interrupt$ in +some of the wrapper functions for operation invocations that we have +defined so conscientiously thus far. The problem with this approach is +that it requires a change of type signatures. To exemplify this +problem consider type of the $\echo$ function if we were to bundle an +invocation of $\Interrupt$ along side $\Write$. % \[ - \sembr{\FelleisenC} \defas \lambda m.\FelleisenF\,(\lambda k. m\,(\lambda v.\FelleisenF\,(\lambda\_.k~v))) + \bl + \echo' : \String \to \UnitType \eff \{\Interrupt : \UnitType \opto \UnitType;\Write : \Record{\UFD;\String} \opto \UnitType\}\\ + \echo'~cs \defas \Do\;\Interrupt\,\Unit;\,\Do\;\Write\,\Record{\stdout;cs} + \el \] % -The first application of $\FelleisenF$ has the effect of aborting the -current continuation, whilst the second application of $\FelleisenF$ -aborts the invocation context. - -\citet{FelleisenFDM87} also postulate that $\FelleisenC$ cannot express $\FelleisenF$. +In addition to $\Write$ the effect row must now necessarily mention +the $\Interrupt$ operation. As a consequence this approach is not +backwards compatible, since the original definition of $\echo$ can be +used in a context that prohibits occurrences of $\Interrupt$. Clearly, +this alternative definition cannot be applied in such a context. -\paragraph{\citeauthor{Landin98}'s J operator} +There is backwards-compatible way to bundle the two operations +together. We can implement a handler that \emph{intercepts} +invocations of $\Write$ and handles them by performing an interrupt +and, crucially, reperforming the intercepted write operation. % -The J operator was introduced by Peter Landin in 1965 (making it the -world's \emph{first} first-class control operator) as a means for -translating jumps and labels in the statement-oriented language -\Algol{} into an expression-oriented -language~\cite{Landin65,Landin65a,Landin98}. Landin used the J -operator to account for the meaning of \Algol{} labels. +\[ + \bl + \dec{interruptWrite} : + \ba[t]{@{~}l@{~}l} + &(\UnitType \to \alpha \eff \{\Interrupt : \UnitType \opto \UnitType;\Write : \Record{\UFD;\String} \opto \UnitType\})\\ + \to& \alpha \eff \{\Interrupt : \UnitType \opto \UnitType;\Write : \Record{\UFD;\String} \opto \UnitType\} + \ea\\ + \dec{interruptWrite}~m \defas + \ba[t]{@{~}l} + \Handle\;m~\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& res\\ + \OpCase{\Write}{\Record{fd;cs}}{resume} &\mapsto& + \ba[t]{@{}l} + \interrupt\,\Unit;\\ + resume\,(\Do\;\Write~\Record{fd;cs}) + \ea + \ea + \ea + \el +\] % -The following example due to \citet{DanvyM08} provides a flavour of -the correspondence between labels and J. +This handler is not `self-contained' as the other handlers we have +defined previously. It gives in some sense a `partial' interpretation +of $\Write$ as it leaves open the semantics of $\Interrupt$ and +$\Write$, i.e. this handler must be run in a suitable context of other +handlers. + +Let us plug this handler into the previous example to see what +happens. % \[ - \ba{@{}l@{~}l} - &\mathcal{S}\sembr{\keyw{begin}\;s_1;\;\keyw{goto}\;L;\;L:\,s_2\;\keyw{end}}\\ - =& \lambda\Unit.\Let\;L \revto \J\,\mathcal{S}\sembr{s_2}\;\In\;\Let\;\Unit \revto \mathcal{S}\sembr{s_1}\,\Unit\;\In\;\Continue~L\,\Unit + \ba{@{~}l@{~}l} + &\bl + \basicIO\,(\lambda\Unit.\\ + \qquad\timeshare\,(\lambda\Unit.\\ + \qquad\qquad\dec{interruptWrite}\,(\lambda\Unit.\\ + \qquad\qquad\qquad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ + \qquad\qquad\qquad\qquad\status\,(\lambda\Unit. + \ba[t]{@{}l} + \If\;\fork\,\Unit\;\Then\; + \su~\Alice;\, + \quoteRitchie~\Unit\\ + \Else\; + \su~\Bob;\, + \quoteHamlet~\Unit)}))) + \ea + \el \smallskip\\ + \reducesto^+& + \bl + \Record{ + \ba[t]{@{}l} + [0, 0]; + \texttt{"}\ba[t]{@{}l} + \texttt{UNIX is basically To be, or not to be,\nl{}}\\ + \texttt{a simple operating system, that is the question:\nl{}}\\ + \texttt{but Whether 'tis nobler in the mind to suffer\nl{}}\\ + \texttt{you have to be a genius to understand the simplicity.\nl{}"}} + \ea + \ea\\ + : \Record{\List~\Int; \UFile} + \el \ea \] % -Here $\mathcal{S}\sembr{-}$ denotes the translation of statements. In the image, -the label $L$ manifests as an application of $\J$ and the -$\keyw{goto}$ manifests as an application of continuation captured by -$\J$. +Evidently, each write operation has been interleaved, resulting in a +mishmash poetry of Shakespeare and \UNIX{}. % -The operator extends the syntactic category of values with a new -form. +I will leave it to the reader to be the judge of whether this new +poetry belongs under the category of either classic arts vandalism or +novel contemporary reinterpretations. As the saying goes: \emph{art + is in the eye of the beholder}. + +\section{State: file i/o} +\label{sec:tiny-unix-io} + +Thus far the system supports limited I/O, abnormal process +termination, multiple user sessions, and multi-tasking via concurrent +processes. At this stage we have most of core features in place. We +still have to complete the I/O model. The current I/O model provides +an incomplete file system consisting of a single write-only file. % -\[ - V,W \in \ValCat ::= \cdots \mid \J -\] +In this section we will implement a \UNIX{}-like file system that +supports file creation, opening, truncation, read and write +operations, and file linking. % -The previous example hints at the fact that the J operator is quite -different to the previously considered undelimited control operators -in that the captured continuation is \emph{not} the current -continuation, but rather, the continuation of statically enclosing -$\lambda$-abstraction. In other words, $\J$ provides access to the -continuation of its the caller. + +To implement a file system we will need to use state. State can +readily be implemented with an effect handler~\cite{KammarLO13}. % -To this effect, the continuation object produced by an application of -$\J$ may be thought of as a first-class variation of the return -statement commonly found in statement-oriented languages. Since it is -a first-class object it can be passed to another function, meaning -that any function can endow other functions with the ability to return -from it, e.g. +It is a deliberate choice to leave state for last, because once you +have state it is tempting to use it excessively --- to the extent it +becomes a cliche. % -\[ - \dec{f} \defas \lambda g. \Let\;return \revto \J\,(\lambda x.x) \;\In\; g~return;~\True -\] +As demonstrated in the previous sections, it is possible to achieve +many things that have a stateful flavour without explicit state by +harnessing the implicit state provided by the program stack. + +In the following subsection, I will provide an interface for stateful +operations and their implementation in terms of a handler. The +stateful operations will be put to use in the subsequent subsection to +implement a basic sequential file system. + +\subsubsection{Handling state} + +The interface for accessing and updating a state cell consists of two +operations. % -If the function $g$ does not invoke its argument, then $\dec{f}$ -returns $\True$, e.g. \[ - \dec{f}~(\lambda return.\False) \reducesto^+ \True + \State~\beta \defas \{\Get:\UnitType \opto \beta;\Put:\beta \opto \UnitType\} \] % -However, if $g$ does apply its argument, then the value provided to -the application becomes the return value of $\dec{f}$, e.g. +The intended operational behaviour of $\Get$ operation is to read the +value of type $\beta$ of the state cell, whilst the $\Put$ operation +is intended to replace the current value held by the state cell with +another value of type $\beta$. As per usual business, the following +functions abstract the invocation of the operations. % \[ - \dec{f}~(\lambda return.\Continue~return~\False) \reducesto^+ \False + \ba{@{~}l@{\quad\qquad\quad}c@{~}l} + \Uget : \UnitType \to \beta \eff \{\Get:\UnitType \opto \beta\} + & & + \Uput : \UnitType \to \beta \eff \{\Put:\beta \opto \UnitType\}\\ + \Uget~\Unit \defas \Do\;\Get~\Unit + & & + \Uput~st \defas \Do\;\Put~st + \el \] % -The function argument gets post-composed with the continuation of the -calling context. +The following handler interprets the operations. % -The particular application $\J\,(\lambda x.x)$ is so idiomatic that it -has its own name: $\JI$, where $\keyw{I}$ is the identity function. - -% Clearly, the return type of a continuation object produced by an $\J$ -% application must be the same as the caller of $\J$. Thus to type $\J$ -% we must track the type of calling context. Formally, we track the type -% of the context by extending the typing judgement relation with an -% additional singleton context $\Delta$. This context is modified by the -% typing rule for $\lambda$-abstraction and used by the typing rule for -% $\J$-applications. This is similar to type checking of return -% statements in statement-oriented programming languages. -% % -% \begin{mathpar} -% \inferrule* -% {\typ{\Gamma,x:A;B}{M : B}} -% {\typ{\Gamma;\Delta}{\lambda x.M : A \to B}} - -% \inferrule* -% {~} -% {\typ{\Gamma;B}{\J : (A \to B) \to \Cont\,\Record{A;B}}} - -% % \inferrule* -% % {\typ{\Gamma;\Delta}{V : A} \\ \typ{\Gamma;\Delta}{W : \Cont\,\Record{A;B}}} -% % {\typ{\Gamma;\Delta}{\Continue~W~V : B}} -% \end{mathpar} +\[ + \bl + \runState : \Record{\beta;\UnitType \to \alpha \eff \State~\beta} \to \Record{\alpha;\beta}\\ + \runState~\Record{st_0;m} \defas + \ba[t]{@{}l} + \Let\;run \revto + \ba[t]{@{}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& \lambda st.\Record{res;st}\\ + \OpCase{\Get}{\Unit}{resume} &\mapsto& \lambda st.resume~st~st\\ + \OpCase{\Put}{st'}{resume} &\mapsto& \lambda st.resume~\Unit~st' + \ea + \ea\\ + \In\;run~st_0 + \ea + \el +\] % -Any meaningful applications of $\J$ must appear under a -$\lambda$-abstraction, because the application captures its caller's -continuation. In order to capture the caller's continuation we -annotate the evaluation contexts for ordinary applications. +The $\runState$ handler provides a generic way to interpret any +stateful computation. It takes as its first parameter the initial +value of the state cell. The second parameter is a potentially +stateful computation. Ultimately, the handler returns the value of the +input computation along with the current value of the state cell. + +This formulation of state handling is analogous to the standard +monadic implementation of state handling~\citep{Wadler95}. In the +context of handlers, the implementation uses a technique known as +\emph{parameter-passing}~\citep{PlotkinP09,Pretnar15}. % -\begin{reductions} - \slab{Annotate} & \EC[(\lambda x.M)\,V] &\reducesto& \EC_\lambda[M[V/x]]\\ - \slab{Capture} & \EC_{\lambda}[\mathcal{D}[\J\,W]] &\reducesto& \EC_{\lambda}[\mathcal{D}[\qq{\cont_{\Record{\EC_{\lambda};W}}}]]\\ - \slab{Resume} & \EC[\Continue~\cont_{\Record{\EC';W}}\,V] &\reducesto& \EC'[W\,V] -\end{reductions} +Each case returns a state-accepting function. % -% \dhil{The continuation object should have time $\Cont\,\Record{A;\Zero}$} +The $\Return$-case returns a function that produces a pair consisting +of return value of $m$ and the final state $st$. % -The $\slab{Capture}$ rule only applies if the application of $\J$ -takes place inside an annotated evaluation context. The continuation -object produced by a $\J$ application encompasses the caller's -continuation $\EC_\lambda$ and the value argument $W$. +The $\Get$-case returns a function that applies the resumption +$resume$ to the current state $st$. Recall that return type of a +resumption is the same as its handler's return type, so since the +handler returns a function, it follows that +$resume : \beta \to \beta \to \Record{\alpha, \beta}$. In other words, +the invocation of $resume$ produces another state-accepting +function. This function arises from the next activation of the handler +either by way of a subsequent operation invocation in $m$ or the +completion of $m$ to invoke the $\Return$-case. Since $\Get$ does not +modify the value of the state cell it passes $st$ unmodified to the +next handler activation. % -This continuation object may be invoked in \emph{any} context. An -invocation discards the current continuation $\EC$ and installs $\EC'$ -instead with the $\J$-argument $W$ applied to the value $V$. +In the $\Put$-case the resumption must also produce a state-accepting +function of the same type, however, the type of the resumption is +slightly different +$resume : \UnitType \to \beta \to \Record{\alpha, \beta}$. The unit +type is the expected return type of $\Put$. The state-accepting +function arising from $resume~\Unit$ is supplied with the new state +value $st'$. This application effectively discards the current state +value $st$. -\citeauthor{Landin98} and \citeauthor{Thielecke02} noticed that $\J$ -can be recovered from the special form -$\JI$~\cite{Thielecke02}. Taking $\JI$ to be a primitive, we can -translate $\J$ to a language with $\JI$ as follows. +The first operation invocation in $m$, or if it completes without +invoking $\Get$ or $\Put$, the handler returns a function that accepts +the initial state. The function gets bound to $run$ which is +subsequently applied to the provided initial state $st_0$ which causes +evaluation of the stateful fragment of $m$ to continue. + +\paragraph{Local state vs global state} The meaning of stateful +operations may depend on whether the ambient environment is +nondeterministic. Post-composing nondeterminism with state gives rise +to the so-called \emph{local state} phenomenon, where state +modifications are local to each strand of nondeterminism, that is each +strand maintains its own copy of the state. Local state is also known +as `backtrackable state' in the literature~\cite{GibbonsH11}, because +returning back to a branch point restores the state as it were prior +to the branch. In contrast, post-composing state with nondeterminism +results in a \emph{global state} interpretation, where the state is +shared across every strand of nondeterminism. In terms of backtracking +this means the original state does not get restored upon a return to +some branch point. + +For modelling the file system we opt for the global state +interpretation such that changes made to file system are visible to +all processes. The local state interpretation could prove useful if we +were to model a virtual file system per process such that each process +would have its own unique standard out file. + +The two state phenomena are inter-encodable. \citet{PauwelsSM19} give +a systematic behaviour-preserving transformation for nondeterminism +with local state into nondeterminism with global state and vice versa. + + +\subsubsection{Basic serial file system} % -\[ - \sembr{\J} \defas (\lambda k.\lambda f.\lambda x.\Continue\;k\,(f\,x))\,(\JI) -\] +\begin{figure}[t] + \centering + \begin{tabular}[t]{| l |} + \hline + \multicolumn{1}{| c |}{\textbf{Directory}} \\ + \hline + \strlit{hamlet}\tikzmark{hamlet}\\ + \hline + \strlit{ritchie.txt}\tikzmark{ritchie}\\ + \hline + \multicolumn{1}{| c |}{$\vdots$}\\ + \hline + \strlit{stdout}\tikzmark{stdout}\\ + \hline + \multicolumn{1}{| c |}{$\vdots$}\\ + \hline + \strlit{act3}\tikzmark{act3}\\ + \hline + \end{tabular} + \hspace{1.5cm} + \begin{tabular}[t]{| c |} + \hline + \multicolumn{1}{| c |}{\textbf{I-List}} \\ + \hline + 1\tikzmark{ritchieino}\\ + \hline + 2\tikzmark{hamletino}\\ + \hline + \multicolumn{1}{| c |}{$\vdots$}\\ + \hline + 1\tikzmark{stdoutino}\\ + \hline + \end{tabular} + \hspace{1.5cm} + \begin{tabular}[t]{| l |} + \hline + \multicolumn{1}{| c |}{\textbf{Data region}} \\ + \hline + \tikzmark{stdoutdr}\strlit{}\\ + \hline + \tikzmark{hamletdr}\strlit{To be, or not to be...}\\ + \hline + \multicolumn{1}{| c |}{$\vdots$}\\ + \hline + \tikzmark{ritchiedr}\strlit{UNIX is basically...}\\ + \hline + \end{tabular} + %% Hamlet arrows. + \tikz[remember picture,overlay]\draw[->,thick,out=30,in=160] ([xshift=1.23cm,yshift=0.1cm]pic cs:hamlet) to ([xshift=-0.85cm,yshift=0.1cm]pic cs:hamletino) node[] {}; + \tikz[remember picture,overlay]\draw[->,thick,out=30,in=180] ([xshift=0.62cm,yshift=0.1cm]pic cs:hamletino) to ([xshift=-0.23cm,yshift=0.1cm]pic cs:hamletdr) node[] {}; + %% Ritchie arrows. + \tikz[remember picture,overlay]\draw[->,thick,out=-30,in=180] ([xshift=0.22cm,yshift=0.1cm]pic cs:ritchie) to ([xshift=-0.85cm,yshift=0.1cm]pic cs:ritchieino) node[] {}; + \tikz[remember picture,overlay]\draw[->,thick,out=30,in=180] ([xshift=0.62cm,yshift=0.1cm]pic cs:ritchieino) to ([xshift=-0.23cm,yshift=0.1cm]pic cs:ritchiedr) node[] {}; + %% Act3 arrow. + \tikz[remember picture,overlay]\draw[->,thick,out=10,in=210] ([xshift=1.64cm,yshift=0.1cm]pic cs:act3) to ([xshift=-0.85cm,yshift=-0.5mm]pic cs:hamletino) node[] {}; + %% Stdout arrows. + \tikz[remember picture,overlay]\draw[->,thick,out=30,in=180] ([xshift=1.23cm,yshift=0.1cm]pic cs:stdout) to ([xshift=-0.85cm,yshift=0.1cm]pic cs:stdoutino) node[] {}; + \tikz[remember picture,overlay]\draw[->,thick,out=30,in=180] ([xshift=0.62cm,yshift=0.1cm]pic cs:stdoutino) to ([xshift=-0.23cm,yshift=0.1cm]pic cs:stdoutdr) node[] {}; + \caption{\UNIX{} directory, i-list, and data region mappings.}\label{fig:unix-mappings} +\end{figure} % -The term $\JI$ captures the caller continuation, which gets bound to -$k$. The shape of the residual term is as expected: when $\sembr{\J}$ -is applied to a function, it returns another function, which when -applied ultimately invokes the captured continuation. +A file system provide an abstraction over storage media in a computer +system by organising the storage space into a collection of files. +This abstraction facilities typical file operations: allocation, +deletion, reading, and writing. % -% Strictly speaking in our setting this encoding is not faithful, -% because we do not treat continuations as first-class functions, -% meaning the types are not going to match up. An application of the -% left hand side returns a continuation object, whereas an application -% of the right hand side returns a continuation function. +\UNIX{} dogmatises the notion of file to the point where +\emph{everything is a file}. A typical \UNIX{}-style file system +differentiates between ordinary files, directory files, and special +files~\cite{RitchieT74}. An ordinary file is a sequence of +characters. A directory file is a container for all kinds of files. A +special file is an interface for interacting with an i/o device. -Let us end by remarking that the J operator is expressive enough to -encode a familiar control operator like $\Callcc$~\cite{Thielecke98}. -% -\[ - \sembr{\Callcc} \defas \lambda f. f\,\JI -\] +We will implement a \emph{basic serial file system}, which we dub +\fsname{}. % -\citet{Felleisen87b} has shown that the J operator can be -syntactically embedded using callcc. +It will be basic in the sense that it models the bare minimum to pass +as a file system, that is we will implement support for the four basic +operations: file allocation, file deletion, file reading, and file +writing. % -\[ - \sembr{\lambda x.M} \defas \lambda x.\Callcc\,(\lambda k.\sembr{M}[\J \mapsto \lambda f.\lambda y. k~(f\,y)]) -\] +The read and write operations will be serial, meaning every file is +read in order from its first character to its last character, and +every file is written to by appending the new content. % -The key point here is that $\lambda$-abstractions are not translated -homomorphically. The occurrence of $\Callcc$ immediately under the -binder reifies the current continuation of the function, which is the -precisely the caller continuation in the body $M$. In $M$ the symbol -$\J$ is substituted with a function that simulates $\J$ by -post-composing the captured continuation with the function argument -provided to $\J$. +\fsname{} will only contain ordinary files, and as a result +the file hierarchy will be entirely flat. Although, the system can +readily be extended to be hierarchical, it comes at the expense of +extra complexity, that blurs rather than illuminates the model. -\subsection{Delimited control operators} -% -The main problem with undelimited control is that it is the -programmatic embodiment of the proverb \emph{all or nothing} in the -sense that an undelimited continuation always represent the entire -residual program from its point of capture. In its basic form -undelimited control does not offer the flexibility to reify only some -segments of the evaluation context. +\paragraph{Directory, i-list, and data region} % -Delimited control rectifies this problem by associating each control -operator with a control delimiter such that designated segments of the -evaluation context can be captured individually without interfering -with the context beyond the delimiter. This provides a powerful and -modular programmatic tool that enables programmers to isolate the -control flow of specific parts of their programs, and thus enables -local reasoning about the behaviour of control infused program -segments. +A storage medium is an array of bytes. An \UNIX{} file system is +implemented on top of this array by interpreting certain intervals of +the array differently. These intervals provide the space for the +essential administrative structures for file organisation. % -One may argue that delimited control to an extent is more first-class -than undelimited control, because, in contrast to undelimited control, -it provides more fine-grain control over the evaluation context. +\begin{enumerate} + \item The \emph{directory} is a collection of human-readable names for + files. In general, a file may have multiple names. Each name is + stored along with a pointer into the i-list. + \item The \emph{i-list} is a collection of i-nodes. Each i-node + contains the meta data for a file along with a pointer into the data + region. + \item The \emph{data region} contains the actual file contents. +\end{enumerate} % -% Essentially, delimited control adds the excluded middle: \emph{all, -% some, or nothing}. - -In 1988 \citeauthor{Felleisen88} introduced the first control -delimiter known as `prompt', as a companion to the composable control -operator F (alias control)~\cite{Felleisen88}. +These structures make up the \fsname{}. % -\citeauthor{Felleisen88}'s line of work was driven by a dynamic -interpretation of composable continuations in terms of algebraic -manipulation of control component of abstract machines. In the -context of abstract machines, a continuation is defined as a sequence -of frames, whose end is denoted by a prompt, and continuation -composition is concatenation of their -sequences~\cite{Felleisen87,FelleisenF86,FelleisenWFD88}. +Figure~\ref{fig:unix-mappings} depicts an example with the three +structures and a mapping between them. % -The natural outcome of this interpretation is the control phenomenon -known as \emph{dynamic delimited control}, where the control operator -is dynamically bound by its delimiter. An application of a control -operator causes the machine to scour through control component to -locate the corresponding delimiter. - -The following year, \citet{DanvyF89} introduced an alternative pair of -operators known as `shift' and `reset', where `shift' is the control -operator and `reset' is the control delimiter. Their line of work were -driven by a static interpretation of composable continuations in terms -of continuation passing style (CPS). In ordinary CPS a continuation is -represented as a function, however, there is no notion of composition, -because every function call must appear in tail position. The `shift' -operator enables composition of continuation functions as it provides -a means for abstracting over control contexts. Technically, this works -by iterating the CPS transform twice on the source program, where -`shift' provides access to continuations that arise from the second -transformation. The `reset' operator acts as the identity for -continuation functions, which effectively delimits the extent of -`shift' as in terms of CPS the identity function denotes the top-level -continuation. +The only file meta data tracked by \fsname{} is the number of names for +a file. % -This interpretation of composable continuations as functions naturally -leads to the control phenomenon known as \emph{static delimited - control}, where the control operator is statically bound by its -delimiter. - -The machine interpretation and continuation passing style -interpretation of composable continuations were eventually connected -through defunctionalisation and refunctionalisation in a line of work -by \citeauthor{Danvy04a} and -collaborators~\cite{DanvyN01,AgerBDM03,Danvy04,AgerDM04,Danvy04a,AgerDM05,DanvyM09}. - - -% The following year, \citet{DanvyF89} introduced an alternative pair of -% operators known as `shift' and `reset', where `shift' is the control -% operator and `reset' is the control delimiter. Their line of work were -% driven by a static interpretation of composable continuations in terms -% of algebraic manipulation of continuations arising from hierarchical -% continuation passing style (CPS) transformations. In ordinary CPS a -% continuation is represented as a function, which is abortive rather -% than composable, because every function application appear in tail -% position. -% % -% The operators `shift' and `reset' were introduced as a programmatic -% way to manipulate and compose continuations. Algebraically `shift' -% corresponds to the composition operation for continuation functions, -% whereas `reset' corresponds to the identity -% element~\cite{DanvyF89,DanvyF90,DanvyF92}. -% % -% Technically, the operators operate on a meta layer, which is obtained -% by CPS transforming the image again. An indefinite amount of meta -% layers can be obtained by iterating the CPS transformation on its -% image, leading to a whole hierarchy of CPS. -% % -% % -% \dhil{Consider dropping the blurb about hierarchy/meta layers.} - -Since control/prompt and shift/reset a whole variety of alternative -delimited control operators has appeared. - -% Delimited control: Control delimiters form the basis for delimited -% control. \citeauthor{Felleisen88} introduced control delimiters in -% 1988, although allusions to control delimiters were made a year -% earlier by \citet{FelleisenFDM87} and in \citeauthor{Felleisen87}'s -% PhD dissertation~\cite{Felleisen87}. The basic idea was teased even -% earlier in \citeauthor{Talcott85}'s teased the idea of control -% delimiters in her PhD dissertation~\cite{Talcott85}. -% % -% Common Lisp resumable exceptions (condition system)~\cite{Steele90}, -% F~\cite{FelleisenFDM87,Felleisen88}, control/prompt~\cite{SitaramF90}, -% shift/reset~\cite{DanvyF89,DanvyF90}, splitter~\cite{QueinnecS91}, -% fcontrol~\cite{Sitaram93}, catchcont~\cite{LongleyW08}, effect -% handlers~\cite{PlotkinP09}. - -% Comparison of various delimited control -% operators~\cite{Shan04}. Simulation of delimited control using -% undelimited control~\cite{Filinski94} - -\paragraph{\citeauthor{Felleisen88}'s control and prompt} +The three structures and their mappings can be implemented using +association lists. Although, a better practical choice may be a +functional map or functional array~\cite{Okasaki99}, association lists +have the advantage of having a simple, straightforward implementation. % -Control and prompt were introduced by \citeauthor{Felleisen88} in -1988~\cite{Felleisen88}. The control operator `control' is a -rebranding of the F operator. Although, the name `control' was first -introduced a little later by \citet{SitaramF90}. A prompt acts as a -control-flow barrier that delimits different parts of a program, -enabling programmers to manipulate and reason about control locally in -different parts of a program. The name `prompt' is intended to draw -connections to shell prompts, and how they act as barriers between the -user and operating system. +\[ + \ba{@{~}l@{\qquad}c@{~}l} + \Directory \defas \List\,\Record{\String;\Int} &&% + \DataRegion \defas \List\,\Record{\Int;\UFile} \smallskip\\ + \INode \defas \Record{lno:\Int;loc:\Int} &&% + \IList \defas \List\,\Record{\Int;\INode} + \ea +\] % - -In this presentation both control and prompt appear as computation -forms. +Mathematically, we may think the type $\dec{Directory}$ as denoting a +partial function $\C^\ast \pto \Z$, where $\C$ is a suitable +alphabet. The function produces an index into the i-list. % -\begin{syntax} - &M,W \in \CompCat &::=& \cdots \mid \Control~k.M \mid \Prompt~M -\end{syntax} +Similarly, the type $\dec{IList}$ denotes a partial function +$\Z \pto \Z \times \Z$, where the codomain is the denotation of +$\dec{INode}$. The first component of the pair is the number of names +linked to the i-node, and as such $\Z$ is really an overapproximation +as an i-node cannot have a negative number of names. The second +component is an index into the data region. % -The $\Control~k.M$ expression reifies the context up to the nearest, -dynamically determined, enclosing prompt and binds it to $k$ inside of -$M$. A prompt is written using the sharp ($\Prompt$) symbol. +The denotation of the type $\dec{DataRegion}$ is another partial +function $\Z \pto \C^\ast$. + +We define the type of the file system to be a record of the three +association lists along with two counters for the next available index +into the data region and i-list, respectively. % -The prompt remains in place after the reification, and thus any -subsequent application of $\Control$ will be delimited by the same -prompt. +\[ + \FileSystem \defas \Record{ + \ba[t]{@{}l} + dir:\Directory;ilist:\IList;dreg:\DataRegion;\\ + dnext:\Int;inext:\Int} + \ea +\] % -Presenting $\Control$ as a binding form may conceal the fact that it -is same as $\FelleisenF$. However, the presentation here is close to -\citeauthor{SitaramF90}'s presentation, which in turn is close to -actual implementations of $\Control$. - -The static semantics of control and prompt were absent in -\citeauthor{Felleisen88}'s original treatment. Later, -\citet{KameyamaY08} have given a polymorphic type system with answer -type modifications for control and prompt (we will discuss answer type -modification when discussing shift/reset). It is also worth mentioning -that \citet{DybvigJS07} present a typed embedding of control and -prompts in Haskell (actually, they present an entire general monadic -framework for implementing control operators based on the idea of -\emph{multi-prompts}, which are a slight generalisation of prompts --- -we will revisit multi-prompts when we discuss splitter and cupto). +We can then give an implementation of the initial state of the file +system. % -% \dhil{Mention Yonezawa and Kameyama's type system.} -% % -% \citet{DybvigJS07} gave a typed embedding of multi-prompts in -% Haskell. In the multi-prompt setting the prompts are named and an -% instance of $\Control$ is indexed by the prompt name of its designated -% delimiter. - -% Typing them, particularly using a simple type system, -% affect their expressivity, because the type of the continuation object -% produced by $\Control$ must be compatible with the type of its nearest -% enclosing prompt -- this type is often called the \emph{answer} type -% (this terminology is adopted from typed continuation passing style -% transforms, where the codomain of every function is transformed to -% yield the type of whatever answer the entire program -% yields~\cite{MeyerW85}). -% % -% \dhil{Give intuition for why soundness requires the answer type to be fixed.} -% % - -% In the static semantics we extend the typing judgement relation to -% contain an up front fixed answer type $A$. -% % -% \begin{mathpar} -% \inferrule* -% {\typ{\Gamma;A}{M : A}} -% {\typ{\Gamma;A}{\Prompt~M : A}} +\[ + \dec{fs}_0 \defas \Record{ + \ba[t]{@{}l} + dir=[\Record{\strlit{stdout};0}];ilist=[\Record{0;\Record{lno=1;loc=0}}];dreg=[\Record{0;\strlit{}}];\\ + dnext=1;inext=1} + \ea +\] +% +Initially the file system contains a single, empty file with the name +$\texttt{stdout}$. Next we will implement the basic operations on the +file system separately. -% \inferrule* -% {~} -% {\typ{\Gamma;A}{\Control : (\Cont\,\Record{A;A} \to A) \to A}} -% \end{mathpar} -% % -% A prompt has the same type as its computation constituent, which in -% turn must have the same type as fixed answer type. -% % -% Similarly, the type of $\Control$ is governed by the fixed answer -% type. Discarding the answer type reveals that $\Control$ has the same -% typing judgement as $\FelleisenF$. -% % +We have made a gross simplification here, as a typical file system +would provide some \emph{file descriptor} abstraction for managing +access open files. In \fsname{} we will operate directly on i-nodes, +meaning we define $\UFD \defas \Int$, meaning the file open operation +will return an i-node identifier. As consequence it does not matter +whether a file is closed after use as file closing would be a no-op +(closing a file does not change the state of its i-node). Therefore +\fsname{} will not provide a close operation. As a further consequence +the file system will have no resource leakage. -The dynamic semantics for control and prompt consist of three rules: -1) handle return through a prompt, 2) continuation capture, and 3) -continuation invocation. +\paragraph{File reading and writing} % -\begin{reductions} - \slab{Value} & - \Prompt~V &\reducesto& V\\ - \slab{Capture} & - \Prompt~\EC[\Control~k.M] &\reducesto& \Prompt~M[\qq{\cont_{\EC}}/k], \text{ where $\EC$ contains no \Prompt}\\ - \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V] -\end{reductions} +Let us begin by giving a semantics to file reading and writing. We +need an abstract operation for each file operation. % -The \slab{Value} rule accounts for the case when the computation -constituent of $\Prompt$ has been reduced to a value, in which case -the prompt is removed and the value is returned. +\[ + \dec{FileRW} \defas \{\URead : \Int \opto \Option~\String;\UWrite : \Record{\Int;\String} \opto \UnitType\} +\] % -The \slab{Capture} rule states that an application of $\Control$ -captures the current continuation up to the nearest enclosing -prompt. The current continuation (up to the nearest prompt) is also -aborted. If we erase $\Prompt$ from the rule, then it is clear that -$\Control$ has the same dynamic behaviour as $\FelleisenF$. +The operation $\URead$ is parameterised by an i-node number +(i.e. index into the i-list) and possibly returns the contents of the +file pointed to by the i-node. The operation may fail if it is +provided with a stale i-node number. Thus the option type is used to +signal failure or success to the caller. % -It is evident from the \slab{Resume} rule that control and prompt are -an instance of a dynamic control operator, because resuming the -continuation object produced by $\Control$ does not install a new -prompt. - -To illustrate $\Prompt$ and $\Control$ in action, let us consider a -few simple examples. +The $\UWrite$ operation is parameterised by an i-node number and some +strings to be appended onto the file pointed to by the i-node. The +operation returns unit, and thus the operation does not signal to its +caller whether it failed or succeed. % -\begin{derivation} - & 1 + \Prompt~2 + (\Control~k.3 + k~0) + (\Control~k'.k'~4)\\ - \reducesto^+& \reason{Capture $\EC = 2 + [\,] + (\Control~k'.k'~4)$}\\ - & 1 + \Prompt~3+\Continue~\cont_{\EC}~0\\ - \reducesto & \reason{Resume with 0}\\ - & 1 + \Prompt~3 + (2 + 0) + (\Control~k'.k'~4)\\ - \reducesto^+ & \reason{Capture $\EC' = 5 + [\,]$}\\ - & 1 + \Prompt~\Continue~\cont_{\EC'}~4\\ - \reducesto^+ & \reason{Resume with 4}\\ - & 1 + \Prompt~5 + 4\\ - \reducesto^+ & \reason{\slab{Value} rule}\\ - & 1 + 9 \reducesto 10 -\end{derivation} +Before we implement a handler for the operations, we will implement +primitive read and write operations that operate directly on the file +system. We will use the primitive operations to implement the +semantics for $\URead$ and $\UWrite$. To implement the primitive the +operations we will need two basic functions on association lists. I +will only their signatures here. % -The continuation captured by the either application of $\Control$ is -oblivious to the continuation $1 + [\,]$ of $\Prompt$. Since the -captured continuation is composable it returns to its call site. The -invocation of the captured continuation $k$ returns the value 0, but -splices the captured context into the context $3 + [\,]$. The second -application of $\Control$ captures the new context up to the -delimiter. The continuation is immediately applied to the value 4, -which causes the captured context to be reinstated with the value 4 -plugged in. Ultimately the delimited context reduces to the value $9$, -after which the prompt $\Prompt$ gets eliminated, and the continuation -of the $\Prompt$ is applied to the value $9$, resulting in the final -result $10$. - -Let us consider a slight variation of the previous example. +\[ + \bl + \lookup : \Record{\alpha;\List\,\Record{\alpha;\beta}} \to \beta \eff \{\Fail : \UnitType \opto \ZeroType\} \smallskip\\ + \modify : \Record{\alpha;\beta;\List\,\Record{\alpha;\beta}} \to \Record{\alpha;\beta} + \el +\] % -\begin{derivation} - & 1 + \Prompt~2 + (\Control~k.3 + k~0) + (\Control~k'.4)\\ - \reducesto^+& \reason{Capture $\EC = 2 + [\,] + (\Control~k'.4)$}\\ - & 1 + \Prompt~3+\Continue~\cont_{\EC}~0\\ - \reducesto & \reason{Resume with 0}\\ - & 1 + \Prompt~3 + (2 + 0) + (\Control~k'.4)\\ - \reducesto^+ & \reason{Capture $\EC' = 5 + [\,]$}\\ - & 1 + \Prompt~4\\ - \reducesto^+ & \reason{\slab{Value} rule}\\ - & 1 + 4 \reducesto 5 -\end{derivation} +Given a key of type $\alpha$ the $\lookup$ function returns the +corresponding value of type $\beta$ in the given association list. If +the key does not exists, then the function invokes the $\Fail$ +operation to signal failure. % -Here the computation constituent of the second application of -$\Control$ drops the captured continuation, which has the effect of -erasing the previous computation, ultimately resulting in the value -$5$ rather than $10$. -% \begin{derivation} -% & 1 + \Prompt~2 + (\Control~k.\Continue~k~0) + (\Control~k'. 0)\\ -% \reducesto^+& \reason{Capture $\EC = 2 + [\,] + (\Control~k'.0)$}\\ -% & 1 + \Prompt~\Continue~\cont_{\EC}~0\\ -% \reducesto & \reason{Resume with 0}\\ -% & 1 + \Prompt~2 + 0 + (\Control~k'. 0)\\ -% \reducesto^+ & \reason{Capture $\EC' = 2 + [\,]$}\\ -% & 1 + \Prompt~0 \\ -% \reducesto & \reason{\slab{Value} rule}\\ -% & 1 + 0 \reducesto 1 -% \end{derivation} +The $\modify$ function takes a key and a value. If the key exists in +the provided association list, then it replaces the value bound by the +key with the provided value. % -The continuation captured by the first application of $\Control$ -contains another application of $\Control$. The application of the -continuation immediate reinstates the captured context filling the -hole left by the first instance of $\Control$ with the value $0$. The -second application of $\Control$ captures the remainder of the -computation of to $\Prompt$. However, the captured context gets -discarded, because the continuation $k'$ is never invoked. +Using these functions we can implement the primitive read and write +operations. % - -A slight variation on control and prompt is $\Controlz$ and -$\Promptz$~\cite{Shan04}. The main difference is that $\Controlz$ -removes its corresponding prompt, i.e. +\[ + \bl + \fread : \Record{\Int;\FileSystem} \to \String \eff \{\Fail : \UnitType \opto \ZeroType\}\\ + \fread\,\Record{ino;fs} \defas + \ba[t]{@{}l} + \Let\;inode \revto \lookup\,\Record{ino; fs.ilist}\;\In\\ + \lookup\,\Record{inode.loc; fs.dreg} + \el + \el +\] % -\begin{reductions} - % \slab{Value} & - % \Prompt~V &\reducesto& V\\ - \slab{Capture_0} & - \Promptz~\EC[\Controlz~k.M] &\reducesto& M[\qq{\cont_{\EC}}/k], \text{ where $\EC$ contains no \Promptz}\\ - % \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V] -\end{reductions} +The function $\fread$ takes as input the i-node number for the file to +be read and a file system. First it looks up the i-node structure in +the i-list, and then it uses the location in the i-node to look up the +file contents in the data region. Since $\fread$ performs no exception +handling it will fail if either look up fails. The implementation of +the primitive write operation is similar. % -Higher-order programming with control and prompt (and delimited -control in general) is fragile, because the body of a higher-order -function may inadvertently trap instances of control in its functional -arguments. +\[ + \bl + \fwrite : \Record{\Int;\String;\FileSystem} \to \FileSystem \eff \{\Fail : \UnitType \opto \ZeroType\}\\ + \fwrite\,\Record{ino;cs;fs} \defas + \ba[t]{@{}l} + \Let\;inode \revto \lookup\,\Record{ino; fs.ilist}\;\In\\ + \Let\;file \revto \lookup\,\Record{inode.loc; fs.dreg}\;\In\\ + \Record{\,fs\;\keyw{with}\;dreg = \modify\,\Record{inode.loc;file \concat cs;fs}} + \el + \el +\] % -This observation led \citet{SitaramF90} to define an indexed family of -control and prompt pairs such that instances of control and prompt can -be layered on top of one another. The idea is that the index on each -pair denotes their level $i$ such that $\Control^i$ matches -$\Prompt^i$ and may capture any other instances of $\Prompt^j$ where -$j < i$. -% \dhil{Mention control0/prompt0 and -% the control hierarchy} - -\paragraph{\citeauthor{DanvyF90}'s shift and reset} Shift and reset -first appeared in a technical report by \citeauthor{DanvyF89} in -1989. Although, perhaps the most widely known account of shift and -reset appeared in \citeauthor{DanvyF90}'s seminal work on abstracting -control the following year~\cite{DanvyF90}. +The first two lines grab hold of the file, whilst the last line +updates the data region in file system by appending the string $cs$ +onto the file. % -Shift and reset differ from control and prompt in that the contexts -abstracted by shift are statically scoped by reset. - -% As with control and prompt, in our setting, shift appears as a value, -% whilst reset appear as a computation. -In our setting both shift and reset appear as computation forms. +Before we can implement the handler, we need an exception handling +mechanism. The following exception handler interprets $\Fail$ as some +default value. % -\begin{syntax} - % & V, W &::=& \cdots \mid \shift\\ - & M, N \in \CompCat &::=& \cdots \mid \shift\; k.M \mid \reset{M} -\end{syntax} +\[ + \bl + \faild : \Record{\alpha;\UnitType \to \alpha \eff \{\Fail : \UnitType \opto \ZeroType\}} \to \alpha\\ + \faild\,\Record{default;m} \defas + \ba[t]{@{~}l} + \Handle\;m~\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& x\\ + \OpCase{\Fail}{\Unit}{\_} &\mapsto& default + \ea + \ea + \el +\] % -The $\shift$ construct captures the continuation delimited by an -enclosing $\reset{-}$ and binds it to $k$ in the computation $M$. - -\citeauthor{DanvyF89}'s original development of shift and reset stands -out from the previous developments of control operators, as they -presented a type system for shift and reset, whereas previous control -operators were originally studied in untyped settings. +The $\Fail$-case is simply the default value, whilst the +$\Return$-case is the identity. % -The standard inference-based approach to type -checking~\cite{Plotkin81,Plotkin04a} is inadequate for type checking -shift and reset, because shift may alter the \emph{answer type} of the -expression (the terminology `answer type' is adopted from typed -continuation passing style transforms, where the codomain of every -function is transformed to yield the type of whatever answer the -entire program yields~\cite{MeyerW85}). +Now we can use all the above pieces to implement a handler for the +$\URead$ and $\UWrite$ operations. % -To capture the potent power of shift in the type system they -introduced the notion of \emph{answer type - modification}~\cite{DanvyF89}. +\[ + \bl + \fileRW : (\UnitType \to \alpha \eff \dec{FileRW}) \to \alpha \eff \State~\FileSystem\\ + \fileRW~m \defas + \ba[t]{@{}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& res\\ + \OpCase{\URead}{ino}{resume} &\mapsto& + \bl + \Let\;cs\revto \faild\,\Record{\None;\lambda\Unit.\\ + \quad\Some\,(\fread\,\Record{ino;\Uget\,\Unit})}\\ + \In\;resume~cs + \el\\ + \OpCase{\UWrite}{\Record{ino;cs}}{resume} &\mapsto& + \ba[t]{@{}l} + \faild~\Record{\Unit; \lambda \Unit.\\ + \quad\bl + \Let\;fs \revto \fwrite\,\Record{ino;cs;\Uget\,\Unit}\\ + \In\;\Uput~fs};\,resume\,\Unit + \el + \ea + \ea + \ea + \el +\] % -The addition of answer type modification changes type judgement to be -a five place relation. +The $\URead$-case uses the $\fread$ function to implement reading a +file. The file system state is retrieved using the state operation +$\Uget$. The possible failure of $\fread$ is dealt with by the +$\faild$ handler by interpreting failure as $\None$. +% +The $\UWrite$-case makes use of the $\fwrite$ function to implement +writing to a file. Again the file system state is retrieved using +$\Uget$. The $\Uput$ operation is used to update the file system state +with the state produced by the successful invocation of +$\fwrite$. Failure is interpreted as unit, meaning that from the +caller's perspective the operation fails silently. + +\paragraph{File creation and opening} +The signature of file creation and opening is unsurprisingly comprised +of two operations. % \[ - \typ{\Gamma;B}{M : A; B'} + \dec{FileCO} \defas \{\UCreate : \String \opto \Option~\Int; \UOpen : \String \opto \Option~\Int\} \] % -This would be read as: in a context $\Gamma$ where the original result -type was $B$, the type of $M$ is $A$, and modifies the result type to -$B'$. In this system the typing rule for $\shift$ is as follows. -% -\begin{mathpar} - \inferrule* - {\typ{\Gamma,k : A / C \to B / C;D}{M : D;B'}} - {\typ{\Gamma;B}{\shift\;k.M : A;B'}} -\end{mathpar} -% -Here the function type constructor $-/- \to -/-$ has been endowed with -the domain and codomain of the continuation. The left hand side of -$\to$ contains the domain type of the function and the codomain of the -continuation, respectively. The right hand side contains the domain of -the continuation and the codomain of the function, respectively. - -Answer type modification is a powerful feature that can be used to -type embedded languages, an illustrious application of this is -\citeauthor{Danvy98}'s typed $\dec{printf}$~\cite{Danvy98}. A -polymorphic extension of answer type modification has been -investigated by \citet{AsaiK07}, \citet{KiselyovS07} developed a -substructural type system with answer type modification, whilst -\citet{KoboriKK16} demonstrated how to translate from a source -language with answer type modification into a system without using -typed multi-prompts. - -Differences between shift/reset and control/prompt manifest in the -dynamic semantics as well. -% -\begin{reductions} - \slab{Value} & \reset{V} &\reducesto& V\\ - \slab{Capture} & \reset{\EC[\shift\;k.M]} &\reducesto& \reset{M[\qq{\cont_{\reset{\EC}}}/k]}, \text { where $\EC$ contains no $\reset{-}$}\\ - % \slab{Resume} & \reset{\EC[\Continue~\cont_{\reset{\EC'}}~V]} &\reducesto& \reset{\EC[\reset{\EC'[V]}]}\\ - \slab{Resume} & \Continue~\cont_{\reset{\EC}}~V &\reducesto& \reset{\EC[V]}\\ -\end{reductions} -% -The key difference between \citeauthor{Felleisen88}'s control/prompt -and shift/reset is that the $\slab{Capture}$ rule for the latter -includes a copy of the delimiter in the reified continuation. This -delimiter gets installed along with the captured context $\EC$ when -the continuation object is resumed. The extra reset has ramifications -for the operational behaviour of subsequent occurrences of $\shift$ in -$\EC$. To put this into perspective, let us revisit the second -control/prompt example with shift/reset instead. -% -\begin{derivation} - & 1 + \reset{2 + (\shift\;k.3 + k\,0) + (\shift\;k'.4)}\\ - \reducesto^+& \reason{Capture $\EC = 2 + [\,] + (\shift\;k.4)$}\\ - & 1 + \reset{\Continue~\cont_{\EC}~0}\\ - \reducesto & \reason{Resume with 0}\\ - & 1 + \reset{3 + \reset{2 + 0 + (\shift\;k'. 4)}}\\ - \reducesto^+ & \reason{Capture $\EC' = 2 + [\,]$}\\ - & 1 + \reset{3 + \reset{4}} \\ - \reducesto^+ & \reason{\slab{Value} rule}\\ - & 1 + \reset{7} \reducesto^+ 8 \\ -\end{derivation} +The implementation of file creation and opening follows the same +pattern as the implementation of reading and writing. As before, we +implement a primitive routine for each operation that interacts +directly with the file system structure. We first implement the +primitive file opening function as the file creation function depends +on this function. % -Contrast this result with the result $5$ obtained when using -control/prompt. In essence the insertion of a new reset after -resumption has the effect of remembering the local context of the -previous continuation invocation. - -This difference naturally raises the question whether shift/reset and -control/prompt are interdefinable or exhibit essential expressivity -differences. \citet{Shan04} answered this question demonstrating that -shift/reset and control/prompt are macro-expressible. The translations -are too intricate to be reproduced here, however, it is worth noting -that \citeauthor{Shan04} were working in the untyped setting of Scheme -and the translation of control/prompt made use of recursive -continuations. \citet{BiernackiDS05} typed and reimplemented this -translation in Standard ML New Jersey~\cite{AppelM91}, using -\citeauthor{Filinski94}'s encoding of shift/reset in terms of callcc -and state~\cite{Filinski94}. +\[ + \bl + \fopen : \Record{\String;\FileSystem} \to \Int \eff \{\Fail : \UnitType \opto \ZeroType\}\\ + \fopen\,\Record{fname;fs} \defas \lookup\,\Record{fname; fs.dir} + \el +\] % +Opening a file in the file system simply corresponds to returning the +i-node index associated with the filename in the directory table. -As with control and prompt there exist various variation of shift and -reset. \citet{DanvyF89} also considered $\shiftz$ and -$\resetz{-}$. The operational difference between $\shiftz$/$\resetz{-}$ -and $\shift$/$\reset{-}$ manifests in the capture rule. +The \UNIX{} file create command does one of two things depending on +the state of the file system. If the create command is provided with +the name of a file that is already present in the directory, then the +system truncates the file, and returns the file descriptor for the +file. Otherwise the system allocates a new empty file and returns its +file descriptor~\cite{RitchieT74}. To check whether a file already +exists in the directory we need a function $\dec{has}$ that given a +filename and the file system state returns whether there exists a file +with the given name. This function can be built completely generically +from the functions we already have at our disposal. % -\begin{reductions} - \slab{Capture_0} & \resetz{\EC[\shiftz\,k.M]} &\reducesto& M[\qq{\cont_{\resetz{\EC}}}/k], \text { where $\EC$ contains no $\resetz{-}$}\\ -\end{reductions} +\[ + \bl + \dec{has} : \Record{\alpha;\List\,\Record{\alpha;\beta}} \to \Bool\\ + \dec{has}\,\Record{k;xs} \defas \faild\,\Record{\False;(\lambda\Unit.\lookup\,\Record{k;xs};\True)} + \el +\] % -The control reifier captures the continuation up to and including its -delimiter, however, unlike $\shift$, it removes the control delimiter -from the current evaluation context. Thus $\shiftz$/$\resetz{-}$ are -`dynamic' variations on $\shift$/$\reset{-}$. \citet{MaterzokB12} -introduced $\dollarz{-}{-}$ (pronounced ``dollar0'') as an -alternative control delimiter for $\shiftz$. -\begin{reductions} - \slab{Value_{\$_0}} & \dollarz{x.N}{V} &\reducesto& N[V/x]\\ - \slab{Capture_{\$_0}} & \dollarz{x.N}{\EC[\shiftz\,k.M]} &\reducesto& M[\qq{\cont_{(\dollarzh{x.N}{\EC})}}/k],\\ - &&&\quad\text{where $\EC$ contains no $\reset{-\mid-}$}\\ - \slab{Resume_{\$_0}} & \Continue~\cont_{(\dollarz{x.N}{\EC})}~V &\reducesto& \dollarz{x.N}{\EC[V]}\\ -\end{reductions} +The function $\dec{has}$ applies $\lookup$ under the failure handler +with default value $\False$. If $\lookup$ returns successfully then +its result is ignored, and the computation returns $\True$, otherwise +the computation returns the default value $\False$. % -The intuition here is that $\dollarz{x.N}{M}$ evaluates $M$ to some -value $V$ in a fresh context, and then continues as $N$ with $x$ bound -to $V$. Thus it builds in a form of ``success continuation'' that -makes it possible to post-process the result of a reset0 term. In -fact, reset0 is macro-expressible in terms of -dollar0~\cite{MaterzokB12}. +With this function we can implement the semantics of create. % \[ - \sembr{\resetz{M}} \defas \dollarz{x.x}{\sembr{M}}\\ + \bl + \fcreate : \Record{\String;\FileSystem} \to \Record{\Int;\FileSystem} \eff \{\Fail : \UnitType \opto \ZeroType\}\\ + \fcreate\,\Record{fname;fs} \defas + \ba[t]{@{}l} + \If\;\dec{has}\,\Record{fname;fs.dir}\;\Then\\ + \quad\bl + \Let\;ino \revto \fopen\,\Record{fname;fs}\;\In\\ + \Let\;inode \revto \lookup\,\Record{ino;fs}\;\In\\ + \Let\;dreg' \revto \modify\,\Record{inode.loc; \strlit{}; fs.dreg}\;\In\\ + \Record{ino;\Record{fs\;\With\;dreg = dreg'}} + \el\\ + \Else\\ + \quad\bl + \Let\;loc \revto fs.lnext \;\In\\ + \Let\;dreg \revto \Record{loc; \strlit{}} \cons fs.dreg\;\In\\ + \Let\;ino \revto fs.inext \;\In\\ + \Let\;inode \revto \Record{loc=loc;lno=1}\;\In\\ + \Let\;ilist \revto \Record{ino;inode} \cons fs.ilist \;\In\\ + \Let\;dir \revto \Record{fname; ino} \cons fs.dir \;\In\\ + \Record{ino;\Record{ + \bl + dir=dir;ilist=ilist;dreg=dreg;\\ + lnext=loc+1;inext=ino+1}} + \el + \el + \el + \el \] % -By taking the success continuation to be the identity function dollar0 -becomes operationally equivalent to reset0. As it turns out reset0 and -shift0 (together) can macro-express dollar0~\cite{MaterzokB12}. +The $\Then$-branch accounts for the case where the filename $fname$ +already exists in the directory. First we retrieve the i-node for the +file to obtain its location in the data region such that we can +truncate the file contents. % -\[ - \sembr{\dollarz{x.N}{M}} \defas (\lambda k.\resetz{(\lambda x.\shiftz~z.k~x)\,\sembr{M}})\,(\lambda x.\sembr{N}) -\] +The branch returns the i-node index along with the modified file +system. The $\Else$-branch allocates a new empty file. First we +allocate a location in the data region by copying the value of +$fs.lnext$ and consing the location and empty string onto +$fs.dreg$. The next three lines allocates the i-node for the file in a +similar fashion. The second to last line associates the filename with +the new i-node. The last line returns the identifier for the i-node +along with the modified file system, where the next location ($lnext$) +and next i-node identifier ($inext$) have been incremented. % -This translation is a little more involved. The basic idea is to first -explicit pass in the success continuation, then evaluate $M$ under a -reset to yield value which gets bound to $x$, and then subsequently -uninstall the reset by invoking $\shiftz$ and throwing away the -captured continuation, afterwards we invoke the success continuation -with the value $x$. - -% Even though the two constructs are equi-expressive (in the sense of macro-expressiveness) there are good reason for preferring dollar0 over reset0 Since the two constructs are equi-expressive, the curious reader might -% wonder why \citet{MaterzokB12} were - -% \dhil{Maybe mention the implication is that control/prompt has CPS semantics.} -% \dhil{Mention shift0/reset0, dollar0\dots} -% \begin{reductions} -% % \slab{Value} & \reset{V} &\reducesto& V\\ -% \slab{Capture} & \reset{\EC[\shift\,k.M]} &\reducesto& M[\cont_{\reset{\EC}}/k]\\ -% % \slab{Resume} & \Continue~\cont_{\reset{\EC}}~V &\reducesto& \reset{\EC[V]}\\ -% \end{reductions} +It is worth noting that the effect signature of $\fcreate$ mentions +$\Fail$ even though it will never fail. It is present in the effect +row due to the use of $\fopen$ and $\lookup$ in the +$\Then$-branch. Either application can only fail if the file system is +in an inconsistent state, where the index $ino$ has become stale. The +$\dec{f}$-family of functions have been carefully engineered to always +leave the file system in a consistent state. % -\paragraph{\citeauthor{QueinnecS91}'s splitter} The `splitter' control -operator reconciles abortive continuations and composable -continuations. It was introduced by \citet{QueinnecS91} in 1991. The -name `splitter' is derived from it operational behaviour, as an -application of `splitter' marks evaluation context in order for it to -be split into two parts, where the context outside the mark represents -the rest of computation, and the context inside the mark may be -reified into a delimited continuation. The operator supports two -operations `abort' and `calldc' to control the splitting of evaluation -contexts. The former has the effect of escaping to the outer context, -whilst the latter reifies the inner context as a delimited -continuation (the operation name is short for ``call with delimited -continuation''). - -Splitter and the two operations abort and calldc are value forms. +Now we can implement the semantics for the $\UCreate$ and $\UOpen$ +effectful operations. The implementation is similar to the +implementation of $\fileRW$. % \[ - V,W \in \ValCat ::= \cdots \mid \splitter \mid \abort \mid \calldc + \bl + \fileAlloc : (\UnitType \to \alpha \eff \dec{FileCO}) \to \alpha \eff \State~\FileSystem\\ + \fileAlloc~m \defas + \ba[t]{@{}l} + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& res\\ + \OpCase{\UCreate}{fname}{resume} &\mapsto& + \bl + \Let\;ino \revto \faild\,\Record{\None; \lambda\Unit.\\ + \quad\bl + \Let\;\Record{ino;fs} = \fcreate\,\Record{\,fname;\Uget\,\Unit}\\ + \In\;\Uput~fs;\,\Some~ino} + \el\\ + \In\; resume~ino + \el\\ + \OpCase{\UOpen}{fname}{resume} &\mapsto& + \ba[t]{@{}l} + \Let\; ino \revto \faild~\Record{\None; \lambda \Unit.\\ + \quad\Some\,(\fopen\,\Record{fname;\Uget\,\Unit})}\\ + \In\;resume~ino + \ea + \ea + \ea + \el \] % -In their treatment of splitter, \citeauthor{QueinnecS91} gave three -different presentations of splitter. The presentation that I have -opted for here is close to their second presentation, which is in -terms of multi-prompt continuations. This variation of splitter admits -a pleasant static semantics too. Thus, we further extend the syntactic -categories with the machinery for first-class prompts. + +\paragraph{Stream redirection} % -\begin{syntax} - & A,B \in \TypeCat &::=& \cdots \mid \prompttype~A \smallskip\\ - & V,W \in \ValCat &::=& \cdots \mid p\\ - & M,N \in \CompCat &::=& \cdots \mid \Prompt_V~M -\end{syntax} -% -The type $\prompttype~A$ classifies prompts whose answer type is -$A$. Prompt names are first-class values and denoted by $p$. The -computation $\Prompt_V~M$ denotes a computation $M$ delimited by a -parameterised prompt, whose value parameter $V$ is supposed to be a -prompt name. +The processes we have defined so far use the $\echo$ utility to write +to the $\stdout$ file. The target file $\stdout$ is hardwired into the +definition of $\echo$ (Section~\ref{sec:tiny-unix-bio}). To take +advantage of the capabilities of the new file system we could choose +to modify the definition of $\echo$ such that it is parameterised by +the target file. However, such a modification is a breaking +change. Instead we can define a \emph{stream redirection} operator +that allow us to redefine the target of $\Write$ operations locally. % -The static semantics of $\splitter$, $\abort$, and $\calldc$ are as -follows. +\[ + \bl + \redirect : + \bl + \Record{\UnitType \to \alpha \eff \{\Write : \Record{\Int;\String} \opto \UnitType\}; \String}\\ + \to \alpha \eff \{\UCreate : \String \opto \Option~\Int;\Exit : \Int \opto \ZeroType;\Write : \Record{\Int;\String} \opto \UnitType\} + \el\\ + m~\redirect~fname \defas + \ba[t]{@{}l} + \Let\;ino \revto \Case\;\Do\;\UCreate~fname\;\{ + \ba[t]{@{~}l@{~}c@{~}l} + \None &\mapsto& \exit~1\\ + \Some~ino &\mapsto& ino\} + \ea\\ + \In\;\Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& res\\ + \OpCase{\Write}{\Record{\_;cs}}{resume} &\mapsto& resume\,(\Do\;\Write\,\Record{ino;cs}) + \ea + \ea + \el +\] % -\begin{mathpar} - \inferrule* - {~} - {\typ{\Gamma}{\splitter : (\prompttype~A \to A) \to A}} - - \inferrule* - {~} - {\typ{\Gamma}{\abort : \prompttype~A \times (\UnitType \to A) \to B}} +The operator $\redirect$ first attempts to create a new target file +with name $fname$. If it fails it simply exits with code +$1$. Otherwise it continues with the i-node reference $ino$. The +handler overloads the definition of $\Write$ inside the provided +computation $m$. The new definition drops the i-node reference of the +initial target file and replaces it by the reference to new target +file. - \inferrule* - {~} - {\typ{\Gamma}{\calldc : \prompttype~A \times ((B \to A) \to B) \to B}} -\end{mathpar} +This stream redirection operator is slightly more general than the +original redirection operator in the original \UNIX{} environment. As +the \UNIX{} redirection operator only redirects writes targeted at the +\emph{stdout} file~\cite{RitchieT74}, whereas the above operator +redirects writes regardless of their initial target. % -In this presentation, the operator and the two operations all amount -to special higher-order function symbols. The argument to $\splitter$ -is parameterised by a prompt name. This name is injected by -$\splitter$ upon application. The operations $\abort$ and $\calldc$ -both accept as their first argument the name of the delimiting -prompt. The second argument of $\abort$ is a thunk, whilst the second -argument of $\calldc$ is a higher-order function, which accepts a -continuation as its argument. - -For the sake of completeness the prompt primitives are typed as -follows. +It is straightforward to implement this original \UNIX{} behaviour by +inspecting the first argument of $\Write$ in the operation clause +before committing to performing the redirecting $\Write$ operation. % -\begin{mathpar} - \inferrule* - {~} - {\typ{\Gamma,p:\prompttype~A}{p : \prompttype~A}} +Modern \UNIX{} environments typically provide more fine-grained +control over redirects, for example by allowing the user to specify on +a per file basis which writes should be redirected. Again, we can +implement this behaviour by comparing the provided file descriptor +with the descriptor in the payload of $\Write$. - \inferrule* - {\typ{\Gamma}{V : \prompttype~A} \\ \typ{\Gamma}{M : A}} - {\typ{\Gamma}{\Prompt_V~M : A}} -\end{mathpar} +% ([0, 0, 0], +% (dir = [("hamlet", 2), ("ritchie.txt", 1), ("stdout", 0)], +% dregion = [(2, "To be, or not to be, +% that is the question: +% Whether 'tis nobler in the mind to suffer +% "), +% (1, "UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity. +% "), ( +% 0, "")], +% inext = 3, +% inodes = [(2, (lno = 1, loc = 2)), (1, (lno = 1, loc = 1)), (0, (lno = 1, loc = 0))], +% lnext = 3)) : ([Int], FileSystem) +% links> init(fsys0, example7); +% ([0, 0, 0], +% (dir = [("hamlet", 2), ("ritchie.txt", 1), ("stdout", 0)], +% dregion = [(2, "To be, or not to be, +% that is the question: +% Whether 'tis nobler in the mind to suffer +% "), +% (1, "UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity. +% "), ( +% 0, "")], +% inext = 3, +% inodes = [(2, (lno = 1, loc = 2)), (1, (lno = 1, loc = 1)), (0, (lno = 1, loc = 0))], +% lnext = 3)) : ([Int], FileSystem) +\medskip We can plug everything together to observe the new file +system in action. % -The dynamic semantics of this presentation require a bit of -generativity in order to generate fresh prompt names. Therefore the -reduction relation is extended with an additional component to keep -track of which prompt names have already been allocated. +\[ + \ba{@{~}l@{~}l} + &\bl + \runState\,\Record{\dec{fs}_0;\fileRW\,(\lambda\Unit.\\ + \quad\fileAlloc\,(\lambda\Unit.\\ + \qquad\timeshare\,(\lambda\Unit.\\ + \qquad\quad\dec{interruptWrite}\,(\lambda\Unit.\\ + \qquad\qquad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ + \qquad\qquad\quad\status\,(\lambda\Unit. + \ba[t]{@{}l} + \If\;\fork\,\Unit\;\Then\; + \su~\Alice;\, + \quoteRitchie~\redirect~\strlit{ritchie.txt}\\ + \Else\; + \su~\Bob;\, + \quoteHamlet~\redirect~\strlit{hamlet})}))))} + \ea + \el \smallskip\\ + \reducesto^+& + \bl + \Record{ + \ba[t]{@{}l} + [0, 0];\\ + \Record{ + \ba[t]{@{}l} + dir=[\Record{\strlit{hamlet};2}, + \Record{\strlit{ritchie.txt};1}, + \Record{\strlit{stdout};0}];\\ + ilist=[\Record{2;\Record{lno=1;loc=2}}, + \Record{1;\Record{lno=1;loc=1}}, + \Record{0;\Record{lno=1;loc=0}}];\\ + dreg=[ + \ba[t]{@{}l} + \Record{2; + \ba[t]{@{}l@{}l} + \texttt{"}&\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\ + &\texttt{Whether 'tis nobler in the mind to suffer\nl{}"}}, + \ea\\ + \Record{1; + \ba[t]{@{}l@{}l} + \texttt{"}&\texttt{UNIX is basically a simple operating system, }\\ + &\texttt{but you have to be a genius to understand the simplicity.\nl{"}}}, + \ea\\ + \Record{0; \strlit{}}]; lnext=3; inext=3}}\\ + \ea + \ea + \ea\\ + : \Record{\List~\Int; \FileSystem} + \el + \ea +\] % -\begin{reductions} - \slab{AppSplitter} & \splitter~V,\rho &\reducesto& \Prompt_p~V\,p,\rho \uplus \{p\}\\ - \slab{Value} & \Prompt_p~V,\rho &\reducesto& V,\rho\\ - \slab{Abort} & \Prompt_p~\EC[\abort\,\Record{p;V}],\rho &\reducesto& V\,\Unit,\rho\\%, \quad \text{where $\EC$ contains no $\Prompt_p$}\\ - \slab{Capture} & \Prompt_p~\EC[\calldc\,\Record{p;V}] &\reducesto& V~\qq{\cont_{\EC}},\rho\\ - \slab{Resume} & \Continue~\cont_{\EC}~V,\rho &\reducesto& \EC[V],\rho -\end{reductions} +The writes of the processes $\quoteRitchie$ and $\quoteHamlet$ are now +being redirected to designated files \texttt{ritchie.txt} and +\texttt{hamlet}, respectively. The operating system returns the +completion status of all the processes along with the current state of +the file system such that it can be used as the initial file system +state on the next start of the operating system. + +\subsubsection{File linking and unlinking} % -We see by the $\slab{AppSplitter}$ rule that an application of -$\splitter$ generates a fresh named prompt, whose name is applied on -the function argument. +At this point the implementation of \fsname{} is almost feature +complete. However, we have yet to implement two dual file operations: +linking and unlinking. The former enables us to associate a new +filename with an existing i-node, thus providing a mechanism for +making soft copies of files (i.e. the file contents are +shared). The latter lets us dissociate a filename from an i-node, thus +providing a means for removing files. The interface of linking and +unlinking is given below. % -The $\slab{Value}$ rule is completely standard. +\[ + \dec{FileLU} \defas \{\ULink : \Record{\String;\String} \opto \UnitType; \UUnlink : \String \opto \UnitType\} +\] % -The $\slab{Abort}$ rule show that an invocation of $\abort$ causes the -current evaluation context $\EC$ up to and including the nearest -enclosing prompt. +The $\ULink$ operation is parameterised by two strings. The first +string is the name of the \emph{source} file and the second string is +the \emph{destination} name (i.e. the new name). The $\UUnlink$ +operation takes a single string argument, which is the name of the +file to be removed. + +As before, we bundle the low level operations on the file system state +into their own functions. We start with file linking. % -The next rule $\slab{Capture}$ show that $\calldc$ captures and aborts -the context up to the nearest enclosing prompt. The captured context -is applied on the function argument of $\calldc$. As part of the -operation the prompt is removed. % Thus, $\calldc$ behaves as a -% delimited variation of $\Callcc$. +\[ + \bl + \flink : \Record{\String;\String;\FileSystem} \to \FileSystem \eff \{\Fail : \UnitType \opto \ZeroType\}\\ + \flink\,\Record{src;dest;fs} \defas + \bl + \If\;\dec{has}\,\Record{dest;fs.dir}\;\Then\;\Absurd~\Do\;\Fail\,\Unit\\ + \Else\; + \bl + \Let\;ino \revto \lookup~\Record{src;fs.dir}\;\In\\ + \Let\;dir' \revto \Record{dest;ino} \cons fs.dir\;\In\\ + \Let\;inode \revto \lookup~\Record{ino;fs.ilist}\;\In\\ + \Let\;inode' \revto \Record{inode\;\With\;lno = inode.lno + 1}\;\In\\ + \Let\;ilist' \revto \modify\,\Record{ino;inode';fs.ilist}\;\In\\ + \Record{fs\;\With\;dir = dir';ilist = ilist'} + \el + \el + \el +\] +% +The function $\flink$ checks whether the destination filename, $dest$, +already exists in the directory. If it exists then the function raises +the $\Fail$ exception. Otherwise it looks up the index of the i-node, +$ino$, associated with the source file, $src$. Next, the directory is +extended with the destination filename, which gets associated with +this index, meaning $src$ and $dest$ both share the same +i-node. Finally, the link count of the i-node at index $ino$ gets +incremented, and the function returns the updated file system state. % -It is clear by the prompt semantics that an invocation of either -$\abort$ and $\calldc$ is only well-defined within the dynamic extent -of $\splitter$. Since the prompt is eliminated after use of either -operation subsequent operation invocations must be guarded by a new -instance of $\splitter$. - -Let us consider an example using both $\calldc$ and $\abort$. +The semantics of file unlinking is slightly more complicated as an +i-node may become unlinked, meaning that it needs to garbage collected +along with its file contents in the data region. To implement file +removal we make use of another standard operation on association +lists. % -\begin{derivation} - &2 + \splitter\,(\lambda p.2 + \splitter\,(\lambda p'.3 + \calldc\,\Record{p;\lambda k. k~0 + \abort\,\Record{p';\lambda\Unit.k~1}})),\emptyset\\ - \reducesto& \reason{\slab{AppSplitter}}\\ - &2 + \Prompt_p~2 + \splitter\,(\lambda p'.3 + \calldc\,\Record{p;\lambda k. k~0 + \abort\,\Record{p';\lambda\Unit.k~1}}), \{p\}\\ - \reducesto& \reason{\slab{AppSplitter}}\\ - &2 + \Prompt_p~2 + \Prompt_{p'}~3 + \calldc\,\Record{p;\lambda k. k~0 + \abort\,\Record{p';\lambda\Unit.k~1}}, \{p,p'\}\\ - \reducesto& \reason{\slab{Capture} $\EC = 2 + \Prompt_{p'}~3 + [\,]$}\\ - &2 + k~0 + \abort\,\Record{p';\lambda\Unit.k~1}, \{p,p'\}\\ - \reducesto& \reason{\slab{Resume} $\EC$ with $0$}\\ - &2 + 2 + \Prompt_{p'}~3 + \abort\,\Record{p';\lambda\Unit.\qq{\cont_{\EC}}\,1}, \{p,p'\}\\ - \reducesto^+& \reason{\slab{Abort}}\\ - & 4 + \qq{\cont_{\EC}}\,1, \{p,p'\}\\ - \reducesto& \reason{\slab{Resume} $\EC$ with $1$}\\ - & 4 + 2 + \Prompt_{p'}~3 + 1, \{p,p'\}\\ - \reducesto^+& \reason{\slab{Value}}\\ - & 6 + 4, \{p,p'\} \reducesto 10, \{p,p'\} -\end{derivation} +\[ + \remove : \Record{\alpha;\Record{\alpha;\beta}} \to \Record{\alpha;\beta} +\] % -The important thing to observe here is that the application of -$\calldc$ skips over the inner prompt and reifies it as part of the -continuation. This behaviour stands differ from the original -formulations of control/prompt, shift/reset. The first application of -$k$ restores the context with the prompt. The $\abort$ application -erases the evaluation context up to this prompt, however, the body of -the functional argument to $\abort$ reinvokes the continuation $k$ -which restores the prompt context once again. - -\citet{MoreauQ94} proposed a variation of splitter called -\emph{marker}, which is also built on top of multi-prompt -semantics. The key difference is that the control reifier strips the -reified context of all prompts. -% \begin{derivation} -% &1 + \splitter\,(\lambda p.2 + \splitter\,(\lambda p'.3 + \calldc\,\Record{p';\lambda k. k\,0 + \calldc\,\Record{p';\lambda k'. k\,(k'\,1)}}))), \emptyset\\ -% \reducesto& \reason{\slab{AppSplitter}}\\ -% &1 + \Prompt_p~2 + \splitter\,(\lambda p'.3 + \calldc\,\Record{p;\lambda k.k~1 + \calldc\,\Record{p';\lambda k'. k\,(k'~1)}})), \{p\}\\ -% \reducesto& \reason{\slab{AppSplitter}}\\ -% &1 + \Prompt_p~2 + \Prompt_{p'} 3 + \calldc\,\Record{p';\lambda k.k~0 + \calldc\,\Record{p';\lambda k'. k\,(k'~1)}}, \{p,p'\}\\ -% \reducesto& \reason{\slab{Capture} $\EC = 2 + \Prompt_{p'}~3 + [\,]$}, \{p,p'\}\\ -% &1 + ((\lambda k.k~0 + \calldc\,\Record{p';\lambda k'. k\,(k'~1)})\,\qq{\cont_\EC}),\{p,p'\}\\ -% \reducesto^+& \reason{\slab{Resume} $\EC$ with $0$}\\ -% &1 + 2 + \Prompt_{p'}~3 + 0 + \calldc\,\Record{p';\lambda k'. \qq{\cont_\EC}\,(k'~1)}\\ -% \reducesto^+& \reason{\slab{Capture} $\EC' = 3 + [\,]$}\\ -% &3 + (\lambda k'. \qq{\cont_\EC}\,(k'~1)\,\qq{\cont_{\EC'}})\\ -% \reducesto^+& \reason{\slab{Resume} $\EC'$ with $1$}\\ -% &3 + \qq{\cont_\EC}\,(3 + 1)\\ -% \reducesto^+& \reason{\slab{Resume} $\EC$ with $4$}\\ -% &3 + 2 + \Prompt_{p'}~3 + 4\\ -% \reducesto^+& \reason{\slab{Value}}\\ -% &5 + 7 \reducesto 13 -% \end{derivation} +The first parameter to $\remove$ is the key associated with the entry +to be removed from the association list, which is given as the second +parameter. If the association list does not have an entry for the +given key, then the function behaves as the identity. The behaviour of +the function in case of multiple entries for a single key does not +matter as our system is carefully set up to ensure that each key has +an unique entry. % - -% \begin{reductions} -% \slab{Value} & \splitter~abort~calldc.V &\reducesto& V\\ -% \slab{Throw} & \splitter~abort~calldc.\EC[\,abort~V] &\reducesto& V~\Unit\\ -% \slab{Capture} & -% \splitter~abort~calldc.\EC[calldc~V] &\reducesto& V~\qq{\cont_{\EC}} \\ -% \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V] -% \end{reductions} - - -\paragraph{Spawn} The spawn control operator appeared in a paper -published by \citet{HiebD90} in 1990. It is designed for using -continuations to program tree-based concurrency. Syntactically, spawn -is just a function symbol (like callcc), whose operational behaviour -establishes the root of a process tree, and passes the -\emph{controller} for the tree to its argument. As we will see shortly -a controller is a higher-order function, which grants its argument -access to the continuation of a process. - -We add $\Spawn$ as a value form. +\[ + \bl + \funlink : \Record{\String;\FileSystem} \to \FileSystem \eff \{\Fail : \UnitType \opto \ZeroType\}\\ + \funlink\,\Record{fname;fs} \defas + \bl + \If\;\dec{has}\,\Record{fname;fs.dir}\;\Then\\ + \quad + \bl + \Let\;ino \revto \lookup\,\Record{fname;fs.dir}\;\In\\ + \Let\;dir' \revto \remove\,\Record{fname;fs.dir}\;\In\\ + \Let\;inode \revto \lookup\,\Record{ino;fs.ilist}\;\In\\ + \Let\;\Record{ilist';dreg'} \revto + \bl + \If\;inode.lno > 1\;\Then\\ + \quad\bl + \Let\;inode' \revto \Record{\bl inode\;\With\\lno = inode.lno - 1}\el\\ + \In\;\Record{\modify\,\Record{ino;inode';fs.ilist};fs.dreg} + \el\\ + \Else\; + \Record{\bl\remove\,\Record{ino;fs.ilist};\\ + \remove\,\Record{inode.loc;fs.dreg}} + \el + \el\\ + \In\;\Record{fs\;\With\;dir = dir'; ilist = ilist'; dreg = dreg'} + \el\\ + \Else\; \Absurd~\Do\;\Fail\,\Unit + \el + \el +\] % -\begin{syntax} - &V,W \in \ValCat &::=& \Spawn -\end{syntax} +The $\funlink$ function checks whether the given filename $fname$ +exists in the directory. If it does not, then it raises the $\Fail$ +exceptions. However, if it does exist then the function proceeds to +lookup the index of the i-node for the file, which gets bound to +$ino$, and subsequently remove the filename from the +directory. Afterwards it looks up the i-node with index $ino$. Now one +of two things happen depending on the current link count of the +i-node. If the count is greater than one, then we need only decrement +the link count by one, thus we modify the i-node structure. If the +link count is 1, then i-node is about to become stale, thus we must +garbage collect it by removing both the i-node from the i-list and the +contents from the data region. Either branch returns the new state of +i-list and data region. Finally, the function returns the new file +system state. + +With the $\flink$ and $\funlink$ functions, we can implement the +semantics for $\ULink$ and $\UUnlink$ operations following the same +patterns as for the other file system operations. % -\citet{HiebD90} do not give a static semantics for $\Spawn$. Their -dynamic semantics depend on an extension reminiscent of multi-prompts. +\[ + \bl + \fileLU : (\UnitType \to \alpha \eff \FileLU) \to \alpha \eff \State~\FileSystem\\ + \fileLU~m \defas + \bl + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;res &\mapsto& res\\ + \OpCase{\ULink}{\Record{src;dest}}{resume} &\mapsto& + \bl + \faild\,\Record{\Unit; \lambda\Unit.\\ + \quad\bl + \Let\;fs = \flink\,\Record{src;dest;\Uget\,\Unit}\\ + \In\;\Uput~fs}; resume\,\Unit + \el + \el\\ + \OpCase{\UUnlink}{fname}{resume} &\mapsto& + \bl + \faild\,\Record{\Unit; \lambda\Unit.\\ + \quad\bl + \Let\;fs = \funlink\,\Record{fname;\Uget\,\Unit}\\ + \In\;\Uput~fs}; resume\,\Unit + \el + \el + \ea + \el + \el +\] % -\begin{syntax} - &\ell \in \mathcal{L} &&\\ - &M,N \in \CompCat &::=& \ell : M \mid V\,\reflect\,\ell -\end{syntax} +The composition of $\fileRW$, $\fileAlloc$, and $\fileLU$ complete the +implementation of \fsname{}. % -The set $\mathcal{L}$ is some countably set of labels. +\[ + \bl + \FileIO \defas \{\FileRW;\FileCO;\FileLU\} \medskip\\ + \fileIO : (\UnitType \to \alpha \eff \FileIO) \to \alpha \eff \State~\FileSystem \\ + \fileIO~m \defas \fileRW\,(\lambda\Unit. \fileAlloc\,(\lambda\Unit.\fileLU~m)) + \el +\] % -The expression ($\ell : M$) is called a \emph{labelled} expression. It -essentially plays the role of prompt. The expression -$(V\,\reflect\,\ell)$ is called a control expression. The operator -$\reflect$ is a control reifier which captures the continuation up to -the label $\ell$ and supplies this continuation to $V$. +The three handlers may as well be implemented as a single monolithic +handler, since they implement different operations, return the same +value, and make use of the same state cell. In practice a monolithic +handler may have better performance. However, a sufficiently clever +compiler would be able to take advantage of the fusion laws of deep +handlers to fuse the three handlers into one (e.g. using the technique +of \citet{WuS15}), and thus allow modular composition without +composition. + +We now have the building blocks to implement a file copying +utility. We will implement the utility such that it takes an argument +to decide whether it should make a soft copy such that the source file +and destination file are linked, or it should make a hard copy such +that a new i-node is allocated and the bytes in the data regions gets +duplicated. % -\begin{reductions} - \slab{AppSpawn} & \Spawn~V,\rho &\reducesto& \ell : V\,(\lambda f. f\,\reflect\,\ell), \{\ell\} \uplus \rho\\ - \slab{Value} & \ell : V,\rho &\reducesto& V,\rho\\ - \slab{Capture} & \ell : \EC[V\,\reflect\,\ell],\rho &\reducesto& V\,\qq{\cont_{\ell : \EC}},\rho\\ - \slab{Resume} & \Continue~\cont_{\ell : \EC}~V,\rho &\reducesto& \ell : \EC[V],\rho -\end{reductions} +\[ + \bl + \dec{cp} : \Record{\Bool;\String;\String} \to \UnitType \eff \{\FileIO;\Exit : \Int \opto \ZeroType\}\\ + \dec{cp}~\Record{link;src;dest} \defas + \bl + \If\;link\;\Then\;\Do\;\ULink\,\Record{src;dest}\;\\ + \Else\; \bl + \Case\;\Do\;\UOpen~src\\ + \{ \ba[t]{@{~}l@{~}c@{~}l} + \None &\mapsto& \exit~1\\ + \Some~ino &\mapsto& \\ + \multicolumn{3}{l}{\quad\Case\;\Do\;\URead~ino\;\{ + \ba[t]{@{~}l@{~}c@{~}l} + \None &\mapsto& \exit~1\\ + \Some~cs &\mapsto& \echo~cs~\redirect~dest \} \} + \ea} + \ea + \el + \el + \el +\] % -The $\slab{AppSpawn}$ rule generates a fresh $\ell$ and applies the -functional value $V$ the controller for process tree. By the -$\slab{Capture}$ rule, an invocation of the controller causes the -evaluation context up to the matching label $\ell$ to be reified as a -continuation. This continuation gets passed to the functional value of -the control expression. The captured continuation contains the label -$\ell$, and as specified by the $\slab{Resume}$ rule an invocation of -the continuation causes this label to be reinstalled. +If the $link$ parameter is $\True$, then the utility makes a soft copy +by performing the operation $\ULink$ to link the source file and +destination file. Otherwise the utility makes a hard copy by first +opening the source file. If $\UOpen$ returns the $\None$ (i.e. the +open failed) then the utility exits with code $1$. If the open +succeeds then the entire file contents are read. If the read operation +fails then we again just exit, however, in the event that it succeeds +we apply the $\echo$ to the file contents and redirects the output to +the file $dest$. -The following example usage of $\Spawn$ is a slight variation on an -example due to \citet{HiebDA94}. +The logic for file removal is part of the semantics for +$\UUnlink$. Therefore the implementation of a file removal utility is +simply an application of the operation $\UUnlink$. % -\begin{derivation} - & 1 \cons (\Spawn\,(\lambda c. 2 \cons (c\,(\lambda k. 3 \cons k\,(k\,\nil))))), \emptyset\\ - \reducesto& \reason{\slab{AppSpawn}}\\ - &1 \cons (\ell : (\lambda c. 2 \cons (c\,(\lambda k. 3 \cons k\,(k\,\nil))))\,(\lambda f.f \reflect \ell)), \{\ell\}\\ - \reducesto& \reason{$\beta$-reduction}\\ - &1 \cons (\ell : 2 \cons ((\lambda f.f \reflect \ell)\,(\lambda k. 3 \cons k\,(k\,\nil)))), \{\ell\}\\ -\end{derivation} +\[ + \bl + \dec{rm} : \String \to \UnitType \eff \{\UUnlink : \String \opto \UnitType\}\\ + \dec{rm}~fname \defas \Do\;\UUnlink~fname + \el +\] % -\begin{derivation} - \reducesto& \reason{$\beta$-reduction}\\ - &1 \cons (\ell : 2 \cons ((\lambda k. 3 \cons k\,(k\,\nil)) \reflect \ell)), \{\ell\}\\ - \reducesto& \reason{\slab{Capture} $\EC = 2 \cons [\,]$}\\ - & 1 \cons 3 \cons \qq{\cont_{\EC}}\,(\qq{\cont_{\EC}}\,\nil), \{\ell\}\\ - \reducesto& \reason{\slab{Resume} $\EC$ with $\nil$}\\ - &1 \cons 3 \cons \qq{\cont_{\EC}}\,(\ell : 2 \cons \nil), \{\ell\}\\ - \reducesto^+& \reason{\slab{Value}}\\ - &1 \cons 3 \cons \qq{\cont_{\EC}}\,[2], \{\ell\}\\ - \reducesto^+& \reason{\slab{Resume} $\EC$ with $[2]$}\\ - &1 \cons 3 \cons (\ell : 2 \cons [2]), \{\ell\}\\ - \reducesto^+& \reason{\slab{Value}}\\ - &1 \cons 3 \cons [2,2], \{\ell\} \reducesto^+ [1,3,2,2], \{\ell\} -\end{derivation} +We can now plug it all together. % -When the controller $c$ is invoked the current continuation is -$1 \cons (\ell : 2 \cons [\,])$. The control expression reifies the -$\ell : 2 \cons [\,]$ portion of the continuation and binds it to -$k$. The first invocation of $k$ reinstates the reified portion and -computes the singleton list $[2]$ which is used as argument to the -second invocation of $k$. +\[ + \ba{@{~}l@{~}l} + &\bl + \runState\,\Record{\dec{fs}_0;\fileIO\,(\lambda\Unit.\\ + \quad\timeshare\,(\lambda\Unit.\\ + \qquad\dec{interruptWrite}\,(\lambda\Unit.\\ + \qquad\quad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ + \qquad\qquad\status\,(\lambda\Unit. + \ba[t]{@{}l} + \If\;\fork\,\Unit\;\\ + \Then\; + \bl + \su~\Alice;\, + \quoteRitchie~\redirect~\strlit{ritchie.txt};\\ + \dec{cp}\,\Record{\False;\strlit{ritchie.txt};\strlit{ritchie}};\\ + \dec{rm}\,\strlit{ritchie.txt} + \el\\ + \Else\; + \bl + \su~\Bob;\, + \quoteHamlet~\redirect~\strlit{hamlet};\\ + \dec{cp}\,\Record{\True;\strlit{hamlet};\strlit{act3}} + )}))))} + \el + \ea + \el \smallskip\\ + \reducesto^+& + \bl + \Record{ + \ba[t]{@{}l} + [0, 0];\\ + \Record{ + \ba[t]{@{}l} + dir=[\Record{\strlit{ritchie};3},\Record{\strlit{act3};2},\Record{\strlit{hamlet};2}, + \Record{\strlit{stdout};0}];\\ + ilist=[\Record{3;\Record{lno=1;loc=3}}, + \Record{2;\Record{lno=2;loc=2}}, + \Record{0;\Record{lno=1;loc=0}}];\\ + dreg=[ + \ba[t]{@{}l} + \Record{3; + \ba[t]{@{}l@{}l} + \texttt{"}&\texttt{UNIX is basically a simple operating system, }\\ + &\texttt{but you have to be a genius }\\ + &\texttt{to understand the simplicity.\nl{"}}}, + \ea\\ + \Record{2; + \ba[t]{@{}l@{}l} + \texttt{"}&\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\ + &\texttt{Whether 'tis nobler in the mind to suffer\nl{}"}}, + \ea\\ + \Record{0; \strlit{}}]; lnext=4; inext=4}}\\ + \ea + \ea + \ea\\ + : \Record{\List~\Int; \FileSystem} + \el + \ea +\] +% +Alice copies the file \texttt{ritchie.txt} as \texttt{ritchie}, and +subsequently removes the original file, which effectively amounts to a +roundabout way of renaming a file. It is evident from the file system +state that the file is a hard copy as the contents of +\texttt{ritchie.txt} now reside in location $3$ rather than location +$1$ in the data region. Bob makes a soft copy of the file +\texttt{hamlet} as \texttt{act3}, which is evident by looking at the +directory where the two filenames point to the same i-node (with index +$2$), whose link counter has value $2$. -Both \citet{HiebD90} and \citet{HiebDA94} give several concurrent -programming examples with spawn. They show how -parallel-or~\cite{Plotkin77} can be codified as a macro using spawn -(and a parallel invocation primitive \emph{pcall}). +\paragraph{Summary} Throughout this section we have used effect +handlers to give a semantics to a \UNIX{}-style operating system by +treating system calls as effectful operations, whose semantics are +given by handlers, acting as composable micro-kernels. Starting from a +simple bare minimum file I/O model we seen how the modularity of +effect handlers enable us to develop a feature-rich operating system +in an incremental way by composing several handlers to implement a +basic file system, multi-user environments, and multi-tasking +support. Each incremental change to the system has been backwards +compatible with previous changes in the sense that we have not +modified any previously defined interfaces in order to support a new +feature. It serves as a testament to demonstrate the versatility of +effect handlers, and it suggests that handlers can be a viable option +to use with legacy code bases to retrofit functionality. The operating +system makes use of fourteen operations, which are being handled by +twelve handlers, some of which are used multiple times, e.g. the +$\environment$ and $\redirect$ handlers. -\paragraph{\citeauthor{Sitaram93}'s fcontrol} The control operator -`fcontrol' was introduced by \citet{Sitaram93} in 1993. It is a -refinement of control0/prompt0, and thus, it is a dynamic delimited -control operator. The main novelty of fcontrol is that it shifts the -handling of continuations from control capture operator to the control -delimiter. The prompt interface for fcontrol lets the programmer -attach a handler to it. This handler is activated whenever a -continuation captured. -% -\citeauthor{Sitaram93}'s observation was that with previous control -operators the handling of control happens at continuation capture -point, meaning that the control handling logic gets intertwined with -application logic. The inspiration for the interface of fcontrol and -its associated prompt came from exception handlers, where the handling -of exceptions is separate from the invocation site of -exceptions~\cite{Sitaram93}. +\section{\UNIX{}-style pipes} +\label{sec:pipes} -The operator fcontrol is a value and prompt with handler is a -computation. -% -\begin{syntax} - & V, W \in \ValCat &::=& \cdots \mid \fcontrol\\ - & M, N \in \CompCat &::=& \cdots \mid \fprompt~V.M -\end{syntax} +A \UNIX{} pipe is an abstraction for streaming communication between +two processes. Technically, a pipe works by connecting the standard +out file descriptor of the first process to the standard in file +descriptor of the second process. The second process can then process +the output of the first process by reading its own standard in +file~\cite{RitchieT74}. + +We could implement pipes using the file system, however, it would +require us to implement a substantial amount of bookkeeping as we +would have to generate and garbage collect a standard out file and a +standard in file per process. Instead we can represent the files as +effectful operations and connect them via handlers. % -As with $\Callcc$, the value $\fcontrol$ may be regarded as a special -unary function symbol. The syntax $\fprompt$ denotes a prompt (in -\citeauthor{Sitaram93}'s terminology it is called run). The value -constituent of $\fprompt$ is the control handler. It is a binary -function, that gets applied to the argument of $\fcontrol$ and the -continuation up to the prompt. +With shallow handlers we can implement a demand-driven Unix pipeline +operator as two mutually recursive handlers. % -The dynamic semantics elucidate this behaviour formally. +\[ +\bl + \Pipe : \Record{\UnitType \to \alpha \eff \{ \Yield : \beta \opto \UnitType \}; \UnitType \to \alpha\eff\{ \Await : \UnitType \opto \beta \}} \to \alpha \\ + \Pipe\, \Record{p; c} \defas + \bl + \ShallowHandle\; c\,\Unit \;\With\; \\ + ~\ba[m]{@{}l@{~}c@{~}l@{}} + \Return~x &\mapsto& x \\ + \OpCase{\Await}{\Unit}{resume} &\mapsto& \Copipe\,\Record{resume; p} \\ + \ea + \el\medskip\\ + + \Copipe : \Record{\beta \to \alpha\eff\{ \Await : \UnitType \opto \beta\}; \UnitType \to \alpha\eff\{ \Yield : \beta \opto \UnitType\}} \to \alpha \\ + \Copipe\, \Record{c; p} \defas + \bl + \ShallowHandle\; p\,\Unit \;\With\; \\ + ~\ba[m]{@{}l@{~}c@{~}l@{}} + \Return~x &\mapsto& x \\ + \OpCase{\Yield}{y}{resume} &\mapsto& \Pipe\,\Record{resume; \lambda \Unit. c\, y} \\ + \ea \\ + \el \\ +\el +\] % -\begin{reductions} - \slab{Value} & - \fprompt~V.W &\reducesto& W\\ - \slab{Capture} & - \fprompt~V.\EC[\fcontrol~W] &\reducesto& V~W~\qq{\cont_{\EC}}, \text{ where $\EC$ contains no \fprompt}\\ - \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V] -\end{reductions} +A $\Pipe$ takes two suspended computations, a producer $p$ and a +consumer $c$. % -The $\slab{Value}$ is similar to the previous $\slab{Value}$ -rules. The interesting rule is the $\slab{Capture}$. When $\fcontrol$ -is applied to some value $W$ the enclosing context $\EC$ gets reified -and aborted up to the nearest enclosing prompt, which invokes the -handler $V$ with the argument $W$ and the continuation. - -Consider the following, slightly involved, example. +Each of the computations returns a value of type $\alpha$. % -\begin{derivation} - &2 + \fprompt\,(\lambda y~k'.1 + k'~y).\fprompt\,(\lambda x~k. x + k\,(\fcontrol~k)).3 + \fcontrol~1\\ - \reducesto& \reason{\slab{Capture} $\EC = 3 + [\,]$}\\ - &2 + \fprompt\,(\lambda y~k'.1 + k'~y).(\lambda x~k. x + k\,(\fcontrol~k))\,1\,\qq{\cont_{\EC}}\\ - \reducesto^+& \reason{\slab{Capture} $\EC' = 1 + \qq{\cont_{\EC}}\,[\,]$}\\ - &2 + (\lambda k~k'.k'\,(k~1))\,\qq{\cont_{\EC}}\,\qq{\cont_{\EC'}}\\ - \reducesto^+& \reason{\slab{Resume} $\EC$ with $1$}\\ - &2 + \qq{\cont{\EC'}}\,(3 + 1)\\ - \reducesto^+& \reason{\slab{Resume} $\EC'$ with $4$}\\ - &2 + 1 + \qq{\cont_{\EC}}\,4\\ - \reducesto^+& \reason{\slab{Resume} $\EC$ with 4}\\ - &3 + 3 + 4 \reducesto^+ 10 -\end{derivation} +The producer can perform the $\Yield$ operation, which yields a value +of type $\beta$ and the consumer can perform the $\Await$ operation, +which correspondingly awaits a value of type $\beta$. The $\Yield$ +operation corresponds to writing to standard out, whilst $\Await$ +corresponds to reading from standard in. % -This example makes use of nontrivial control manipulation as it passes -a captured continuation around. However, the point is that the -separation of the handling of continuations from their capture makes -it considerably easier to implement complicated control idioms, -because the handling code is compartmentalised. +The shallow handler $\Pipe$ runs the consumer first. If the consumer +terminates with a value, then the $\Return$ clause is executed and +returns that value as is. If the consumer performs the $\Await$ +operation, then the $\Copipe$ handler is invoked with the resumption +of the consumer ($resume$) and the producer ($p$) as arguments. This +models the effect of blocking the consumer process until the producer +process provides some data. +The $\Copipe$ function runs the producer to get a value to feed to the +waiting consumer. +% The arguments are swapped and the consumer component +% now expects a value. +If the producer performs the $\Yield$ operation, then $\Pipe$ is +invoked with the resumption of the producer along with a thunk that +applies the consumer's resumption to the yielded value. +% +For aesthetics, we define a right-associative infix alias for pipe: +$p \mid c \defas \lambda\Unit.\Pipe\,\Record{p;c}$. -\paragraph{Cupto} The control operator cupto is a variation of -control0/prompt0 designed to fit into the typed ML-family of -languages. It was introduced by \citet{GunterRR95} in 1995. The name -cupto is an abbreviation for ``control up to''~\cite{GunterRR95}. +Let us put the pipe operator to use by performing a simple string +frequency analysis on a file. We will implement the analysis as a +collection of small single-purpose utilities which we connect by way +of pipes. We will build a collection of small utilities. We will make +use of two standard list iteration functions. % -The control operator comes with a set of companion constructs, and -thus, augments the syntactic categories of types, values, and -computations. +\[ + \ba{@{~}l@{~}c@{~}l} + \map &:& \Record{\alpha \to \beta;\List~\alpha} \to \List~\beta\\ + \iter &:& \Record{\alpha \to \beta; \List~\alpha} \to \UnitType + \ea +\] % -\begin{syntax} - & A,B \in \TypeCat &::=& \cdots \mid \prompttype~A \smallskip\\ - & V,W \in \ValCat &::=& \cdots \mid p \mid \newPrompt\\ - & M,N \in \CompCat &::=& \cdots \mid \Set\;V\;\In\;N \mid \Cupto~V~k.M -\end{syntax} +The function $\map$ applies its function argument to each element of +the provided list in left-to-right order and returns the resulting +list. The function $\iter$ is simply $\map$ where the resulting list +is ignored. Our first utility is a simplified version of the GNU +coreutil utility \emph{cat}, which copies the contents of files to +standard out~\cite[Section~3.1]{MacKenzieMPPBYS20}. Our version will +open a single file and stream its contents one character at a time. % -The type $\prompttype~A$ is the type of prompts. It is parameterised -by an answer type $A$ for the prompt context. Prompts are first-class -values, which we denote by $p$. The construct $\newPrompt$ is a -special function symbol, which returns a fresh prompt. The computation -form $\Set\;V\;\In\;N$ activates the prompt $V$ to delimit the dynamic -extent of continuations captured inside $N$. The $\Cupto~V~k.M$ -computation binds $k$ to the continuation up to (the first instance -of) the active prompt $V$ in the computation $M$. - -\citet{GunterRR95} gave a Hindley-Milner type -system~\cite{Hindley69,Milner78} for $\Cupto$, since they were working -in the context of ML languages. I do not reproduce the full system -here, only the essential rules for the $\Cupto$ constructs. +\[ + \bl + \cat : \String \to \UnitType \eff \{\FileIO;\Yield : \Char \opto \UnitType;\Exit : \Int \opto \ZeroType\}\\ + \cat~fname \defas + \bl + \Case\;\Do\;\UOpen~fname~\{\\ + ~\ba[t]{@{~}l@{~}c@{~}l} + \None &\mapsto& \exit\,1\\ + \Some~ino &\mapsto& \bl \Case\;\Do\;\URead~ino~\{\\ + ~\ba[t]{@{~}l@{~}c@{~}l} + \None &\mapsto& \exit\,1\\ + \Some~cs &\mapsto& \iter\,\Record{\lambda c.\Do\;\Yield~c;cs}; \Do\;\Yield~\charlit{\textnil} \}\} + \ea + \el + \ea + \el + \el +\] % -\begin{mathpar} - \inferrule* - {~} - {\typ{\Gamma,p:\prompttype~A}{p : \prompttype~A}} - - \inferrule* - {~} - {\typ{\Gamma}{\newPrompt} : \UnitType \to \prompttype~A} - - \inferrule* - {\typ{\Gamma}{V : \prompttype~A} \\ \typ{\Gamma}{N : A}} - {\typ{\Gamma}{\Set\;V\;\In\;N : A}} - - \inferrule* - {\typ{\Gamma}{V : \prompttype~B} \\ \typ{\Gamma,k : A \to B}{M : B}} - {\typ{\Gamma}{\Cupto\;V\;k.M : A}} -\end{mathpar} +The last line is the interesting line of code. The contents of the +file gets bound to $cs$, which is supplied as an argument to the list +iteration function $\iter$. The function argument yields each +character. Each invocation of $\Yield$ effectively suspends the +iteration until the next character is awaited. % -%val cupto : 'b prompt -> (('a -> 'b) -> 'b) -> 'a +This is an example of inversion of control as the iterator $\iter$ has +been turned into a generator. % -The typing rule for $\Set$ uses the type embedded in the prompt to fix -the type of the whole computation $N$. Similarly, the typing rule for -$\Cupto$ uses the prompt type of its value argument to fix the answer -type for the continuation $k$. % The type of the $\Cupto$ expression is -% the same as the domain of the continuation, which at first glance may -% seem strange. The intuition is that $\Cupto$ behaves as a let binding -% for the continuation in the context of a $\Set$ expression, i.e. -% % -% \[ -% \bl -% \Set\;p^{\prompttype~B}\;\In\;\EC[\Cupto\;p\;k^{A \to B}.M^B]^B\\ -% \reducesto \Let\;k \revto \lambda x^{A}.\EC[x]^B \;\In\;M^B -% \el -% \] -% % +We use the character $\textnil$ to identify the end of a stream. It is +essentially a character interpretation of the empty list (file) +$\nil$. -The dynamic semantics is generative to accommodate generation of fresh -prompts. Formally, the reduction relation is augmented with a store -$\rho$ that tracks which prompt names have already been allocated. +The $\cat$ utility processes the entire contents of a given +file. However, we may only be interested in some parts. The GNU +coreutil \emph{head} provides a way to process only a fixed amount of +lines and ignore subsequent +lines~\cite[Section~5.1]{MacKenzieMPPBYS20}. % -\begin{reductions} - \slab{Value} & - \Set\; p \;\In\; V, \rho &\reducesto& V, \rho\\ - \slab{NewPrompt} & - \newPrompt~\Unit, \rho &\reducesto& p, \rho \uplus \{p\}\\ - \slab{Capture} & - \Set\; p \;\In\; \EC[\Cupto~p~k.M], \rho &\reducesto& M[\qq{\cont_{\EC}}/k], \rho,\\ - \multicolumn{4}{l}{\hfill\text{where $p$ is not active in $\EC$}}\\ - \slab{Resume} & \Continue~\cont_{\EC}~V, \rho &\reducesto& \EC[V], \rho -\end{reductions} +We will implement a simplified version of this utility which lets us +keep the first $n$ lines of a stream and discard the remainder. This +process will act as a \emph{filter}, which is an intermediary process +in a pipeline that both awaits and yields data. % -The $\slab{Value}$ rule is akin to value rules of shift/reset and -control/prompt. The rule $\slab{NewPrompt}$ allocates a fresh prompt -name $p$ and adds it to the store $\rho$. The $\slab{Capture}$ rule -reifies and aborts the evaluation context up to the nearest enclosing -active prompt $p$. After reification the prompt is removed and -evaluation continues as $M$. The $\slab{Resume}$ rule reinstalls the -captured context $\EC$ with the argument $V$ plugged in. +\[ + \bl + \head : \Int \to \UnitType \eff \{\Await : \UnitType \opto \Char;\Yield : \Char \opto \UnitType\}\\ + \head~n \defas + \bl + \If\;n = 0\;\Then\;\Do\;\Yield~\charlit{\textnil}\\ + \Else\; + \bl + \Let\;c \revto \Do\;\Await~\Unit\;\In\\ + \Do\;\Yield~c;\\ + \If\;c = \charlit{\textnil}\;\Then\;\Unit\\ + \Else\;\If\;c = \charlit{\nl}\;\Then\;\head~(n-1)\\ + \Else\;\head~n + \el + \el + \el +\] % +The function first checks whether more lines need to be processed. If +$n$ is zero, then it yields the nil character to signify the end of +stream. This has the effect of ignoring any future instances of +$\Yield$ in the input stream. Otherwise it awaits a character. Once a +character has been received the function yields the character in order +to include it in the output stream. After the yield, it checks whether +the character was nil in which case the process +terminates. Alternatively, if the character was a newline the function +applies itself recursively with $n$ decremented by one. Otherwise it +applies itself recursively with the original $n$. -\citeauthor{GunterRR95}'s cupto provides similar behaviour to -\citeauthor{QueinnecS91}'s splitter in regards to being able to `jump -over prompt'. However, the separation of prompt creation from the -control reifier coupled with the ability to set prompts manually -provide a considerable amount of flexibility. For instance, consider -the following example which illustrates how control reifier $\Cupto$ -may escape a matching control delimiter. Let us assume that two -distinct prompts $p$ and $p'$ have already been created. -% -\begin{derivation} - &2 + \Set\; p \;\In\; 3 + \Set\;p'\;\In\;(\Set\;p\;\In\;\lambda\Unit.\Cupto~p~k.k\,(k~1))\,\Unit,\{p,p'\}\\ - \reducesto& \reason{\slab{Value}}\\ - &2 + \Set\; p \;\In\; 3 + \Set\;p'\;\In\;(\lambda\Unit.\Cupto~p~k.k\,(k~1))\,\Unit,\{p,p'\}\\ - \reducesto^+& \reason{\slab{Capture} $\EC = 3 + \Set\;p'\;\In\;[\,]$}\\ - &2 + \qq{\cont_{\EC}}\,(\qq{\cont_{\EC}}\,1),\{p,p'\}\\ - \reducesto& \reason{\slab{Resume} $\EC$ with $1$}\\ - &2 + \qq{\cont_{\EC}}\,(3 + \Set\;p'\;\In\;1),\{p,p'\}\\ - \reducesto^+& \reason{\slab{Value}}\\ - &2 + \qq{\cont_{\EC}}\,4,\{p,p'\}\\ - \reducesto& \reason{\slab{Resume} $\EC$ with $4$}\\ - &2 + \Set\;p'\;In\;4,\{p,p'\}\\ - \reducesto& \reason{\slab{Value}}\\ - &2 + 4,\{p,p'\} \reducesto 6,\{p,p'\} -\end{derivation} -% -The prompt $p$ is used twice, and the dynamic scoping of $\Cupto$ -means when it is evaluated it reifies the continuation up to the -nearest enclosing usage of the prompt $p$. Contrast this with the -morally equivalent example using splitter, which would get stuck on -the application of the control reifier, because it has escaped the -dynamic extent of its matching delimiter. +The $\head$ filter does not transform the shape of its data stream. It +both awaits and yields a character. However, the awaits and yields +need not operate on the same type within the same filter, meaning we +can implement a filter that transforms the shape of the data. Let us +implement a variation of the GNU coreutil \emph{paste} which merges +lines of files~\cite[Section~8.2]{MacKenzieMPPBYS20}. Our +implementation will join characters in its input stream into strings +separated by spaces and newlines such that the string frequency +analysis utility need not operate on the low level of characters. % +\[ + \bl + \paste : \UnitType \to \UnitType \eff \{\Await : \UnitType \opto \Char;\Yield : \String \opto \UnitType\}\\ + \paste\,\Unit \defas + \bl + pst\,\Record{\Do\;\Await~\Unit;\strlit{}}\\ + \where + \ba[t]{@{~}l@{~}c@{~}l} + pst\,\Record{\charlit{\textnil};str} &\defas& \Do\;\Yield~str;\Do\;\Yield~\strlit{\textnil}\\ + pst\,\Record{\charlit{\nl};str} &\defas& \Do\;\Yield~str;\Do\;\Yield~\strlit{\nl};pst\,\Record{\Do\;\Await~\Unit;\strlit{}}\\ + pst\,\Record{\charlit{~};str} &\defas& \Do\;\Yield~str;pst\,\Record{\Do\;\Await~\Unit;\strlit{}}\\ + pst\,\Record{c;str} &\defas& pst\,\Record{\Do\;\Await~\Unit;str \concat [c]} -\paragraph{\citeauthor{PlotkinP09}'s effect handlers} In 2009, -\citet{PlotkinP09} introduced handlers for \citeauthor{PlotkinP01}'s -algebraic effects~\cite{PlotkinP01,PlotkinP03,PlotkinP13}. In contrast -to the previous control operators, the mathematical foundations of -handlers were not an afterthought, rather, their origin is deeply -rooted in mathematics. Nevertheless, they turn out to provide a -pragmatic interface for programming with control. Operationally, -effect handlers can be viewed as a small extension to exception -handlers, where exceptions are resumable. Effect handlers are similar -to fcontrol in that handling of control happens at the delimiter and -not at the point of control capture. Unlike fcontrol, the interface of -effect handlers provide a mechanism for handling the return value of a -computation similar to \citeauthor{BentonK01}'s exception handlers -with success continuations~\cite{BentonK01}. - -Effect handler definitions occupy their own syntactic category. -% -\begin{syntax} - &A,B \in \ValTypeCat &::=& \cdots \mid A \Harrow B \smallskip\\ - &H \in \HandlerCat &::=& \{ \Return \; x \mapsto M \} - \mid \{ \OpCase{\ell}{p}{k} \mapsto N \} \uplus H\\ -\end{syntax} + \ea + \el + \el +\] % -An effect handler consists of a $\Return$-clause and zero or more -operation clauses. Each operation clause binds the payload of the -matching operation $\ell$ to $p$ and the continuation of the operation -invocation to $k$ in $N$. +The heavy-lifting is delegated to the recursive function $pst$ +which accepts two parameters: 1) the next character in the input +stream, and 2) a string buffer for building the output string. The +function is initially applied to the first character from the stream +(returned by the invocation of $\Await$) and the empty string +buffer. The function $pst$ is defined by pattern matching on the +character parameter. The first three definitions handle the special +cases when the received character is nil, newline, and space, +respectively. If the character is nil, then the function yields the +contents of the string buffer followed by a string with containing +only the nil character. If the character is a newline, then the +function yields the string buffer followed by a string containing the +newline character. Afterwards the function applies itself recursively +with the next character from the input stream and an empty string +buffer. The case when the character is a space is similar to the +previous case except that it does not yield a newline string. The +final definition simply concatenates the character onto the string +buffer and recurses. -Effect handlers introduces a new syntactic category of signatures, and -extends the value types with operation types. Operation and handler -application both appear as computation forms. -% -\begin{syntax} - &\Sigma \in \mathsf{Sig} &::=& \emptyset \mid \{ \ell : A \opto B \} \uplus \Sigma\\ - &A,B,C,D \in \ValTypeCat &::=& \cdots \mid A \opto B \smallskip\\ - &M,N \in \CompCat &::=& \cdots \mid \Do\;\ell\,V \mid \Handle \; M \; \With \; H\\[1ex] -\end{syntax} -% -A signature is a collection of labels with operation types. An -operation type $A \opto B$ is similar to the function type in that $A$ -denotes the domain (type of the argument) of the operation, and $B$ -denotes the codomain (return type). For simplicity, we will just -assume a global fixed signature. The form $\Do\;\ell\,V$ is the -application form for operations. It applies an operation $\ell$ with -payload $V$. The construct $\Handle\;M\;\With\;H$ handles a -computation $M$ with handler $H$. -% -\begin{mathpar} - \inferrule* - {{\bl - % C = A \eff \{(\ell_i : A_i \opto B_i)_i; R\} \\ - % D = B \eff \{(\ell_i : P_i)_i; R\}\\ - \{\ell_i : A_i \opto B_i\}_i \in \Sigma \\ - H = \{\Return\;x \mapsto M\} \uplus \{ \OpCase{\ell_i}{p_i}{k_i} \mapsto N_i \}_i \\ - \el}\\\\ - \typ{\Gamma, x : A;\Sigma}{M : D}\\\\ - [\typ{\Gamma,p_i : A_i, k_i : B_i \to D;\Sigma}{N_i : D}]_i - } - {\typ{\Gamma;\Sigma}{H : C \Harrow D}} -\end{mathpar} -\begin{mathpar} - \inferrule* - {\{ \ell : A \opto B \} \in \Sigma \\ \typ{\Gamma;\Sigma}{V : A}} - {\typ{\Gamma;\Sigma}{\Do\;\ell\,V : B}} - - \inferrule* - { - \typ{\Gamma}{M : C} \\ - \typ{\Gamma}{H : C \Harrow D} - } - {\typ{\Gamma;\Sigma}{\Handle \; M \; \With\; H : D}} -\end{mathpar} -% -The first typing rule checks that the operation label of each -operation clause is declared in the signature $\Sigma$. The signature -provides the necessary information to construct the type of the -payload parameters $p_i$ and the continuations $k_i$. Note that the -domain of each continuation $k_i$ is compatible with the codomain of -$\ell_i$, and the codomain of $k_i$ is compatible with the codomain of -the handler. -% -The second and third typing rules are application of operations and -handlers, respectively. The rule for operation application simply -inspects the signature to check that the operation is declared, and -that the type of the payload is compatible with the declared type. - -This particular presentation is nominal, because operations are -declared up front. Nominal typing is the only sound option in the -absence of an effect system (unless we restrict operations to work -over a fixed type, say, an integer). In -Chapter~\ref{ch:unary-handlers} we see a different presentation based -on structural typing. - -The dynamic semantics of effect handlers are similar to that of -$\fcontrol$, though, the $\slab{Value}$ rule is more interesting. -% -\begin{reductions} - \slab{Value} & \Handle\; V \;\With\;H &\reducesto& M[V/x], \text{ where } \{\Return\;x \mapsto M\} \in H\\ - \slab{Capture} & \Handle\;\EC[\Do\;\ell~V] \;\With\; H &\reducesto& M[V/p,\qq{\cont_{\Record{\EC;H}}}/k],\\ - \multicolumn{4}{l}{\hfill\bl\text{where $\ell$ is not handled in $\EC$}\\\text{and }\{\OpCase{\ell}{p}{k} \mapsto M\} \in H\el}\\ - \slab{Resume} & \Continue~\cont_{\Record{\EC;H}}~V &\reducesto& \Handle\;\EC[V]\;\With\;H\\ -\end{reductions} -% -The \slab{Value} rule differs from previous operators as it is not -just the identity. Instead the $\Return$-clause of the handler -definition is applied to the return value of the computation. -% -The \slab{Capture} rule handles operation invocation by checking -whether the handler $H$ handles the operation $\ell$, otherwise the -operation implicitly passes through the term to the context outside -the handler. This behaviour is similar to how exceptions pass through -the context until a suitable handler has been found. -% -If $H$ handles $\ell$, then the context $\EC$ from the operation -invocation up to and including the handler $H$ are reified as a -continuation object, which gets bound in the corresponding clause for -$\ell$ in $H$ along with the payload of $\ell$. -% -This form of effect handlers is known as \emph{deep} handlers. They -are deep in the sense that they embody a structural recursion scheme -akin to fold over computation trees induced by effectful -operations. The recursion is evident from $\slab{Resume}$ rule, as -continuation invocation causes the same handler to be reinstalled -along with the captured context. - -A classic example of handlers in action is handling of -nondeterminism. Let us fix a signature with two operations. +Another useful filter is the GNU stream editor abbreviated +\emph{sed}~\cite{PizziniBMG20}. It is an advanced text processing +editor, whose complete functionality we will not attempt to replicate +here. We will just implement the ability to replace a string by +another. This will be useful for normalising the input stream to the +frequency analysis utility, e.g. decapitalise words, remove unwanted +characters, etc. % \[ - \Sigma \defas \{\Fail : \UnitType \opto \ZeroType; \Choose : \UnitType \opto \Bool\} + \bl + \sed : \Record{\String;\String} \to \UnitType \eff \{\Await : \UnitType \opto \String;\Yield : \String \opto \UnitType\}\\ + \sed\,\Record{target;str'} \defas + \bl + \Let\;str \revto \Do\;\Await~\Unit\;\In\\ + \If\;str = target\;\Then\;\Do\;\Yield~str';\sed\,\Record{target;str'}\\ + \Else\;\Do\;\Yield~str;\sed\,\Record{target;str'} + \el + \el \] % -The $\Fail$ operation is essentially an exception as its codomain is -the empty type, meaning that its continuation can never be -invoked. The $\Choose$ operation returns a boolean. +The function $\sed$ takes two string arguments. The first argument is +the string to be replaced in the input stream, and the second argument +is the replacement. The function first awaits the next string from the +input stream, then it checks whether the received string is the same +as $target$ in which case it yields the replacement $str'$ and +recurses. Otherwise it yields the received string and recurses. -We will define a handler for each operation. +Now let us implement the string frequency analysis utility. It work on +strings and count the occurrences of each string in the input stream. % \[ - \ba{@{~}l@{~}l} - H^{A}_{f} : A \Harrow \Option~A\\ - H_{f} \defas \{ \Return\; x \mapsto \Some~x; &\OpCase{\Fail}{\Unit}{k} \mapsto \None \}\\ - H^B_{c} : B \Harrow \List~B\\ - H_{c} \defas \{ \Return\; x \mapsto [x]; &\OpCase{\Choose}{\Unit}{k} \mapsto k~\True \concat k~\False \} - \ea + \bl + \freq : \UnitType \to \UnitType \eff \{\Await : \UnitType \opto \String;\Yield : \List\,\Record{\String;\Int} \opto \UnitType\}\\ + \freq\,\Unit \defas + \bl + freq'\,\Record{\Do\;\Await~\Unit;\nil}\\ + \where + \ba[t]{@{~}l@{~}c@{~}l} + freq'\,\Record{\strlit{\textnil};tbl} &\defas& \Do\;\Yield~tbl\\ + freq'\,\Record{str;tbl} &\defas& + \bl + \Let\;tbl' \revto \faild\,\Record{ + \bl + \Record{str;1} \cons tbl; \lambda\Unit.\\ + \Let\; sum \revto \lookup\,\Record{str;tbl}\;\In\\ + \modify\,\Record{str;sum+1;tbl}} + \el\\ + \In\;freq'\,\Record{\Do\;\Await~\Unit;tbl'} + \el + \ea + \el + \el \] % -The handler $H_f$ handles an invocation of $\Fail$ by dropping the -continuation and simply returning $\None$ (due to the lack -polymorphism, the definitions are parameterised by types $A$ and $B$ -respectively. We may consider them as universal type variables). The -$\Return$-case of $H_f$ tags its argument with $\Some$. -% -The $H_c$ definition handles an invocation of $\Choose$ by first -invoking the continuation $k$ with $\True$ and subsequently with -$\False$. The two results are ultimately concatenated. The -$\Return$-case lifts its argument into a singleton list. -% -Now, let us define a simple nondeterministic coin tossing computation -with failure (by convention let us interpret $\True$ as heads and -$\False$ as tails). +The auxiliary recursive function $freq'$ implements the analysis. It +takes two arguments: 1) the next string from the input stream, and 2) +a table to keep track of how many times each string has occurred. The +table is implemented as an association list indexed by strings. The +function is initially applied to the first string from the input +stream and the empty list. The function is defined by pattern matching +on the string argument. The first definition handles the case when the +input stream has been exhausted in which case the function yields the +table. The other case is responsible for updating the entry associated +with the string $str$ in the table $tbl$. There are two subcases to +consider: 1) the string has not been seen before, thus a new entry +will have to created; or 2) the string already has an entry in the +table, thus the entry will have to be updated. We handle both cases +simultaneously by making use of the handler $\faild$, where the +default value accounts for the first subcase, and the computation +accounts for the second. The computation attempts to lookup the entry +associated with $str$ in $tbl$, if the lookup fails then $\faild$ +returns the default value, which is the original table augmented with +an entry for $str$. If an entry already exists it gets incremented by +one. The resulting table $tbl'$ is supplied to a recursive application +of $freq'$. + +We need one more building block to complete the pipeline. The utility +$\freq$ returns a value of type $\List~\Record{\String;\Int}$, we need +a utility to render the value as a string in order to write it to a +file. % \[ \bl - \toss : \UnitType \to \Bool\\ - \toss~\Unit \defas - \ba[t]{@{~}l} - \If\;\Do\;\Choose~\Unit\\ - \Then\;\Do\;\Choose~\Unit\\ - \Else\;\Absurd\;\Do\;\Fail~\Unit - \ea + \printTable : \UnitType \to \UnitType \eff \{\Await : \UnitType \opto \List\,\Record{\String;\Int}\}\\ + \printTable\,\Unit \defas + \map\,\Record{\lambda\Record{s;i}.s \concat \strlit{:} \concat \intToString~i \concat \strlit{;};\Do\;\Await~\Unit} \el \] % -The computation $\toss$ first performs $\Choose$ in order to -branch. If it returns $\True$ then a second instance of $\Choose$ is -performed. Otherwise, it raises the $\Fail$ exception. +The function performs one invocation of $\Await$ to receive the table, +and then performs a $\map$ over the table. The function argument to +$\map$ builds a string from the provided string-integer pair. % -If we apply $\toss$ outside of $H_c$ and $H_f$ then the computation -gets stuck as either $\Choose$ or $\Fail$, or both, would be -unhandled. Thus, we have to run the computation in the context of both -handlers. However, we have a choice to make as we can compose the -handlers in either order. Let us first explore the composition, where -$H_c$ is the outermost handler. Thus we instantiate $H_c$ at type -$\Option~\Bool$ and $H_f$ at type $\Bool$. +Here we make use of an auxiliary function, +$\intToString : \Int \to \String$, that turns an integer into a +string. The definition of this function is omitted here for brevity. % -\begin{derivation} - & \Handle\;(\Handle\;\toss~\Unit\;\With\; H_f)\;\With\;H_c\\ - \reducesto & \reason{$\beta$-reduction, $\EC = \If\;[\,]\;\Then \cdots$}\\ - & \Handle\;(\Handle\; \EC[\Do\;\Choose~\Unit] \;\With\; H_f)\;\With\;H_c\\ - \reducesto & \reason{\slab{Capture}, $\{\OpCase{\Choose}{\Unit}{k} \mapsto \cdots\} \in H_c$, $\EC' = (\Handle\;\EC\;\cdots)$}\\ - & k~\True \concat k~\False, \qquad \text{where $k = \qq{\cont_{\Record{\EC';H_c}}}$}\\ - \reducesto^+ & \reason{\slab{Resume} with $\True$}\\ - & (\Handle\;(\Handle\;\EC[\True] \;\With\;H_f)\;\With\;H_c) \concat k~\False\\ - \reducesto & \reason{$\beta$-reduction}\\ - & (\Handle\;(\Handle\; \Do\;\Choose~\Unit \;\With\; H_f)\;\With\;H_c) \concat k~\False\\ -\end{derivation} -\begin{derivation} - \reducesto & \reason{\slab{Capture}, $\{\OpCase{\Choose}{\Unit}{k'} \mapsto \cdots\} \in H_c$, $\EC'' = (\Handle\;[\,]\;\cdots)$}\\ - & (k'~\True \concat k'~\False) \concat k~\False, \qquad \text{where $k' = \qq{\cont_{\Record{\EC'';H_c}}}$}\\ - \reducesto& \reason{\slab{Resume} with $\True$}\\ - &((\Handle\;(\Handle\; \True \;\With\; H_f)\;\With\;H_c) \concat k'~\False) \concat k~\False\\ - \reducesto& \reason{\slab{Value}, $\{\Return\;x \mapsto \cdots\} \in H_f$}\\ - &((\Handle\;\Some~\True\;\With\;H_c) \concat k'~\False) \concat k~\False\\ - \reducesto& \reason{\slab{Value}, $\{\Return\;x \mapsto \cdots\} \in H_c$}\\ - & ([\Some~\True] \concat k'~\False) \concat k~\False\\ - \reducesto^+& \reason{\slab{Resume} with $\False$, \slab{Value}, \slab{Value}}\\ - & [\Some~\True] \concat [\Some~\False] \concat k~\False\\ - \reducesto^+& \reason{\slab{Resume} with $\False$}\\ - & [\Some~\True, \Some~\False] \concat (\Handle\;(\Handle\; \Absurd\;\Do\;\Fail\,\Unit \;\With\; H_f)\;\With\;H_c)\\ - \reducesto& \reason{\slab{Capture}, $\{\OpCase{\Fail}{\Unit}{k} \mapsto \cdots\} \in H_f$}\\ - & [\Some~\True, \Some~\False] \concat (\Handle\; \None\; \With\; H_c)\\ - \reducesto& \reason{\slab{Value}, $\{\Return\;x \mapsto \cdots\} \in H_c$}\\ - & [\Some~\True, \Some~\False] \concat [\None] \reducesto [\Some~\True,\Some~\False,\None] -\end{derivation} % -Note how the invocation of $\Choose$ passes through $H_f$, because -$H_f$ does not handle the operation. This is a key characteristic of -handlers, and it is called \emph{effect forwarding}. Any handler will -implicitly forward every operation that it does not handle. +% \[ +% \bl +% \wc : \UnitType \to \UnitType \eff \{\Await : \UnitType \opto \Char;\Yield : \Int \opto \UnitType\}\\ +% \wc\,\Unit \defas +% \bl +% \Do\;\Yield~(wc'\,\Unit)\\ +% \where~ +% \bl +% wc' \Unit \defas +% \bl +% \Let\;c \revto \Do\;\Await~\Unit\;\In\\ +% \If\;c = \charlit{\textnil}\;\Then\;0\\ +% \Else\; 1 + wc'~\Unit +% \el +% \el +% \el +% \el +% \] +% -Suppose we were to swap the order of $H_c$ and $H_f$, then the -computation would yield $\None$, because the invocation of $\Fail$ -would transfer control to $H_f$, which is the now the outermost -handler, and it would drop the continuation and simply return $\None$. - -The alternative to deep handlers is known as \emph{shallow} -handlers. They do not embody a particular recursion scheme, rather, -they correspond to case splits to over computation trees. -% -To distinguish between applications of deep and shallow handlers, we -will mark the latter with a dagger superscript, i.e. -$\ShallowHandle\; - \;\With\;-$. Syntactically deep and shallow -handler definitions are identical, however, their typing differ. -% -\begin{mathpar} -%\mprset{flushleft} - \inferrule* - {{\bl - % C = A \eff \{(\ell_i : A_i \opto B_i)_i; R\} \\ - % D = B \eff \{(\ell_i : P_i)_i; R\}\\ - \{\ell_i : A_i \opto B_i\}_i \in \Sigma \\ - H = \{\Return\;x \mapsto M\} \uplus \{ \OpCase{\ell_i}{p_i}{k_i} \mapsto N_i \}_i \\ - \el}\\\\ - \typ{\Gamma, x : A;\Sigma}{M : D}\\\\ - [\typ{\Gamma,p_i : A_i, k_i : B_i \to C;\Sigma}{N_i : D}]_i - } - {\typ{\Gamma;\Sigma}{H : C \Harrow D}} -\end{mathpar} -% -The difference is in the typing of the continuation $k_i$. The -codomains of continuations must now be compatible with the return type -$C$ of the handled computation. The typing suggests that an invocation -of $k_i$ does not reinstall the handler. The dynamic semantics reveal -that a shallow handler does not reify its own definition. -% -\begin{reductions} - \slab{Capture} & \ShallowHandle\;\EC[\Do\;\ell~V] \;\With\; H &\reducesto& M[V/p,\qq{\cont_{\EC}}/k],\\ - \multicolumn{4}{l}{\hfill\bl\text{where $\ell$ is not handled in $\EC$}\\\text{and }\{\ell~p~k \mapsto M\} \in H\el}\\ - \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V]\\ -\end{reductions} -% -The $\slab{Capture}$ reifies the continuation up to the handler, and -thus the $\slab{Resume}$ rule can only reinstate the captured -continuation without the handler. +We now have all the building blocks to construct a pipeline for +performing string frequency analysis on a file. The following performs +the analysis on the two first lines of Hamlet quote. % -%\dhil{Revisit the toss example with shallow handlers} -% \begin{reductions} -% \slab{Capture} & \Handle\;\EC[\Do\;\ell~V] \;\With\; H &\reducesto& M[V/p,\qq{\cont_{\EC}}/k],\\ -% \multicolumn{4}{l}{\hfill\bl\text{where $\ell$ is not handled in $\EC$}\\\text{and }\{\ell~p~k \mapsto M\} \in H\el}\\ -% \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V]\\ -% \end{reductions} +\[ + \ba{@{~}l@{~}l} + &\bl + \runState\,\Record{\dec{fs}_0;\fileIO\,(\lambda\Unit.\\ + \quad\timeshare\,(\lambda\Unit.\\ + \qquad\dec{interruptWrite}\,(\lambda\Unit.\\ + \qquad\quad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ + \qquad\qquad\status\,(\lambda\Unit. + \ba[t]{@{}l} + \quoteHamlet~\redirect~\strlit{hamlet};\\ + \Let\;p \revto + \bl + ~~(\lambda\Unit.\cat~\strlit{hamlet}) \mid (\lambda\Unit.\head~2) \mid \paste\\ + \mid (\lambda\Unit.\sed\,\Record{\strlit{be,};\strlit{be}}) \mid (\lambda\Unit.\sed\,\Record{\strlit{To};\strlit{to}})\\ + \mid (\lambda\Unit.\sed\,\Record{\strlit{question:};\strlit{question}})\\ + \mid \freq \mid \printTable + \el\\ + \In\;(\lambda\Unit.\echo~(p\,\Unit))~\redirect~\strlit{analysis})})))} + \ea + \el \smallskip\\ + \reducesto^+& + \bl + \Record{ + \ba[t]{@{}l} + [0];\\ + \Record{ + \ba[t]{@{}l} + dir=[\Record{\strlit{analysis};2},\Record{\strlit{hamlet};1}, + \Record{\strlit{stdout};0}];\\ + ilist=[\Record{2;\Record{lno=1;loc=2}}, + \Record{1;\Record{lno=1;loc=1}}, + \Record{0;\Record{lno=1;loc=0}}];\\ + dreg=[ + \ba[t]{@{}l} + \Record{2; + \ba[t]{@{}l@{}l} + \texttt{"}&\texttt{to:2;be:2;or:1;not:1;\nl:2;that:1;is:1}\\ + &\texttt{the:1;question:1;"}}, + \ea\\ + \Record{1; + \ba[t]{@{}l@{}l} + \texttt{"}&\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\ + &\texttt{Whether 'tis nobler in the mind to suffer\nl{}"}}, + \ea\\ + \Record{0; \strlit{}}]; lnext=3; inext=3}}\\ + \ea + \ea + \ea\\ + : \Record{\List~\Int; \FileSystem} + \el + \ea +\] % +The pipeline gets bound to the variable $p$. The pipeline starts with +call to $\cat$ which streams the contents of the file +$\strlit{hamlet}$ to the process $\head$ applied to $2$, meaning it +will only forward the first two lines of the file to its +successor. The third process $\paste$ receives the first two lines one +character at a time and joins the characters into strings delimited by +whitespace. The next three instances of $\sed$ perform some string +normalisation. The first instance removes the trailing comma from the +string $\strlit{be,}$; the second normalises the capitalisation of the +word ``to''; and the third removes the trailing colon from the string +$\strlit{question:}$. The seventh process performs the frequency +analysis and outputs a table, which is being rendered as a string by +the eighth process. The output of the pipeline is supplied to the +$\echo$ utility whose output is being redirected to a file named +$\strlit{analysis}$. Contents of the file reside in location $2$ in +the data region. Here we can see that the analysis has found that the +words ``to'', ``be'', and the newline character ``$\nl$'' appear two +times each, whilst the other words appear once each. -Chapter~\ref{ch:unary-handlers} contains further examples of deep and -shallow handlers in action. +\section{Process synchronisation} % -% \dhil{Consider whether to present the below encodings\dots} -% % -% Deep handlers can be used to simulate shift0 and -% reset0~\cite{KammarLO13}. -% % -% \begin{equations} -% \sembr{\shiftz~k.M} &\defas& \Do\;\dec{Shift0}~(\lambda k.M)\\ -% \sembr{\resetz{M}} &\defas& -% \ba[t]{@{~}l}\Handle\;m\,\Unit\;\With\\ -% ~\ba{@{~}l@{~}c@{~}l} -% \Return\;x &\mapsto& x\\ -% \OpCase{\dec{Shift0}}{f}{k} &\mapsto& f\,k -% \ea -% \ea -% \end{equations} -% % +In Section~\ref{sec:tiny-unix-time} we implemented a time-sharing +system on top of a simple process model. However, the model lacks a +process synchronisation facility. It is somewhat difficult to cleanly +add support for synchronisation to the implementation as it is in +Section~\ref{sec:tiny-unix-time}. Firstly, because the interface of +$\Fork : \UnitType \to \Bool$ only gives us two possible process +identifiers: $\True$ and $\False$, meaning at any point we can only +identify two processes. Secondly, and more importantly, some state is +necessary to implement synchronisation, but the current implementation +of process scheduling is split amongst two handlers and one auxiliary +function, all of which need to coordinate their access and +manipulation of the state cell. One option is to use some global state +via the interface from Section~\ref{sec:tiny-unix-io}, which has the +advantage of making the state manipulation within the scheduler +modular, but it also has the disadvantage of exposing the state as an +implementation detail --- and it comes with all the caveats of +programming with global state. A parameterised handler provides an +elegant solution, which lets us internalise the state within the +scheduler. -% Shallow handlers can be used to simulate control0 and -% prompt0~\cite{KammarLO13}. -% % -% \begin{equations} -% \sembr{\Controlz~k.M} &\defas& \Do\;\dec{Control0}~(\lambda k.M)\\ -% \sembr{\Promptz~M} &\defas& -% \bl -% prompt0\,(\lambda\Unit.M)\\ -% \textbf{where}\; -% \bl -% prompt0~m \defas -% \ba[t]{@{~}l}\ShallowHandle\;m\,\Unit\;\With\\ -% ~\ba{@{~}l@{~}c@{~}l} -% \Return\;x &\mapsto& x\\ -% \OpCase{\dec{Control0}}{f}{k} &\mapsto& prompt0\,(\lambda\Unit.f\,k) -% \ea -% \ea -% \el -% \el -% \end{equations} +We will see how a parameterised handler enables us to implement a +richer process model supporting synchronisation with ease. The effect +signature of process concurrency is as follows. % -%Recursive types are required to type the image of this translation - -\paragraph{\citeauthor{Longley09}'s catch-with-continue} +\[ + \Co \defas \{\UFork : \UnitType \opto \Int; \Wait : \Int \opto \UnitType; \Interrupt : \UnitType \opto \UnitType\} +\] % -The control operator \emph{catch-with-continue} (abbreviated -catchcont) is a delimited extension of the $\Catch$ operator. It was -designed by \citet{Longley09} in 2008~\cite{LongleyW08}. Its origin is -in game semantics, in which program evaluation is viewed as an -interactive dialogue with the ambient environment~\cite{Hyland97} --- -this view aligns neatly with the view of effect handler oriented -programming. Curiously, we can view catchcont and effect handlers as -``siblings'' in the sense that \citeauthor{Longley09} and -\citeauthor{PlotkinP09} them respectively, during the same time, -whilst working in the same department. However, the relationship is -presently just `spiritual' as no formal connections have been drawn -between the two operators. +The operation $\UFork$ models \UNIX{} +\emph{fork}~\cite{RitchieT74}. It is generalisation of the $\Fork$ +operation from Section~\ref{sec:tiny-unix-time}. The operation is +intended to return twice: once to the parent process with a unique +process identifier for the child process, and a second time to the +child process with the zero identifier. The $\Wait$ operation takes a +process identifier as argument and then blocks the invoking process +until the process associated with the provided identifier has +completed. The $\Interrupt$ operation is the same as in +Section~\ref{sec:tiny-unix-time}; it temporarily suspends the invoking +process in order to let another process run. -The catchcont operator appears as a computation form in our calculus. -% -\begin{syntax} - &M,N \in \CompCat &::=& \cdots \mid \Catchcont~f.M -\end{syntax} +The main idea is to use the state cell of a parameterised handler to +manage the process queue and to keep track of the return values of +completed processes. The scheduler will return an association list of +process identifiers mapped to the return value of their respective +process when there are no more processes to be run. The process queue +will consist of reified processes, which we will represent using +parameterised resumptions. To make the type signatures understandable +we will make use of three mutually recursive type aliases. % -Unlike other delimited control operators, $\Catchcont$ does not -introduce separate explicit syntactic constructs for the control -delimiter and control reifier. Instead it leverages the higher-order -facilities of $\lambda$-calculus: the syntactic construct $\Catchcont$ -play the role of control delimiter and the name $f$ of function type -is the name of the control reifier. \citet{LongleyW08} describe $f$ as -a `dummy variable'. - -The typing rule for $\Catchcont$ is as follows. +\[ + \ba{@{~}l@{~}l@{~}c@{~}l} + \Proc &\alpha~\varepsilon &\defas& \Sstate~\alpha~\varepsilon \to \List\,\Record{\Int;\alpha} \eff \varepsilon\\ + \Pstate &\alpha~\varepsilon &\defas& [\Ready:\Proc~\alpha~\varepsilon;\Blocked:\Record{\Int;\Proc~\alpha~\varepsilon}]\\ + \Sstate &\alpha~\varepsilon &\defas& \Record{q:\List\,\Record{\Int;\Pstate~\alpha~\varepsilon};done:\List~\alpha;pid:\Int;pnext:\Int}\\ + \ea +\] % -\begin{mathpar} - \inferrule* - {\typ{\Gamma, f : A \to B}{M : C \times D} \\ \Ground\;C} - {\typ{\Gamma}{\Catchcont~f.M : C \times ((A \to B) \to D) + (A \times (B \to (A \to B) \to C \times D))}} -\end{mathpar} +The $\Proc$ alias is the type of reified processes. It is defined as a +function that takes the current scheduler state and returns an +association list of $\alpha$s indexed by integers. This is almost the +type of a parameterised resumption as the only thing missing is the a +component for the interpretation of an operation. % -The computation handled by $\Catchcont$ must return a pair, where the -first component must be a ground value. This restriction ensures that -the value is not a $\lambda$-abstraction, which means that the value -cannot contain any further occurrence of the control reifier $f$. The -second component is unrestricted, and thus, it may contain further -occurrences of $f$. If $M$ fully reduces then $\Catchcont$ returns a -pair consisting of a ground value (i.e. an answer from $M$) and a -continuation function which allow $M$ to yield further -`answers'. Alternatively, if $M$ invokes the control reifier $f$, then -$\Catchcont$ returns a pair consisting of the argument supplied to $f$ -and the current continuation of the invocation of $f$. - -The operational rules for $\Catchcont$ are as follows. -% -\begin{reductions} - \slab{Value} & - \Catchcont~f . \Record{V;W} &\reducesto& \Inl\; \Record{V;\lambda\,f. W}\\ - \slab{Capture} & - \Catchcont~f .\EC[\,f\,V] &\reducesto& \Inr\; \Record{V; \lambda x. \lambda f. \Continue~\cont_{\EC}~x}\\ - \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V] -\end{reductions} +The second alias $\Pstate$ enumerates the possible process +states. Either a process is \emph{ready} to be run or it is +\emph{blocked} on some other process. The payload of the $\Ready$ tag +is the process to run. The $\Blocked$ tag is parameterised by a pair, +where the first component is the identifier of the process that is +being waited on and the second component is the process to be +continued when the other process has completed. % -The \slab{Value} makes sure to bind any lingering instances of $f$ in -$W$ before escaping the delimiter. The \slab{Capture} rule reifies and -aborts the current evaluation up to, but no including, the delimiter, -which gets uninstalled. The reified evaluation context gets stored in -the second component of the returned pair. Importantly, the second -$\lambda$-abstraction makes sure to bind any instances of $f$ in the -captured evaluation context once it has been reinstated by the -\slab{Resume} rule. +The third alias $\Sstate$ is the type of scheduler state. It is a +quadruple, where the first label $q$ is the process queue. It is +implemented as an association list indexed by process identifiers. The +second label $done$ is used to store the return values of completed +processes. The third label $pid$ is used to remember the identifier of +currently executing process, and the fourth label $pnext$ is used to +compute a unique identifier for new processes. -Let us consider an example use of catchcont to compute a tree -representing the interaction between a second-order function and its -first-order parameter. +We will abstract some of the scheduling logic into an auxiliary +function $\runNext$, which is responsible for dequeuing and running +the next process from the queue. % \[ \bl - \dec{odd} : (\Int \to \Bool) \to \Bool \times \UnitType\\ - \dec{odd}~f \defas \Record{\dec{xor}\,(f~0)\,(f~1); \Unit} + \runNext : \Sstate~\alpha~\varepsilon \to \List~\alpha \eff \varepsilon\\ + \runNext~st \defas + \bl + \Case\;st.q\;\{\\ + ~\bl + \nil \mapsto st.done\\ + \Record{pid;\Blocked\,\Record{pid';resume}} \cons q' \mapsto\\ + \quad\bl + \Let\;st' \revto \Record{st \;\With\; q = q' \concat [\Record{pid;\Blocked\,\Record{pid';resume}}]}\;\In\\ + \runNext~st' + \el\\ + \Record{pid;\Ready~resume} \cons q' \mapsto\\ + \quad\bl + \Let\;st' \revto \Record{st \;\With\; q = q'; pid = pid}\;\In\\ + resume~st'\,\} + \el + \el + \el \el \] % -The function $\dec{odd}$ expects its environment to provide it with an -implementation of a single operation of type $\Int \to \Bool$. The -body of $\dec{odd}$ invokes, or queries, this operation twice with -arguments $0$ and $1$, respectively. The results are tested using -exclusive-or. - -Now, let us implement the environment for $\dec{odd}$. +The function operates on the scheduler state. It first performs a case +split on the process queue. There are three cases to consider. +\begin{enumerate} + \item The queue is empty. Then the function returns the list $done$, + which is the list of process return values. + \item The next process is blocked. Then the process is appended on + to the end of the queue, and $\runNext$ is applied recursively to + the scheduler state $st'$ with the updated queue. + \item The next process is ready. Then the $q$ and $pid$ fields + within the scheduler state are updated accordingly. The reified + process $resume$ is applied to the updated scheduler state $st'$. +\end{enumerate} +% +Evidently, this function may enter an infinite loop if every process +is in blocked state. This may happen if we deadlock any two processes +by having them wait on one another. Using this function we can define +a handler that implements a process scheduler. % \[ \bl - \dec{Dialogue} \defas [!:\Int;?:\Record{\Bool,\dec{Dialogue},\dec{Dialogue}}] \smallskip\\ - \dec{env} : ((\Int \to \Bool) \to \Bool \times \UnitType) \to \dec{Dialogue}\\ - \dec{env}~m \defas + \scheduler : \Record{\alpha \eff \{\Co;\varepsilon\};\Sstate~\alpha~\varepsilon} \Harrow^\param \List\,\Record{\Int;\alpha} \eff \varepsilon\\ + \scheduler \defas \bl - \Case\;\Catchcont~f.m~f\;\{ - \ba[t]{@{}l@{~}c@{~}l} - \Inl~\Record{ans;\Unit} &\mapsto& \dec{!}ans;\\ - \Inr~\Record{q;k} &\mapsto& \dec{?}q\,(\dec{env}~k~\True)\,(\dec{env}~k~\False)\} - \ea - \el + st.\\ + \bl + \Return\;x \mapsto \\ + \quad\bl + \Let\;done' \revto \Record{st.pid;x} \cons st.done\;\In\\ + \runNext\,\Record{st\;\With\;done = done'} + \el\\ + \OpCase{\UFork}{\Unit}{resume} \mapsto\\ + \quad\bl + \Let\;resume' \revto \lambda st.resume\,\Record{0;st}\;\In\\ + \Let\;pid \revto st.pnext \;\In\\ + \Let\;q' \revto st.q \concat [\Record{pid;\Ready~resume'}]\;\In\\ + \Let\;st' \revto \Record{st\;\With\;q = q'; pnext = pid + 1}\;\In\\ + resume\,\Record{pid;st'} + \el\\ + \OpCase{\Wait}{pid}{resume} \mapsto\\ + \quad\bl + \Let\;resume' \revto \lambda st.resume~\Record{\Unit;st}\;\In\\ + \Let\;q' \revto + \bl + \If\;\dec{has}\,\Record{pid;st.q}\\ + \Then\;st.q \concat [\Record{st.pid;\Blocked\,\Record{pid;resume'}}]\\ + \Else\;st.q \concat [\Record{st.pid;\Ready~resume'}] + \el\\ + \In\;\runNext\,\Record{st\;\With\;q = q'} + \el\\ + \OpCase{\Interrupt}{\Unit}{resume} \mapsto\\ + \quad\bl + \Let\;resume' \revto \lambda st.resume\,\Record{\Unit;st}\;\In\\ + \Let\;q' \revto st.q \concat [\Record{st.pid;\Ready~resume'}]\;\In\\ + \runNext~\Record{st \;\With\; q = q'} + \el + \el + \el \el \] % -Type $\dec{Dialogue}$ represents the dialogue between $\dec{odd}$ and -its parameter. The data structure is a standard binary tree with two -constructors: $!$ constructs a leaf holding a value of type $\Int$ and -$?$ constructs an interior node holding a value of type $\Bool$ and -two subtrees. The function $\dec{env}$ implements the environment that -$\dec{odd}$ will be run in. This function evaluates its parameter $m$ -under $\Catchcont$ which injects the operation $f$. If $m$ returns, -then the left component gets tagged with $!$, otherwise the argument -to the operation $q$ gets tagged with a $?$ along with the subtrees -constructed by the two recursive applications of $\dec{env}$. -% -% The primitive structure of catchcont makes it somewhat fiddly to -% program with it compared to other control operators as we have to -% manually unpack the data. - -The following derivation gives the high-level details of how -evaluation proceeds. -% -\begin{figure} -\begin{center} - \scalebox{1.3}{\SXORTwoModel} - \end{center} - \caption{Visualisation of the result obtained by - $\dec{env}~\dec{odd}$.}\label{fig:decision-tree-cc} -\end{figure} -% -\begin{derivation} - &\dec{env}~\dec{odd}\\ - \reducesto^+ & \reason{$\beta$-reduction}\\ - &\bl - \Case\;\Catchcont~f.\;\Record{\dec{xor}\,(f~0)\,(f~1);\Unit}\{\cdots\} - % \ba[t]{@{}l@{~}c@{~}l} - % \Inl~\Record{ans;\Unit} &\mapsto& \dec{!}ans;\\ - % \Inr~\Record{q;k} &\mapsto& \dec{?}q\,(\dec{env}~k~\True)\,(\dec{env}~k~\False)\} - % \ea - \el\\ - \reducesto& \reason{\slab{Capture} $\EC = \Record{\dec{xor}\,[\,]\,(f~1),\Unit}$}\\ - &\bl - \Case\;\Inr\,\Record{0;\lambda x.\lambda f.\qq{\cont_{\EC}}\,x}\;\{\cdots\} - % \ba[t]{@{}l@{~}c@{~}l} - % \Inl~\Record{ans;\Unit} &\mapsto& \dec{!}ans;\\ - % \Inr~\Record{q;k} &\mapsto& \dec{?}q\,\bl (\dec{env}~\qq{\cont_{\EC}}~\True)\\(\dec{env}~\qq{\cont_{\EC}}~\False)\}\el - % \ea - \el\\ - \reducesto^+& \reason{\slab{Resume} $\EC$ with $\True$}\\ - &\dec{?}0\,(\Case\;\Catchcont~f.\;\Record{\dec{xor}~\True\,(f~1);\Unit}\{\cdots\})\,(\dec{env}~\qq{\cont_{\EC}}~\False)\\ - \reducesto^+& \reason{\slab{Capture} $\EC' = \Record{\dec{xor}~\True\,[\,], \Unit}$}\\ - &\dec{?}0\,(\dec{?}1\,(\dec{env}~\qq{\cont_{\EC'}}~\True)\,(\dec{env}~\qq{\cont_{\EC'}}~\False))\,(\dec{env}~\qq{\cont_{\EC}}~\False)\\ - \reducesto^+& \reason{\slab{Resume} $\EC'$ with $\True$}\\ - &\dec{?}0\,\bl(\dec{?}1\,(\Case\;\Catchcont~f.\Record{\dec{xor}~\True~\True;\Unit}\;\{\cdots\})\,(\dec{env}~\qq{\cont_{\EC'}}~\False))\\(\dec{env}~\qq{\cont_{\EC}}~\False)\el\\ - \reducesto^+& \reason{\slab{Value}}\\ - &\dec{?}0\,\bl(\dec{?}1\,(\Case\;\Inl~\Record{\False;\Unit}\;\{\cdots\})\,(\dec{env}~\qq{\cont_{\EC'}}~\False))\\(\dec{env}~\qq{\cont_{\EC}}~\False)\el\\ - \reducesto& \reason{$\beta$-reduction}\\ - &\dec{?}0\,\bl(\dec{?}1\,\dec{!}\False\,(\dec{env}~\qq{\cont_{\EC'}}~\False))\,(\dec{env}~\qq{\cont_{\EC}}~\False)\el\\ - \reducesto^+&\reason{Same reasoning}\\ - &?0\,(?1\,!\False\,!\True)\,(?1\,!\True\,!\False) -\end{derivation} +The handler definition $\scheduler$ takes as input a computation that +computes a value of type $\alpha$ whilst making use of the concurrency +operations from the $\Co$ signature. In addition it takes the initial +scheduler state as input. Ultimately, the handler returns a +computation that computes a list of $\alpha$s, where all the +$\Co$-operations have been handled. % -Figure~\ref{fig:decision-tree-cc} visualises this result as a binary -tree. The example here does not make use of the `continuation -component', the interested reader may consult \citet{LongleyW08} for -an example usage. -% \subsection{Second-class control operators} -% Coroutines, async/await, generators/iterators, amb. +In the definition the scheduler state is bound by the name $st$. -% Backtracking: Amb~\cite{McCarthy63}. +The $\Return$ case is invoked when a process completes. The return +value $x$ is paired with the identifier of the currently executing +process and consed onto the list $done$. Subsequently, the function +$\runNext$ is invoked in order to the next ready process. -% Coroutines~\cite{DahlDH72} as introduced by Simula -% 67~\cite{DahlMN68}. The notion of coroutines was coined by Melvin -% Conway, who used coroutines as a code idiom in assembly -% programs~\cite{Knuth97}. Canonical reference for implementing -% coroutines with call/cc~\cite{HaynesFW86}. +The $\UFork$ case implements the semantics for process forking. First +the child process is constructed by abstracting the parameterised +resumption $resume$ such that it becomes an unary state-accepting +function, which can be ascribed type $\Proc~\alpha~\varepsilon$. The +parameterised resumption applied to the process identifier $0$, which +lets the receiver know that it assumes the role of child in the +parent-child relationship amongst the processes. The next line +retrieves the unique process identifier for the child. Afterwards, the +child process is pushed on to the queue in ready state. The next line +updates the scheduler state with the new queue and a new unique +identifier for the next process. Finally, the parameterised resumption +is applied to the child process identifier and the updated scheduler +state. -\section{Programming continuations} -\label{sec:programming-continuations} -%Blind vs non-blind backtracking. Engines. Web -% programming. Asynchronous -% programming. Coroutines. +The $\Wait$ case implements the synchronisation operation. The +parameter $pid$ is the identifier of the process that the invoking +process wants to wait on. First we construct an unary state-accepting +function. Then we check whether there exists a process with identifier +$pid$ in the queue. If there is one, then we enqueue the current +process in blocked state. If no such process exists (e.g. it may +already have finished), then we enqueue the current process in ready +state. Finally, we invoke $\runNext$ with the scheduler state updated +with the new process queue in order to run the next ready process. -Amongst the first uses of continuations were modelling of unrestricted -jumps, such as \citeauthor{Landin98}'s modelling of \Algol{} labels -and gotos using the J -operator~\cite{Landin65,Landin65a,Landin98,Reynolds93}. +The $\Interrupt$ case suspends the current process by enqueuing it in +ready state, and dequeuing the next ready process. -Backtracking is another early and prominent use of continuations. For -example, \citet{Burstall69} used the J operator to implement a -heuristic-driven search procedure with continuation-backed -backtracking for tree-based search. +Using this handler we can implement version 2 of the time-sharing +system. % -Somewhat related to backtracking, \citet{FriedmanHK84} posed the -\emph{devils and angels problem} as an example that has no direct -solution in a programming language without first-class control. Any -solution to the devils and angels problem involves extensive -manipulation of control to jump both backwards and forwards to resume -computation. - -If the reader ever find themselves in a quiz show asked to single out -a canonical example of continuation use, then implementation of -concurrency would be a qualified guess. Cooperative concurrency in -terms of various forms of coroutines as continuations occur so -frequently in the literature and in the wild that they have become -routine. +\[ + \bl + \timesharee : (\UnitType \to \alpha \eff \Co) \to \List\,\Record{\Int;\alpha}\\ + \timesharee~m \defas + \bl + \Let\;st_0 \revto \Record{q=\nil;done=\nil;pid=1;pnext=2}\;\In\\ + \ParamHandle\;m\,\Unit\;\With\; \scheduler~st_0 + \el + \el +\] % -\citet{HaynesFW86} published one of the first implementations of -coroutines using first-class control. +The computation $m$, which may perform any of the concurrency +operations, is handled by the parameterised handler $\scheduler$. The +parameterised handler definition is applied to the initial scheduler +state, which has an empty process queue, an empty done list, and it +assigns the first process the identifier $1$, and sets up the +identifier for the next process to be $2$. + +With $\UFork$ and $\Wait$ we can implement the \emph{init} process, +which is the initial startup process in +\UNIX{}~\cite{RitchieT74}. This process remains alive until the +operating system is shutdown. It is the ancestor of every process +created by the operating system. % -Preemptive concurrency in the form of engines were implemented by -\citet{DybvigH89}. An engine is a control abstraction that runs -computations with an allotted time budget~\cite{HaynesF84}. They used -continuations to represent strands of computation and timer interrupts -to suspend continuations. +\[ + \bl + \init : (\Unit \to \alpha \eff \varepsilon) \to \alpha \eff \{\Co;\varepsilon\}\\ + \init~main \defas + \bl + \Let\;pid \revto \Do\;\UFork~\Unit\;\In\\ + \If\;pid = 0\\ + \Then\;main\,\Unit\\ + \Else\;\Do\;\Wait~pid + \el + \el +\] % -\citet{KiselyovS07a} used delimited continuations to explain various -phenomena of operating systems, including multi-tasking and file -systems. +We implement $\init$ as a higher-order function. It takes a main +routine that will be applied when the system has been started. The +function first performs $\UFork$ to duplicate itself. The child branch +executes the $main$ routine, whilst the parent branch waits on the +child. + +Now we can plug everything together. % -On the web, \citet{Queinnec04} used continuations to model the -client-server interactions. This model was adapted by -\citet{CooperLWY06} in Links with support for an Erlang-style -concurrency model~\cite{ArmstrongVW93}. +\[ + \ba{@{~}l@{~}l} + &\bl + \runState\,\Record{\dec{fs}_0;\fileIO\,(\lambda\Unit.\\ + \quad\timesharee\,(\lambda\Unit.\\ + \qquad\dec{interruptWrite}\,(\lambda\Unit.\\ + \qquad\quad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ + \qquad\qquad\status\,(\lambda\Unit. + \init\,(\lambda\Unit. + \ba[t]{@{}l} + \Let\;pid \revto \Do\;\UFork\,\Unit\;\In\\ + \If\;pid = 0\\ + \Then\;\bl + \su~\Alice; + \quoteRitchie\,\Unit + \el\\ + \Else\; \su~\Bob; \Do\;\Wait~pid; \quoteHamlet\,\Unit))})))} + \ea + \el \smallskip\\ + \reducesto^+& + \bl + \Record{ + \ba[t]{@{}l} + [\Record{1;0};\Record{2;0};\Record{3;0}];\\ + \Record{ + \ba[t]{@{}l} + dir=[\Record{\strlit{stdout};0}];\\ + ilist=[\Record{0;\Record{lno=1;loc=0}}];\\ + dreg=[ + \ba[t]{@{}l} + \Record{0; + \ba[t]{@{}l@{}l} + \texttt{"}&\texttt{UNIX is basically a simple operating system, but }\\ + &\texttt{you have to be a genius to understand the simplicity.\nl}\\ + &\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\ + &\texttt{Whether 'tis nobler in the mind to suffer\nl{}"}}] + \ea\\ + lnext=1; inext=1}}\\ + \ea + \ea + \ea\\ + : \Record{\List\,\Record{\Int;\Int}; \FileSystem} + \el + \ea +\] % -\citet{Leijen17a} and \citet{DolanEHMSW17} gave two different ways of -implementing the asynchronous programming operator async/await as a -user-definable library. +Process number $1$ is $\init$, which forks itself to run its +argument. The argument runs as process $2$, which also forks itself, +thus creating a process $3$. Process $3$ executes the child branch, +which switches user to $\Alice$ and invokes the $\quoteRitchie$ +process which writes to standard out. Process $2$ executes the parent +branch, which switches user to $\Bob$ and waits for the child process +to complete before it invokes the routine $\quoteHamlet$ which also +writes to standard out. % -In the setting of distributed programming, \citet{BracevacASEEM18} -describe a modular event correlation system that makes crucial use of -effect handlers. \citeauthor{Bracevec19}'s PhD dissertation -explicates the theory, design, and implementation of event correlation -by way of effect handlers~\cite{Bracevec19}. - -Continuations have also been used in meta-programming to speed up -partial evaluation and -multi-staging~\cite{LawallD94,KameyamaKS11,OishiK17,Yallop17,WeiBTR20}. Let -insertion is a canonical example of use of continuations in -multi-staging~\cite{Yallop17}. +It is evident from looking at the file system state that the writes to +standard out has not been interleaved as the contents of +$\strlit{stdout}$ appear in order. We can also see from the process +completion list that Alice's process (pid $3$) is the first to +complete with status $0$, and the second to complete is Bob's process +(pid $2$) with status $0$, whilst the last process to complete is the +$\init$ process (pid $1$) with status $0$. -Probabilistic programming is yet another application domain of -continuations. \citet{KiselyovS09} used delimited continuations to -speed up probabilistic programs. \citet{GorinovaMH20} used -continuations to achieve modularise probabilistic programs and to -provide a simple and efficient mechanism for reparameterisation of -inference algorithms. +\paragraph{Retrofitting fork} In the previous program we replaced the +original implementation of $\timeshare$ +(Section~\ref{sec:tiny-unix-time}), which handles invocations of +$\Fork : \UnitType \opto \Bool$, by $\timesharee$, which handles the +more general operation $\UFork : \UnitType \opto \Int$. In practice, +we may be unable to dispense of the old interface so easily, meaning +we have to retain support for, say, legacy reasons. As we have seen +previously we can an operation in terms of another operation. Thus to +retain support for $\Fork$ we simply have to insert a handler under +$\timesharee$ which interprets $\Fork$ in terms of $\UFork$. The +operation case of this handler would be akin to the following. % -In the subject of differentiable programming \citet{WangZDWER19} -explained reverse-mode automatic differentiation operators in terms of -delimited continuations. - -The aforementioned applications of continuations are by no means -exhaustive, though, the diverse application spectrum underlines the -versatility of continuations. +\[ + \OpCase{\Fork}{\Unit}{resume} \mapsto + \bl + \Let\;pid \revto \Do\;\UFork~\Unit\;\In\\ + resume\,(pid \neq 0) + \el +\] +% +The interpretation of $\Fork$ inspects the process identifier returned +by the $\UFork$ to determine the role of the current process in the +parent-child relationship. If the identifier is nonzero, then the +process is a parent, hence $\Fork$ should return $\True$ to its +caller. Otherwise it should return $\False$. This preserves the +functionality of the legacy code. -\section{Constraining continuations} -\label{sec:constraining-continuations} +\section{Related work} +\label{sec:unix-related-work} -\citet{FriedmanH85} advocated for constraining the power of -(undelimited) continuations~\cite{HaynesF87}. +\paragraph{Programming languages with handlers} The work presented in +this chapter has been retrofitted on to the programming language +Links~\cite{HillerstromL16,HillerstromL18}. A closely related +programming language with handlers is \citeauthor{Leijen17}'s Koka, +which has been retrofitted with ordinary deep and parameterised effect +handlers~\cite{Leijen17}. In Koka effects are nominal, meaning an +effect and its constructors must be declared before use, which is +unlike the structural approach taken in this chapter. Koka also tracks +effects via an effect system based on \citeauthor{Leijen05}-style row +polymorphism~\cite{Leijen05,Leijen14}, where rows are interpreted as +multisets which means an effect can occur multiple times in an effect +row. The ability to repeat effects provide a form for effect scoping +in the sense that an effect instance can shadow another. A handler +handles only the first instance of a repeated effect, leaving the +remaining instances for another handler. Consequently, the order of +repeated effect instances matter and it can therefore be situational +useful to manipulate the order of repeated instances by way of +so-called \emph{effect masking}. % -Even though, they were concerned with callcc and undelimited -continuations some of their arguments are applicable to other control -operators and delimited continuations. +The notion of effect masking was formalised by \citet{BiernackiPPS18} +and generalised by \citet{ConventLMM20}. % -For example, they argued in favour of restricting continuations to be -one-shot, which means continuations may only be invoked once. Firstly, -because one-shot continuations admit particularly efficient -implementations. Secondly, many applications involve only single use -of continuations. Thirdly, one-shot continuations interact more -robustly with resources, such as file handles, than general multi-shot -continuations, because multiple use of a continuation may accidentally -interact with a resource after it has been released. -One-shot continuations by themselves are no saving grace for avoiding -resource leakage as they may be dropped or used to perform premature -exits from a block with resources. For example, Racket provides the -programmer with a facility known as \emph{dynamic-wind} to protect a -context with resources such that non-local exits properly release -whatever resources the context has acquired~\cite{Flatt20}. -% -An alternative approach is taken by Multicore OCaml, whose -implementation of effect handlers with one-shot continuations provides -both a \emph{continue} primitive for continuing a given continuation -and a \emph{discontinue} primitive for aborting a given -continuation~\cite{DolanWSYM15,DolanEHMSW17}. The latter throws an -exception at the operation invocation site to which can be caught by -local exception handlers to release resources properly. +\citet{BiernackiPPS18} designed Helium, which is a programming +language that features a rich module system, deep handlers, and +\emph{lexical} handlers~\cite{BiernackiPPS20}. Lexical handlers +\emph{bind} effectful operations to specific handler +instances. Operations remain bound for the duration of +computation. This makes the nature of lexical handlers more static +than ordinary deep handlers, as for example it is not possible to +dynamically overload the interpretation of residual effects of a +resumption invocation as in Section~\ref{sec:tiny-unix-env}. % -This approach is also used by \citet{Fowler19}, who uses a -substructural type system to statically enforce the use of -continuations, either by means of a continue or a discontinue. - -% For example callec is a variation of callcc where continuation -% invocation is only defined during the dynamic extent of -% callec~\cite{Flatt20}. +The mathematical foundations for lexical handlers has been developed +by \citet{Geron19}. -\section{Implementing continuations} -\label{sec:implementing-continuations} +The design of the Effekt language by \citet{BrachthauserSO20b} +resolves around the idea of lexical handlers for efficiency. Effekt +takes advantage of the static nature of lexical handlers to eliminate +the dynamic handler lookup at runtime by tying the correct handler +instance directly to an operation +invocation~\cite{BrachthauserS17,SchusterBO20}. The effect system of +Effekt is based on intersection types, which provides a limited form +of effect polymorphism~\cite{BrachthauserSO20b}. A design choice that +means it does not feature first-class functions. -There are numerous strategies for implementing continuations. There is -no best implementation strategy. Each strategy has different -trade-offs, and as such, there is no ``best'' strategy. In this -section, I will briefly outline the gist of some implementation -strategies and their trade-offs. For an in depth analysis the -interested reader may consult the respective work of -\citet{ClingerHO88} and \citet{FarvardinR20}, which contain thorough -studies of implementation strategies for first-class continuations. -% -Table~\ref{tbl:ctrl-operators-impls} lists some programming languages -with support for first-class control operators and their -implementation strategies. +The Frank language by \citet{LindleyMM17} is born and bred on shallow +effect handlers. One of the key novelties of Frank is $n$-ary shallow +handlers, which generalise ordinary unary shallow handlers to be able +to handle multiple computations simultaneously. Another novelty is the +effect system, which is based on a variation of +\citeauthor{Leijen05}-style row polymorphism, where the programmer +rarely needs to mention effect variables. This is achieved by +insisting that the programmer annotates each input argument with the +particular effects handled at the particular argument position as well +as declaring what effects needs to be handled by the ambient +context. Each annotation is essentially an incomplete row. They are +made complete by concatenating them and inserting a fresh effect +variable. -\begin{table} - \centering - \begin{tabular}{| l | >{\raggedright}p{4.3cm} | l |} - \hline - \multicolumn{1}{|c|}{\textbf{Language}} & \multicolumn{1}{c |}{\textbf{Control operators}} & \multicolumn{1}{c|}{\textbf{Implementation strategies}}\\ - \hline - Eff & Effect handlers & Virtual machine, interpreter \\ - \hline - Effekt & Lexical effect handlers & CPS\\ - \hline - Frank & N-ary effect handlers & CEK machine \\ - % \hline - % Gauche & callcc, shift/reset & Virtual machine \\ - \hline - Helium & Effect handlers & CEK machine \\ - \hline - Koka & Effect handlers & Continuation monad\\ - \hline - Links & Effect handlers, escape & CEK machine, CPS\\ - \hline - MLton & callcc & Stack copying\\ - \hline - Multicore OCaml & Affine effect handlers & Segmented stacks\\ - \hline - OchaCaml & shift/reset & Virtual machine\\ - \hline - Racket & callcc, \textCallcomc{}, cupto, fcontrol, control/prompt, shift/reset, splitter, spawn & Segmented stacks\\ - \hline - % Rhino JavaScript & JI & Interpreter \\ - % \hline - Scala & shift/reset & CPS\\ - \hline - SML/NJ & callcc & CPS\\ - \hline - Wasm/k & control/prompt & Virtual machine \\ - \hline - \end{tabular} - \caption{Some languages and their implementation strategies for continuations.}\label{tbl:ctrl-operators-impls} -\end{table} -% -The control stack provides a adequate runtime representation of -continuations as the contiguous sequence of activation records quite -literally represent what to do next. -% -Thus continuation capture can be implemented by making a copy of the -current stack (possibly up to some delimiter), and continuation -invocation as reinstatement of the stack. This implementation strategy -works well if continuations are captured infrequently. The MLton -implementation of Standard ML utilises this strategy~\cite{Fluet20}. -A slight variation is to defer the first copy action until the -continuation is invoked, which requires marking the stack to remember -which sequence of activation records to copy. +\citeauthor{BauerP15}'s Eff language was the first programming +language designed from the ground up with effect handlers in mind. It +features only deep handlers~\cite{BauerP15}. A previous iteration of +the language featured an explicit \emph{effect instance} system. An +effect instance is a sort of generative interface, where the +operations are unique to each instance. As a result it is possible to +handle two distinct instances of the same effect differently in a +single computation. Their system featured a type-and-effect system +with support for effect inference~\cite{Pretnar13,BauerP13}, however, +the effect instance system was later dropped to in favour of a vanilla +nominal approach to effects and handlers. -Obviously, frequent continuation use on top of a stack copying -implementation can be expensive time wise as well as space wise, -because with undelimited continuations multiple copies of the stack -may be alive simultaneously. -% -Typically the prefix of copies will be identical, which suggests they -ought to be shared. One way to achieve optimal sharing is to move from -a contiguous stack to a non-contiguous stack representation, -e.g. representing the stack as a heap allocated linked list of -activation records~\cite{Danvy87}. With such a representation copying -is a constant time and space operation, because there is no need to -actually copy anything as the continuation is just a pointer into the -stack. -% -The disadvantage of this strategy is that it turns every operation -into an indirection. +Multicore OCaml is, at the time of writing, an experimental branch of +the OCaml programming language, which aims to extend OCaml with effect +handlers for multicore and concurrent +programming~\cite{DolanWM14,DolanWSYM15}. The current incarnation +features untracked nominal effects and deep handlers with single-use +resumptions. -Segmented stacks provide a middle ground between contiguous stack and -non-contiguous stack representations. With this representation the -control stack is represented as a linked list of contiguous stacks -which makes it possible to only copy a segment of the stack. The -stacks grown and shrink dynamically as needed. This representation is -due to \citet{HiebDB90}. It is used by Chez Scheme, which is the -runtime that powers Racket~\cite{FlattD20}. -% -For undelimited continuations the basic idea is to create a pointer to -the current stack upon continuation capture, and then allocate a new -stack where subsequent computation happens. -% -For delimited continuations the control delimiter identify when a new -stack should be allocated. -% -A potential problem with this representation is \emph{stack - thrashing}, which is a phenomenon that occurs when a stack is being -continuously resized. -% -This problem was addressed by \citet{BruggemanWD96}, who designed a -slight variation of segmented stacks optimised for one-shot -continuations, which has been adapted by Multicore -OCaml~\cite{DolanEHMSW17}. +%\dhil{Possibly move to the introduction or background} -Full stack copying and segmented stacks both depend on being able to -manipulate the stack directly. This is seldom possible if the language -implementer do not have control over the target runtime, -e.g. compilation to JavaScript. However, it is possible to emulate -stack copying and segmented stacks in lieu of direct stack access. For -example, \citet{PettyjohnCMKF05} describe a technique that emulates -stack copying by piggybacking on the facile stack inception facility -provided by exception handlers in order to lazily reify the control -stack. +\paragraph{Effect-driven concurrency} +In their tutorial of the Eff programming language \citet{BauerP15} +implement a simple lightweight thread scheduler. It is different from +the schedulers presented in this section as their scheduler only uses +resumptions linearly. This is achieved by making the fork operation +\emph{higher-order} such that the operation is parameterised by a +computation. The computation is run under a fresh instance of the +handler. On one hand this approach has the benefit of making threads +cheap as it is no stack copying is necessary at runtime. On the other +hand it does not guarantee that every operation is handled uniformly +(when in the setting of deep handlers) as every handler in between the +fork operation invocation site and the scheduler handler needs to be +manually reinstalled when the computation argument is +run. Nevertheless, this is the approach to concurrency that +\citet{DolanWSYM15} have adopted for Multicore +OCaml~\cite{DolanWSYM15}. % -\citet{KumarBD98} emulated segmented stacks via threads. Each thread -has its own local stack, and as such, a collection of threads -effectively models segmented stacks. To actually implement -continuations as threads \citeauthor{KumarBD98} also made use of -standard synchronisation primitives. +In my MSc(R) dissertation I used a similar approach to implement a +cooperative version of the actor concurrency model of Links as a +user-definable Links library~\cite{Hillerstrom16}. This library was +used by a prototype compiler for Links to make the runtime as lean as +possible~(this compiler hooked directly into the backend of the +Multicore OCaml compiler in order to produce native code for effect +handlers~\cite{HillerstromL16}). % -The advantage of these techniques is that they are generally -applicable and portable. The disadvantage is the performance overhead -induced by emulation. +This line of work was further explored by \citet{Convent17}, who +implemented various cooperative actor-based concurrency abstractions +using effect handlers in the Frank programming +language. \citet{Poulson20} expanded upon this work by investigating +ways to handle preemptive concurrency. -Abstract and virtual machines are a form of full machine emulation. An -abstract machine is an idealised machine. Abstract machines, such as -the CEK machine~\cite{FelleisenF86}, are attractive because they -provide a suitably high-level framework for defining language -semantics in terms of control string manipulations, whilst admitting a -direct implementation. -% -We will discuss abstract machines in more detail in -Chapter~\ref{ch:abstract-machine}. -% -The term virtual machine typically connotes an abstract machine that -works on a byte code representation of programs, whereas the default -connotation of abstract machine is a machine that works on a rich -abstract syntax tree representation of programs. -% \citeauthor{Landin64}'s SECD machine was the -% first abstract machine for evaluating $\lambda$-calculus -% terms~\cite{Landin64,Danvy04}. +\citet{FowlerLMD19} uses effect handlers in the setting of linearly +typed fault-tolerant distributed programming. They use effect handlers +to codify an exception handling mechanism, which automatically +consumes linear resources. Exceptions are implemented as operations, +that are handled by \emph{cancelling} their resumptions. Cancellation +is a runtime primitive that gathers and closes active resources in the +computation represented by some resumption. + +\citet{DolanEHMSW17} and \citet{Leijen17a} gave two widely different +implementations of the async/await idiom using effect +handlers. \citeauthor{DolanEHMSW17}'s implementation is based on +higher-order operations with linearly used resumptions, whereas +\citeauthor{Leijen17a}'s implementation is based on first-order +operations with multi-shot resumptions, and thus, it is close in the +spirit to the schedulers we have considered in this chapter. + +\paragraph{Continuations and operating systems} +% The very first implementation of `lightweight threads' using +% continuations can possibly be credited to +% \citet{Burstall69}. \citeauthor{Burstall69} used +% \citeauthor{Landin65}'s J operator to arrange tree-based search, where +% each branch would be reified as a continuation and put into a +% queue. +The idea of using continuations to implement various facets of +operating systems is not new. However, most work has focused on +implementing some form of multi-tasking mechanism. % -% Either machine model has an explicit representation of the control -% state in terms of an environment and a control string. Thus either machine can to the -% interpretative overhead. -The disadvantage of abstract machines is their interpretative -overhead, although, techniques such as just-in-time compilation can be -utilised to reduce this overhead. +\citet{Wand80} implements a small multi-tasking kernel with support +for mutual exclusion and data protection using undelimited +continuations in the style of the catch operator of Scheme. +% \citet{HaynesFW86} codify coroutines as library using call/cc. +\citet{DybvigH89} implements \emph{engines} using call/cc in Scheme +--- an engine is a kind of process abstraction which support +preemption. An engine runs a computation on some time budget. If +computation exceeds the allotted time budget, then it is +interrupted. They represent engines as reified continuations and use +the macro system of Scheme to insert clock ticks at appropriate places +in the code. % \citet{HiebD90} also design the \emph{spawn-controller} +% operator for programming tree-based concurrency abstractions. +\citet{KiselyovS07a} develop a small fault-tolerant operating system +with multi-tasking support and a file system using delimited +continuations. Their file system is considerably more sophisticated +than the one we implemented in this chapter as it supports +transactional storage, meaning user processes can roll back actions +such as file deletion and file update. -Continuation passing style (CPS) is a canonical implementation -strategy for continuations --- the word `continuation' even feature in -its name. +\paragraph{Resumption monad} +The resumption monad is both a semantic and programmatic abstraction +for interleaving computation. \citet{Papaspyrou01} applies a +resumption monad transformer to construct semantic models of +concurrent computation. A resumption monad transformer, i.e. a monad +$T$ that transforms an arbitrary monad $M$ to a new monad $T~M$ with +commands for interrupting computation. % -CPS is a particular idiomatic notation for programs, where every -function takes an additional argument, the current continuation, as -input and every function call appears in tail position. Consequently, -every aspect of control flow is made explicit, which makes CPS a good -fit for implementing control abstraction. In classic CPS the -continuation argument is typically represented as a heap allocated -closure~\cite{Appel92}, however, as we shall see in -Chapter~\ref{ch:cps} richer representations of continuations are -possible. -% -At first thought it may seem that CPS will not work well in -environments that lack proper tail calls such as JavaScript. However, -the contrary is true, because the stackless nature of CPS means it can -readily be implemented with a trampoline~\cite{GanzFW99}. Alas, at the -cost of the indirection induced by the trampoline. +\citet{Harrison06} demonstrates the resumption monad as a practical +programming abstraction by implementing a small multi-tasking +kernel. \citeauthor{Harrison06} implements two variations of the +resumption monad: basic and reactive. The basic resumption monad is a +closed environment for interleaving different strands of +computations. It is closed in the sense that strands of computation +cannot interact with the ambient context of their environment. The +reactive resumption monad makes the environment open by essentially +registering a callback with an interruption action. This provides a +way to model system calls. + +The origins of the (semantic) resumption monad can be traced back to +at least \citet{Moggi90}, who described a monad for modelling the +interleaving semantics of \citeauthor{Milner75}'s \emph{calculus of + communicating systems}~\cite{Milner75}. + +The usage of \emph{resumption} in the name has a slightly different +meaning than the term `resumption' we have been using throughout this +chapter. We have used `resumption' to mean delimited continuation. In +the setting of the resumption monad it has a precise domain-theoretic +meaning. It is derived from \citeauthor{Plotkin76}'s domain of +resumptions, which in turn is derived from \citeauthor{Milner75}'s +domain of processes~\cite{Milner75,Plotkin76}. + +% \dhil{Briefly mention \citet{AtkeyJ15}} + + +% \begin{figure}[t] +% \centering +% \begin{tikzpicture}[node distance=4cm,auto,>=stealth'] +% \node[] (server) {\bfseries Bob (server)}; +% \node[left = of server] (client) {\bfseries Alice (client)}; +% \node[below of=server, node distance=5cm] (server_ground) {}; +% \node[below of=client, node distance=5cm] (client_ground) {}; +% % +% \draw (client) -- (client_ground); +% \draw (server) -- (server_ground); +% \draw[->,thick] ($(client)!0.25!(client_ground)$) -- node[rotate=-6,above,scale=0.7,midway]{SYN 42} ($(server)!0.40!(server_ground)$); +% \draw[<-,thick] ($(client)!0.56!(client_ground)$) -- node[rotate=6,above,scale=0.7,midway]{SYN 84;ACK 43} ($(server)!0.41!(server_ground)$); +% \draw[->,thick] ($(client)!0.57!(client_ground)$) -- node[rotate=-6,above,scale=0.7,midway]{ACK 85} ($(server)!0.72!(server_ground)$); +% \end{tikzpicture} +% \caption{Sequence diagram for the TCP handshake example.}\label{fig:tcp-handshake} +% \end{figure} + +% \paragraph{TCP threeway handshake} -\part{Programming} -\label{p:design} -\chapter{An ML-flavoured programming language based on rows} +% \chapter{An ML-flavoured programming language based on rows} +\chapter{ML-flavoured calculi for effect handler oriented programming} \label{ch:base-language} +\dhil{TODO merge this chapter with ``Effect handler calculi''} + In this chapter we introduce a core calculus, \BCalc{}, which we shall later use as the basis for exploration of design considerations for effect handlers. This calculus is based on \CoreLinks{} by @@ -6992,7 +7142,7 @@ tame function is one whose body can be translated and run directly on the database, whereas a wild function cannot. -\chapter{Effect handler oriented programming} +\section{Effect handler calculi} \label{ch:unary-handlers} % Programming with effect handlers is a dichotomy of \emph{performing} @@ -7723,12029 +7873,11608 @@ $\HCalc$. With this syntactic sugar in place we can program with second-order effectful functions without having to write down redundant information. +\section{Shallow handlers} +\label{sec:unary-shallow-handlers} -\section{Composing \UNIX{} with effect handlers} -\label{sec:deep-handlers-in-action} - -There are several analogies for effect handlers, e.g. as interpreters -for effects, folds over computation trees, etc. A particularly -compelling programmatic analogy for effect handlers is \emph{effect - handlers as composable operating systems}. Effect handlers and -operating systems share operational characteristics: an operating -system interprets a set of system commands performed via system calls, -which is similar to how an effect handler interprets a set of abstract -operations performed via operation invocations. This analogy was -suggested to me by James McKinna (personal communication, 2017). +Shallow handlers are an alternative to deep handlers. Shallow handlers +are defined as case-splits over computation trees, whereas deep +handlers are defined as folds. Consequently, a shallow handler +application unfolds only a single layer of the computation tree. % -The compelling aspect of this analogy is that we can understand a -monolithic and complex operating system like \UNIX{}~\cite{RitchieT74} -as a collection of effect handlers, or alternatively, a collection of -tiny operating systems, that when composed yield a semantics for -\UNIX{}. - -In this section we will take this reading of effect handlers -literally, and demonstrate how we can harness the power of (deep) -effect handlers to implement a \UNIX{}-style operating system with -multiple user sessions, time-sharing, and file i/o. We dub the system -\OSname{}. +Semantically, the difference between deep and shallow handlers is +analogous to the difference between \citet{Church41} and +\citet{Scott62} encoding techniques for data types in the sense that +the recursion is intrinsic to the former, whilst recursion is +extrinsic to the latter. % -It is a case study that demonstrates the versatility of effect -handlers, and shows how standard computational effects such as -\emph{exceptions}, \emph{dynamic binding}, \emph{nondeterminism}, and -\emph{state} make up the essence of an operating system. +Thus a fixpoint operator is necessary to make programming with shallow +handlers practical. -For the sake of clarity, we will occasionally make some blatant -simplifications, nevertheless the resulting implementation will -capture the essence of a \UNIX{}-like operating system. +Shallow handlers offer more flexibility than deep handlers as they do +not hard wire a particular recursion scheme. Shallow handlers are +favourable when catamorphisms are not the natural solution to the +problem at hand. % -The implementation will be composed of several small modular effect -handlers, that each handles a particular set of system commands. In -this respect, we will truly realise \OSname{} in the spirit of the -\UNIX{} philosophy~\cite[Section~1.6]{Raymond03}. - -% This case study demonstrates that we can decompose a monolithic -% operating system like \UNIX{} into a collection of specialised effect -% handlers. Each handler interprets a particular system command. +A canonical example of when shallow handlers are desirable over deep +handlers is \UNIX{}-style pipes, where the natural implementation is +in terms of two mutually recursive functions (specifically +\emph{mutumorphisms}~\cite{Fokkinga90}), which is convoluted to +implement with deep +handlers~\cite{KammarLO13,HillerstromL18,HillerstromLA20}. -% A systems software engineering reading of effect handlers may be to -% understand them as modular ``tiny operating systems''. Operationally, -% how an \emph{operating system} interprets a set of \emph{system -% commands} performed via \emph{system calls} is similar to how an -% effect handler interprets a set of abstract operations performed via -% operation invocations. -% % -% This reading was suggested to me by James McKinna (personal -% communication). In this section I will take this reading literally, -% and demonstrate how we can use the power of (deep) effect handlers to -% implement a tiny operating system that supports multiple users, -% time-sharing, and file i/o. -% % -% The operating system will be a variation of \UNIX{}~\cite{RitchieT74}, -% which we will call \OSname{}. -% % -% To make the task tractable we will occasionally jump some hops and -% make some simplifying assumptions, nevertheless the resulting -% implementation will capture the essence of a \UNIX{}-like operating -% system. +In this section we take the full $\BCalc$ as our starting point and +extend it with shallow handlers, resulting in the calculus $\SCalc$. +The calculus borrows some syntax and semantics from \HCalc{}, whose +presentation will not be duplicated in this section. +% Often deep handlers are attractive because they are semantically +% well-behaved and provide appropriate structure for efficient +% implementations using optimisations such as fusion~\cite{WuS15}, and +% as we saw in the previous they codify a wide variety of applications. % % -% The implementation will be composed of several small modular effect -% handlers, that each handles a particular set of system commands. In -% this respect, we will truly realise \OSname{} in the spirit of the -% \UNIX{} philosophy~\cite[Section~1.6]{Raymond03}. The implementation of -% the operating system will showcase several computational effects in -% action including \emph{dynamic binding}, \emph{nondeterminism}, and -% \emph{state}. - -\subsection{Basic i/o} -\label{sec:tiny-unix-bio} +% However, they are not always convenient for implementing other +% structural recursion schemes such as mutual recursion. -The file system is a cornerstone of \UNIX{} as the notion of \emph{file} -in \UNIX{} provides a unified abstraction for storing text, interprocess -communication, and access to devices such as terminals, printers, -network, etc. +\subsection{Syntax and static semantics} +The syntax and semantics for effectful operation invocations are the +same as in $\HCalc$. Handler definitions and applications also have +the same syntax as in \HCalc{}, although we shall annotate the +application form for shallow handlers with a superscript $\dagger$ to +distinguish it from deep handler application. % -Initially, we shall take a rather basic view of the file system. In -fact, our initial system will only contain a single file, and -moreover, the system will only support writing operations. This system -hardly qualifies as a \UNIX{} file system. Nevertheless, it serves a -crucial role for development of \OSname{}, because it provides the -only means for us to be able to observe the effects of processes. +\begin{syntax} +\slab{Computations} &M,N \in \CompCat &::=& \cdots \mid \ShallowHandle \; M \; \With \; H\\[1ex] +\end{syntax} % -We defer development of a more advanced file system to -Section~\ref{sec:tiny-unix-io}. +The static semantics for $\Handle^\dagger$ are the same as the static +semantics for $\Handle$. +% +\begin{mathpar} + \inferrule*[Lab=\tylab{Handle^\dagger}] + { + \typ{\Gamma}{M : C} \\ + \typ{\Gamma}{H : C \Harrow D} + } + {\Gamma \vdash \ShallowHandle \; M \; \With\; H : D} -Much like \UNIX{} we shall model a file as a list of characters, that is -$\UFile \defas \List~\Char$. For convenience we will use the same -model for strings, $\String \defas \List~\Char$, such that we can use -string literal notation to denote the $\strlit{contents of a file}$. + +%\mprset{flushleft} + \inferrule*[Lab=\tylab{Handler^\dagger}] + {{\bl + C = A \eff \{(\ell_i : A_i \opto B_i)_i; R\} \\ + D = B \eff \{(\ell_i : P_i)_i; R\}\\ + H = \{\Return\;x \mapsto M\} \uplus \{ \OpCase{\ell_i}{p_i}{r_i} \mapsto N_i \}_i + \el}\\\\ + \typ{\Delta;\Gamma, x : A}{M : D}\\\\ + [\typ{\Delta;\Gamma,p_i : A_i, r_i : B_i \to C}{N_i : D}]_i + } + {\typ{\Delta;\Gamma}{H : C \Harrow D}} +\end{mathpar} % -The signature of the basic file system will consist of a single -operation $\Write$ for writing a list of characters to the file. +The \tylab{Handler^\dagger} rule is remarkably similar to the +\tylab{Handler} rule. In fact, the only difference is the typing of +resumptions $r_i$. The codomain of $r_i$ is $C$ rather than $D$, +meaning that a resumption returns a value of the same type as the +input computation. In general the type $C$ may be different from the +output type $D$, thus it is evident from this typing rule that the +handler does not guard invocations of resumptions $r_i$. + + +\subsection{Dynamic semantics} + +There are two reduction rules. +%{\small{ +\begin{reductions} +\semlab{Ret^\dagger} & + \ShallowHandle \; (\Return \; V) \; \With \; H &\reducesto& N[V/x], \hfill\text{where } \hret = \{ \Return \; x \mapsto N \} \\ +\semlab{Op^\dagger} & + \ShallowHandle \; \EC[\Do \; \ell \, V] \; \With \; H + &\reducesto& N[V/p, \lambda y . \, \EC[\Return \; y]/r], \\ +\multicolumn{4}{@{}r@{}}{ + \hfill\ba[t]{@{~}r@{~}l} + \text{where}& \hell = \{ \OpCase{\ell}{p}{r} \mapsto N \}\\ + \text{and} & \ell \notin \BL(\EC) + \ea +} +\end{reductions}%}}% % -\[ - \BIO \defas \{\Write : \Record{\UFD;\String} \opto \UnitType\} -\] +The rule \semlab{Ret^\dagger} is the same as the \semlab{Ret} rule for +deep handlers --- there is no difference in how the return value is +handled. The \semlab{Op^\dagger} rule is almost the same as the +\semlab{Op} rule the crucial difference being the construction of the +resumption $r$. The resumption consists entirely of the captured +context $\EC$. Thus an invocation of $r$ does not reinstall its +handler as in the setting of deep handlers, meaning is up to the +programmer to supply the handler the next invocation of $\ell$ inside +$\EC$. This handler may be different from $H$. + +The basic metatheoretic properties of $\SCalc$ are a carbon copy of +the basic properties of $\HCalc$. % -The operation is parameterised by a $\UFD$ and a character -sequence. We will leave the $\UFD$ type abstract until -Section~\ref{sec:tiny-unix-io}, however, we shall assume the existence -of a term $\stdout : \UFD$ such that we can perform invocations of -$\Write$. +\begin{theorem}[Progress] + Suppose $\typ{}{M : C}$, then either there exists $\typ{}{N : C}$ + such that $M \reducesto^+ N$ and $N$ is normal, or $M$ diverges. +\end{theorem} % -Let us define a suitable handler for this operation. +\begin{proof} + By induction on the typing derivations. +\end{proof} % -\[ - \bl - \basicIO : (\UnitType \to \alpha \eff \BIO) \to \Record{\alpha; \UFile}\\ - \basicIO~m \defas - \ba[t]{@{~}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& \Record{res;\nil}\\ - \OpCase{\Write}{\Record{\_;cs}}{resume} &\mapsto& - \ba[t]{@{}l} - \Let\; \Record{res;file} = resume\,\Unit\;\In\\ - \Record{res; cs \concat file} - \ea - \ea - \ea - \el -\] +\begin{theorem}[Subject reduction] + Suppose $\typ{\Gamma}{M : C}$ and $M \reducesto M'$, then + $\typ{\Gamma}{M' : C}$. +\end{theorem} % -The handler takes as input a computation that produces some value -$\alpha$, and in doing so may perform the $\BIO$ effect. +\begin{proof} + By induction on the typing derivations. +\end{proof} + +\section{Parameterised handlers} +\label{sec:unary-parameterised-handlers} + +Parameterised handlers are a variation of ordinary deep handlers with +an embedded functional state cell. This state cell is only accessible +locally within the handler. The use of state within the handler is +opaque to both the ambient context and the context of the computation +being handled. Semantically, parameterised handlers are defined as +folds with state threading over computation trees. + +We take the deep handler calculus $\HCalc$ as our starting point and +extend it with parameterised handlers to yield the calculus +$\HPCalc$. The parameterised handler extension interacts nicely with +shallow handlers, and as such it can be added to $\SCalc$ with low +effort. + +\subsection{Syntax and static semantics} +In addition to a computation, a parameterised handler also take a +value as argument. This argument is the initial value of the state +cell embedded inside the handler. % -The handler ultimately returns a pair consisting of the return value -$\alpha$ and the final state of the file. +\begin{syntax} +\slab{Handler\textrm{ }types} & F &::=& \cdots \mid \Record{C; A} \Rightarrow^\param D\\ +\slab{Computations} & M,N &::=& \cdots \mid \ParamHandle\; M \;\With\; H^\param(W)\\ +\slab{Parameterised\textrm{ }definitions} & H^\param &::=& q^A.~H +\end{syntax} % -The $\Return$-case pairs the result $res$ with the empty file $\nil$ -which models the scenario where the computation $m$ performed no -$\Write$-operations, e.g. -$\basicIO\,(\lambda\Unit.\Unit) \reducesto^+ -\Record{\Unit;\strlit{}}$. +The syntactic category of handler types $F$ is extended with a new +kind of handler arrow for parameterised handlers. The left hand side +of the arrow is a pair, whose first component denotes the type of the +input computation and the second component denotes the type of the +handler parameter. The right hand side denotes the return type of the +handler. % -The $\Write$-case extends the file by first invoking the resumption, -whose return type is the same as the handler's return type, thus it -returns a pair containing the result of $m$ and the file state. The -file gets extended with the character sequence $cs$ before it is -returned along with the original result of $m$. +The computations category is extended with a new application form for +handlers, which runs a computation $M$ under a parameterised handler +$H$ applied to the value $W$. % -Intuitively, we may think of this implementation of $\Write$ as a -peculiar instance of buffered writing, where the contents of the -operation are committed to the file when the computation $m$ finishes. +Finally, a new category is added for parameterised handler +definitions. A parameterised handler definition is a new binding form +$(q^A.~H)$, where $q$ is the name of the parameter, whose type is $A$, +and $H$ is an ordinary handler definition $H$. The parameter $q$ is +accessible in the $\Return$ and operation clauses of $H$. -Let us define an auxiliary function that writes a string to the -$\stdout$ file. +As with ordinary deep handlers and shallow handlers, there are two +typing rules: one for handler application and another for handler +definitions. % -\[ - \bl - \echo : \String \to \UnitType \eff\, \BIO\\%\{\Write : \Record{\UFD;\String} \opto \UnitType\}\\ - \echo~cs \defas \Do\;\Write\,\Record{\stdout;cs} - \el -\] +\begin{mathpar} + % Handle + \inferrule*[Lab=\tylab{Handle^\param}] + { + % \typ{\Gamma}{V : A} \\ + \typ{\Gamma}{M : C} \\ + \typ{\Gamma}{W : A} \\ + \typ{\Gamma}{H^\param : \Record{C; A} \Harrow^\param D} + } + {\Gamma \vdash \ParamHandle \; M \; \With\; H^\param(W) : D} +\end{mathpar} % -The function $\echo$ is a simple wrapper around an invocation of -$\Write$. +The $\tylab{Handle^\param}$ rule is similar to the $\tylab{Handle}$ +and $\tylab{Handle^\dagger}$ rules, except that it has to account for +the parameter $W$, whose type has to be compatible with the second +component of the domain type of the handler definition $H^\param$. % -We can now write some contents to the file and observe the effects. +The typing rule for parameterised handler definitions adapts the +corresponding typing rule $\tylab{Handler}$ for ordinary deep handlers +with the addition of a parameter. % -\[ - \ba{@{~}l@{~}l} - &\basicIO\,(\lambda\Unit. \echo~\strlit{Hello}; \echo~\strlit{World})\\ - \reducesto^+& \Record{\Unit;\strlit{HelloWorld}} : \Record{\UnitType;\UFile} - \ea -\] - -\subsection{Exceptions: non-local exits} -\label{sec:tiny-unix-exit} +\begin{mathpar} +% Parameterised handler + \inferrule*[Lab=\tylab{Handler^\param}] + {{\bl + C = A \eff \{(\ell_i : A_i \to B_i)_i; R\} \\ + D = B \eff \{(\ell_i : P)_i; R\} \\ + H = \{\Return\;x \mapsto M\} \uplus \{ \OpCase{\ell_i}{p_i}{r_i} \mapsto N_i \}_i + \el}\\\\ + \typ{\Delta;\Gamma, q : A', x : A}{M : D}\\\\ + [\typ{\Delta;\Gamma,q : A', p_i : A_i, r_i : \Record{B_i;A'} \to D}{N_i : D}]_i + } + {\typ{\Delta;\Gamma}{(q^{A'} . H) : \Record{C;A'} \Harrow^\param D}} +\end{mathpar} +%% +The key differences between the \tylab{Handler} and +\tylab{Handler^\param} rules are that in the latter the return and +operation cases are typed with respect to the parameter $q$, and that +resumptions $r_i$ have type $\Record{B_\ell;A'} \to D$, that is a +parameterised resumption is a binary function, where the first +argument is the interpretation of an operation and the second argument +is the (updated) handler state. The return type of $r_i$ is the same +as the return type of the handler, meaning that an invocation of $r_i$ +is guarded in the same way as an invocation of an ordinary deep +resumption. -A process may terminate successfully by running to completion, or it -may terminate with success or failure in the middle of some -computation by performing an \emph{exit} system call. The exit system -call is typically parameterised by an integer value intended to -indicate whether the exit was due to success or failure. By -convention, \UNIX{} interprets the integer zero as success and any -nonzero integer as failure, where the specific value is supposed to -correspond to some known error code. +\subsection{Dynamic semantics} +The two reduction rules for parameterised handlers adapt the reduction +rules for ordinary deep handlers with a parameter. +% +\begin{reductions} +\semlab{Ret^\param} & + \ParamHandle \; (\Return \; V) \; \With \; (q.H)(W) &\reducesto& N[V/x,W/q],\\ + \multicolumn{4}{@{}r@{}}{ + \hfill\text{where } \hret = \{ \Return \; x \mapsto N \}} \\ +\semlab{Op^\param} & + \ParamHandle \; \EC[\Do \; \ell \, V] \; \With \; (q.H)(W) + &\reducesto& N[V/p,W/q,R/r],\\ +\multicolumn{4}{@{}r@{}}{ + \hfill\ba[t]{@{~}r@{~}l} + \text{where}& R = \lambda\Record{y;q'}.\ParamHandle\;\EC[\Return \; y]\;\With\;(q.H)(q')\\ + \text{and} &\hell = \{ \OpCase{\ell}{p}{r} \mapsto N \}\\ + \text{and} &\ell \notin \BL(\EC) + \ea +} +\end{reductions} % +The rule $\semlab{Ret^\param}$ handles the return value of a +computation. Just like the rule $\semlab{Ret}$ the return value $V$ is +substituted for the binder $x$ in the return case body +$N$. Furthermore the value $W$ is substituted for the handler +parameter $q$ in $N$, meaning the handler parameter is accessible in +the return case. -We can model the exit system call by way of a single operation -$\Exit$. +The $\semlab{Op^\param}$ handles an operation invocation. Both the +operation payload $V$ and handler argument $W$ are accessible inside +the case body $N$. As with ordinary deep handlers, the resumption +rewraps its handler, but with the slight twist that the parameterised +handler definition is applied to the updated parameter value $q'$ +rather than the original value $W$. This achieves the effect of state +passing as the value of $q'$ becomes available upon the next +activation of the handler. + +The metatheoretic properties of $\HCalc$ carries over to $\HPCalc$. +\begin{theorem}[Progress] + Suppose $\typ{}{M : C}$, then either there exists $\typ{}{N : C}$ + such that $M \reducesto^+ N$ and $N$ is normal, or $M$ diverges. +\end{theorem} % -\[ - \Status \defas \{\Exit : \Int \opto \ZeroType\} -\] +\begin{proof} + By induction on the typing derivations. +\end{proof} % -The operation is parameterised by an integer value, however, an -invocation of $\Exit$ can never return, because the type $\ZeroType$ is -uninhabited. Thus $\Exit$ acts like an exception. +\begin{theorem}[Subject reduction] + Suppose $\typ{\Gamma}{M : C}$ and $M \reducesto M'$, then + $\typ{\Gamma}{M' : C}$. +\end{theorem} % -It is convenient to abstract invocations of $\Exit$ to make it -possible to invoke the operation in any context. +\begin{proof} + By induction on the typing derivations. +\end{proof} + +\part{Implementation} +\label{p:implementation} + +\chapter{Continuation-passing style} +\label{ch:cps} + +Continuation-passing style (CPS) is a \emph{canonical} program +notation that makes every facet of control flow and data flow +explicit. In CPS every function takes an additional function-argument +called the \emph{continuation}, which represents the next computation +in evaluation position. CPS is canonical in the sense that it is +definable in pure $\lambda$-calculus without any further +primitives. As an informal illustration of CPS consider again the +ever-green factorial function from Section~\ref{sec:tracking-div}. % \[ \bl - \exit : \Int \to \alpha \eff \Status\\ - \exit~n \defas \Absurd\;(\Do\;\Exit~n) + \dec{fac} : \Int \to \Int\\ + \dec{fac} \defas \lambda n. + \ba[t]{@{}l} + \Let\;isz \revto n = 0\;\In\\ + \If\;isz\;\Then\; \Return\;1\\ + \Else\;\ba[t]{@{~}l} + \Let\; n' \revto n - 1 \;\In\\ + \Let\; m \revto \dec{fac}~n' \;\In\\ + \Let\;res \revto n * m \;\In\\ + \Return\;res + \ea + \ea \el \] % -The $\Absurd$ computation term is used to coerce the return type -$\ZeroType$ of $\Fail$ into $\alpha$. This coercion is safe, because -$\ZeroType$ is an uninhabited type. -% -An interpretation of $\Exit$ amounts to implementing an exception -handler. +The above implementation of the function $\dec{fac}$ is given in +direct-style fine-grain call-by-value. In CPS notation the +implementation of this function changes as follows. % \[ \bl - \status : (\UnitType \to \alpha \eff \Status) \to \Int\\ - \status~m \defas - \ba[t]{@{~}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;\_ &\mapsto& 0\\ - \ExnCase{\Exit}{n} &\mapsto& n - \ea - \ea + \dec{fac}_{\dec{cps}} : \Int \to (\Int \to \alpha) \to \alpha\\ + \dec{fac}_{\dec{cps}} \defas \lambda n.\lambda k. + (=_{\dec{cps}})~n~0~ + (\lambda isz. + \ba[t]{@{~}l} + \If\;isz\;\Then\; k~1\\ + \Else\; + (-_{\dec{cps}})~n~\bl 1\\ + (\lambda n'. + \dec{fac}_{\dec{cps}}~\bl n'\\ + (\lambda m. (*_{\dec{cps}})~n~\bl m\\ + (\lambda res. k~res)))) + \el + \el + \el + \ea \el \] % -Following the \UNIX{} convention, the $\Return$-case interprets a -successful completion of $m$ as the integer $0$. The operation case -returns whatever payload the $\Exit$ operation was carrying. As a -consequence, outside of $\status$, an invocation of $\Exit~0$ in $m$ -is indistinguishable from $m$ returning normally, e.g. -$\status\,(\lambda\Unit.\exit~0) = \status\,(\lambda\Unit.\Unit)$. - -To illustrate $\status$ and $\exit$ in action consider the following -example, where the computation gets terminated mid-way. -% -\[ - \ba{@{~}l@{~}l} - &\bl - \basicIO\,(\lambda\Unit. - \status\,(\lambda\Unit. - \echo~\strlit{dead};\exit~1;\echo~\strlit{code})) - \el\\ - \reducesto^+& \Record{1;\strlit{dead}} : \Record{\Int;\UFile} - \ea -\] +There are several worthwhile observations to make about the +differences between the two implementations $\dec{fac}$ and +$\dec{fac}_{\dec{cps}}$. % -The (delimited) continuation of $\exit~1$ is effectively dead code. +Firstly note that their type signatures differ. The CPS version has an +additional formal parameter of type $\Int \to \alpha$ which is the +continuation. By convention the continuation parameter is named $k$ in +the implementation. As usual, the continuation represents the +remainder of computation. In this specific instance $k$ represents the +undelimited current continuation of an application of +$\dec{fac}_{\dec{cps}}$. Given a value of type $\Int$, the +continuation produces a result of type $\alpha$, which is the +\emph{answer type} of the entire program. Thus applying +$\dec{fac}_{\dec{cps}}~3$ to the identity function ($\lambda x.x$) +yields $6 : \Int$, whilst applying it to the predicate +$\lambda x. x > 2$ yields $\True : \Bool$. +% or put differently: it determines what to do with the result +% returned by an invocation of $\dec{fac}_{\dec{cps}}$. % -Here, we have a choice as to how we compose the handlers. Swapping the -order of handlers would cause the whole computation to return just -$1 : \Int$, because the $\status$ handler discards the return value of -its computation. Thus with the alternative layering of handlers the -system would throw away the file state after the computation -finishes. However, in this particular instance the semantics the -(local) behaviour of the operations $\Write$ and $\Exit$ would be -unaffected if the handlers were swapped. In general the behaviour of -operations may be affected by the order of handlers. The canonical -example of this phenomenon is the composition of nondeterminism and -state, which we will discuss in Section~\ref{sec:tiny-unix-io}. - -\subsection{Dynamic binding: user-specific environments} -\label{sec:tiny-unix-env} -When a process is run in \UNIX{}, the operating system makes available -to the process a collection of name-value pairs called the -\emph{environment}. +Secondly note that every $\Let$-binding in $\dec{fac}$ has become a +function application in $\dec{fac}_{\dec{cps}}$. The binding sequence +in the $\Else$-branch has been turned into a series of nested function +applications. The functions $=_{\dec{cps}}$, $-_{\dec{cps}}$, and +$*_{\dec{cps}}$ denote the CPS versions of equality testing, +subtraction, and multiplication respectively. % -The name of a name-value pair is known as an \emph{environment - variable}. +For clarity, I have meticulously written each continuation function on +a newline. For instance, the continuation of the +$-_{\dec{cps}}$-application is another application of +$\dec{fac}_{\dec{cps}}$, whose continuation is an application of +$*_{\dec{cps}}$, and its continuation is an application of the current +continuation, $k$, of $\dec{fac}_{\dec{cps}}$. % -During execution the process may perform a system call to ask the -operating system for the value of some environment variable. +Each $\Return$-computation has been turned into an application of the +current continuation $k$. In the $\Then$-branch the continuation +applied to $1$, whilst in the $\Else$-branch the continuation is +applied to the result obtained by multiplying $n$ and $m$. + % -The value of environment variables may change throughout process -execution, moreover, the value of some environment variables may vary -according to which user asks the environment. +Thirdly note that every function application occurs in tail position +(recall Definition~\ref{def:tail-comp}). This is a characteristic +property of CPS transforms that make them feasible as a practical +implementation strategy, since programs in CPS notation require only a +constant amount of stack space to run, namely, a single activation +frame~\cite{Appel92}. Although, the pervasiveness of closures in CPS +means that CPS programs make heavy use of the heap for closure +allocation. % -For example, an environment may contain the environment variable -\texttt{USER} that is bound to the name of the enquiring user. +Some care must be taken when CPS transforming a program as if done +naïvely the image may be inflated with extraneous +terms~\cite{DanvyN05}. For example in $\dec{fac}_{\dec{cps}}$ the +continuation term $(\lambda res.k~res)$ is redundant as it is simply +an eta expansion of the continuation $k$. A more optimal transform +would simply pass $k$. Extraneous terms can severely impact the +runtime performance of a CPS program. A smart CPS transform recognises +and eliminates extraneous terms at translation +time~\cite{DanvyN03}. Extraneous terms come in various disguises as we +shall see later in this chapter. -An environment variable can be viewed as an instance of dynamic -binding. The idea of dynamic binding as a binding form in programming -dates back as far as the original implementation of -Lisp~\cite{McCarthy60}, and still remains an integral feature in -successors such as Emacs Lisp~\cite{LewisLSG20}. It is well-known that -dynamic binding can be encoded as a computational effect by using -delimited control~\cite{KiselyovSS06}. -% -Unsurprisingly, we will use this insight to simulate user-specific -environments using effect handlers. +The complete exposure of the control flow makes CPS a good fit for +implementing control operators such as effect handlers. It is an +established intermediate representation used by compilers, providing +it with merits as a practical compilation +target~\cite{Appel92,Kennedy07}. -For simplicity we fix the users of the operating system to be root, -Alice, and Bob. +The purpose of this chapter is to use the CPS formalism to develop a +universal implementation strategy for deep, shallow, and parameterised +effect handlers. Section~\ref{sec:target-cps} defines a suitable +target calculus $\UCalc$ for CPS transformed +programs. Section~\ref{sec:cps-cbv} demonstrates how to CPS transform +$\BCalc$-programs to $\UCalc$-programs. In Section~\ref{sec:fo-cps} +develop a CPS transform for deep handlers through step-wise refinement +of the initial CPS transform for $\BCalc$. The resulting CPS transform +is adapted in Section~\ref{sec:cps-shallow} to support for shallow +handlers. As a by-product we develop the notion of \emph{generalised + continuation}, which provides a versatile abstraction for +implementing effect handlers. We use generalised continuations to +implement parameterised handlers in Section~\ref{sec:cps-param}. % -\[ - \User \defas [\Alice;\Bob;\Root] -\] -Our environment will only support a single environment variable -intended to store the name of the current user. The value of this -variable can be accessed via an operation $\Ask : \UnitType \opto \String$. % +%\dhil{The focus of the introduction should arguably not be to explain CPS.} +%\dhil{Justify CPS as an implementation technique} +%\dhil{Give a side-by-side reduction example of $\dec{fac}$ and $\dec{fac}_{\dec{cps}}$.} +% \dhil{Define desirable properties of a CPS translation: properly tail-recursive, no static administrative redexes} +% +% \begin{definition}[Properly tail-recursive~\cite{Danvy06}] +% % +% A CPS translation $\cps{-}$ is properly tail-recursive if the +% continuation of every CPS transformed tail call $\cps{V\,W}$ within +% $\cps{\lambda x.M}$ is $k$, where +% \begin{equations} +% \cps{\lambda x.M} &=& \lambda x.\lambda k.\cps{M}\\ +% \cps{V\,W} &=& \cps{V}\,\cps{W}\,k. +% \end{equations} +% \end{definition} + % \[ -% \EnvE \defas \{\Ask : \UnitType \opto \String\} +% \ba{@{~}l@{~}l} +% \pcps{(\lambda x.(\lambda y.\Return\;y)\,x)\,\Unit} &= (\lambda x.(\lambda y.\lambda k.k\,y)\,x)\,\Unit\,(\lambda x.x)\\ +% &\reducesto ((\lambda y.\lambda k.k\,y)\,\Unit)\,(\lambda x.x)\\ +% &\reducesto (\lambda k.k\,\Unit)\,(\lambda x.x)\\ +% &\reducesto (\lambda x.x)\,\Unit\\ +% &\reducesto \Unit +% \ea % \] + +\paragraph{Relation to prior work} This chapter is based on the +following work. % -Using this operation we can readily implement the \emph{whoami} -utility from the GNU coreutils~\cite[Section~20.3]{MacKenzieMPPBYS20}, -which returns the name of the current user. -% -\[ - \bl - \whoami : \UnitType \to \String \eff \{\Ask : \UnitType \opto \String\}\\ - \whoami~\Unit \defas \Do\;\Ask~\Unit - \el -\] -% -The following handler implements the environment. -% -\[ - \bl - \environment : \Record{\User;\UnitType \to \alpha \eff \{\Ask : \UnitType \opto \String\}} \to \alpha\\ - \environment~\Record{user;m} \defas - \ba[t]{@{~}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& res\\ - \OpCase{\Ask}{\Unit}{resume} &\mapsto& - \bl - \Case\;user\,\{ - \ba[t]{@{}l@{~}c@{~}l} - \Alice &\mapsto& resume~\strlit{alice}\\ - \Bob &\mapsto& resume~\strlit{bob}\\ - \Root &\mapsto& resume~\strlit{root}\} - \ea - \el - \ea - \ea - \el -\] -% -The handler takes as input the current $user$ and a computation that -may perform the $\Ask$ operation. When an invocation of $\Ask$ occurs -the handler pattern matches on the $user$ parameter and resumes with a -string representation of the user. With this implementation we can -interpret an application of $\whoami$. -% -\[ - \environment~\Record{\Root;\whoami} \reducesto^+ \strlit{root} : \String -\] +\begin{enumerate}[i] + \item \bibentry{HillerstromLAS17}\label{en:ch-cps-HLAS17} + \item \bibentry{HillerstromL18} \label{en:ch-cps-HL18} + \item \bibentry{HillerstromLA20} \label{en:ch-cps-HLA20} +\end{enumerate} % -It is not difficult to extend this basic environment model to support -an arbitrary number of variables. This can be done by parameterising -the $\Ask$ operation by some name representation (e.g. a string), -which the environment handler can use to index into a list of string -values. In case the name is unbound the environment, the handler can -embrace the laissez-faire attitude of \UNIX{} and resume with the -empty string. +Section~\ref{sec:higher-order-uncurried-deep-handlers-cps} is +based on item \ref{en:ch-cps-HLAS17}, however, I have adapted it to +follow the notation and style of item \ref{en:ch-cps-HLA20}. -\paragraph{User session management} -% -It is somewhat pointless to have multiple user-specific environments, -if the system does not support some mechanism for user session -handling, such as signing in as a different user. +\section{Initial target calculus} +\label{sec:target-cps} % -In \UNIX{} the command \emph{substitute user} (su) enables the invoker -to impersonate another user account, provided the invoker has -sufficient privileges. -% -We will implement su as an operation $\Su : \User \opto \UnitType$ -which is parameterised by the user to be impersonated. -% -To model the security aspects of su, we will use the weakest possible -security model: unconditional trust. Put differently, we will not -bother with security at all to keep things relatively simple. -% -Consequently, anyone can impersonate anyone else. +\begin{figure} + \flushleft + \textbf{Syntax} + \begin{syntax} + \slab{Values} &U, V, W \in \UValCat &::= & x \mid \lambda x.M \mid % \Rec\,g\,x.M + \mid \Record{} \mid \Record{V, W} \mid \ell + \smallskip \\ + \slab{Computations} &M,N \in \UCompCat &::= & V \mid M\,W \mid \Let\; \Record{x,y} = V \; \In \; N\\ + & &\mid& \Case\; V\, \{\ell \mapsto M; y \mapsto N\} \mid \Absurd\,V + \smallskip \\ + \slab{Evaluation contexts} &\EC \in \UEvalCat &::= & [~] \mid \EC\;W \\ + \end{syntax} -The session signature consists of two operations, $\Ask$, which we -used above, and $\Su$, for switching user. -% + \textbf{Reductions} + \begin{reductions} + \usemlab{App} & (\lambda x . \, M) V &\reducesto& M[V/x] \\ + % \usemlab{Rec} & (\Rec\,g\,x.M) V &\reducesto& M[\Rec\,g\,x.M/g,V/x]\\ + \usemlab{Split} & \Let \; \Record{x,y} = \Record{V,W} \; \In \; N &\reducesto& N[V/x,W/y] \\ + \usemlab{Case_1} & + \Case \; \ell \; \{ \ell \mapsto M; y \mapsto N\} &\reducesto& M \\ + \usemlab{Case_2} & + \Case \; \ell \; \{ \ell' \mapsto M; y \mapsto N\} &\reducesto& N[\ell/y], \hfill\quad \text{if } \ell \neq \ell' \\ + \usemlab{Lift} & + \EC[M] &\reducesto& \EC[N], \hfill \text{if } M \reducesto N \\ + \end{reductions} + + \textbf{Syntactic sugar} \[ - \EnvE \defas \{\Ask : \UnitType \opto \String;\Su : \User \opto \UnitType\} + \begin{eqs} + \Let\;x=V\;\In\;N &\equiv & N[V/x]\\ + \ell \; V & \equiv & \Record{\ell; V}\\ + \Record{} & \equiv & \ell_{\Record{}} \\ + \Record{\ell = V; W} & \equiv & \Record{\ell, \Record{V, W}}\\ + \nil &\equiv & \ell_{\nil} \\ + V \cons W & \equiv & \Record{\ell_{\cons}, \Record{V, W}}\\ + \Case\;V\;\{\ell\;x \mapsto M; y \mapsto N \} &\equiv& + \ba[t]{@{~}l} + \Let\;y = V\;\In\; \Let\;\Record{z,x} = y\;\In \\ + \Case\; z\;\{ \ell \mapsto M; z' \mapsto N \} + \ea\\ + \Let\; \Record{\ell=x;y} = V\;\In\;N &\equiv& + \ba[t]{@{~}l} + \Let\; \Record{z,z'} = V\;\In\;\Let\; \Record{x,y} = z'\;\In \\ + \Case\;z\;\{\ell \mapsto N; z'' \mapsto \ell_\bot \} + \ea + \end{eqs} \] + +\caption{Untyped target calculus for the CPS translations.} +\label{fig:cps-cbv-target} +\end{figure} % -As usual, we define a small wrapper around invocations of $\Su$. +The syntax, semantics, and syntactic sugar for the target calculus +$\UCalc$ is given in Figure~\ref{fig:cps-cbv-target}. The calculus +largely amounts to an untyped variation of $\BCalc$, specifically +we retain the syntactic distinction between values ($V$) and +computations ($M$). % -\[ - \bl - \su : \User \to \UnitType \eff \{\Su : \User \opto \UnitType\}\\ - \su~user \defas \Do\;\Su~user - \el -\] +The values ($V$) comprise lambda abstractions ($\lambda x.M$), +% recursive functions ($\Rec\,g\,x.M$), +empty tuples ($\Record{}$), pairs ($\Record{V,W}$), and first-class +labels ($\ell$). % +Computations ($M$) comprise values ($V$), applications ($M~V$), pair +elimination ($\Let\; \Record{x, y} = V \;\In\; N$), label elimination +($\Case\; V \;\{\ell \mapsto M; x \mapsto N\}$), and explicit marking +of unreachable code ($\Absurd$). A key difference from $\BCalc$ is +that the function position of an application is allowed to be a +computation (i.e., the application form is $M~W$ rather than +$V~W$). Later, when we refine the initial CPS translation we will be +able to rule out this relaxation. -The intended operational behaviour of an invocation of $\Su~user$ is -to load the environment belonging to $user$ and continue the -continuation under this environment. -% -We can achieve this behaviour by defining a handler for $\Su$ that -invokes the provided resumption under a fresh instance of the -$\environment$ handler. -% +The reduction semantics follows the trend of the previous reduction +semantics in the sense that it is a small-step context-based reduction +semantics. Evaluation contexts comprise the empty context and function +application. + +To make the notation more lightweight, we define syntactic sugar for +variant values, record values, list values, let binding, variant +eliminators, and record eliminators. We use pattern matching syntax +for deconstructing variants, records, and lists. For desugaring +records, we assume a failure constant $\ell_\bot$ (e.g. a divergent +term) to cope with the case of pattern matching failure. +% \dhil{Most of the primitives are Church encodable. Discuss the value +% of treating them as primitive rather than syntactic sugar (target +% languages such as JavaScript has similar primitives).} + +\section{Transforming fine-grain call-by-value} +\label{sec:cps-cbv} + +We start by giving a CPS translation of $\BCalc$ in +Figure~\ref{fig:cps-cbv}. Fine-grain call-by-value admits a +particularly simple CPS translation due to the separation of values +and computations. All constructs from the source language are +translated homomorphically into the target language $\UCalc$, except +for $\Return$ and $\Let$ (and type abstraction because the translation +performs type erasure). Lifting a value $V$ to a computation +$\Return~V$ is interpreted by passing the value to the current +continuation $k$. Sequencing computations with $\Let$ is translated by +applying the translation of $M$ to the translation of the continuation +$N$, which is ultimately applied to the current continuation $k$. In +addition, we explicitly $\eta$-expand the translation of a type +abstraction in order to ensure that value terms in the source calculus +translate to value terms in the target. + +\begin{figure} +\flushleft +\textbf{Values} \\ \[ - \bl - \sessionmgr : \Record{\User; \UnitType \to \alpha \eff \EnvE} \to \alpha\\ - \sessionmgr\,\Record{user;m} \defas - \environment\langle{}user;(\lambda\Unit. - \ba[t]{@{}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& res\\ - \OpCase{\Su}{user'}{resume} &\mapsto& \environment\Record{user';resume})\rangle - \ea - \ea - \el -\] -% -The function $\sessionmgr$ manages a user session. It takes two -arguments: the initial user ($user$) and the computation ($m$) to run -in the current session. An initial instance of $\environment$ is -installed with $user$ as argument. The computation argument is a -handler for $\Su$ enclosing the computation $m$. The $\Su$-case -installs a new instance of $\environment$, which is the environment -belonging to $user'$, and runs the resumption $resume$ under this -instance. -% -The new instance of $\environment$ shadows the initial instance, and -therefore it will intercept and handle any subsequent invocations of -$\Ask$ arising from running the resumption. A subsequent invocation of -$\Su$ will install another environment instance, which will shadow -both the previously installed instance and the initial instance. -% +\bl -To make this concrete, let us plug together the all components of our -system we have defined thus far. -% +\begin{eqs} + \cps{-} &:& \ValCat \to \UValCat\\ +\cps{x} &=& x \\ +\cps{\lambda x.M} &=& \lambda x.\cps{M} \\ +\cps{\Lambda \alpha.M} &=& \lambda k.\cps{M}~k \\ +% \cps{\Rec\,g\,x.M} &=& \Rec\,g\,x.\cps{M}\\ +\cps{\Record{}} &=& \Record{} \\ +\cps{\Record{\ell = V; W}} &=& \Record{\ell = \cps{V}; \cps{W}} \\ +\cps{\ell~V} &=& \ell~\cps{V} \\ +\end{eqs} +\el +\] +\textbf{Computations} \[ - \ba{@{~}l@{~}l} - &\bl - \basicIO\,(\lambda\Unit.\\ - \qquad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ - \qquad\qquad\status\,(\lambda\Unit. - \ba[t]{@{}l@{~}l} - \su~\Alice;&\echo\,(\whoami\,\Unit);~\echo~\strlit{ };\\ - \su~\Bob; &\echo\,(\whoami\,\Unit);~\echo~\strlit{ };\\ - \su~\Root; &\echo\,(\whoami\,\Unit))}) - \ea - \el \smallskip\\ - \reducesto^+& \Record{0;\strlit{alice bob root}} : \Record{\Int;\UFile} - \ea +\bl +\begin{eqs} +\cps{-} &:& \CompCat \to \UCompCat\\ +\cps{V\,W} &=& \cps{V}\,\cps{W} \\ +\cps{V\,T} &=& \cps{V} \\ +\cps{\Let\; \Record{\ell=x;y} = V \; \In \; N} &=& \Let\; \Record{\ell=x;y} = \cps{V} \; \In \; \cps{N} \\ +\cps{\Case~V~\{\ell~x \mapsto M; y \mapsto N\}} &=& + \Case~\cps{V}~\{\ell~x \mapsto \cps{M}; y \mapsto \cps{N}\} \\ +\cps{\Absurd~V} &=& \Absurd~\cps{V} \\ +\cps{\Return~V} &=& \lambda k.k\,\cps{V} \\ +\cps{\Let~x \revto M~\In~N} &=& \lambda k.\cps{M}(\lambda x.\cps{N}\,k) \\ +\end{eqs} +\el \] +\caption{First-order CPS translation of $\BCalc$.} +\label{fig:cps-cbv} +\end{figure} + +\section{Transforming deep effect handlers} +\label{sec:fo-cps} + +The translation of a computation term by the basic CPS translation in +Section~\ref{sec:cps-cbv} takes a single continuation parameter that +represents the context. % -The session manager ($\sessionmgr$) is installed in between the basic -IO handler ($\basicIO$) and the process status handler -($\status$). The initial user is $\Root$, and thus the initial -environment is the environment that belongs to the root user. Main -computation signs in as $\Alice$ and writes the result of the system -call $\whoami$ to the global file, and then repeats these steps for -$\Bob$ and $\Root$. +In the presence of effect handlers in the source language, it becomes +necessary to keep track of two kinds of contexts in which each +computation executes: a \emph{pure context} that tracks the state of +pure computation in the scope of the current handler, and an +\emph{effect context} that describes how to handle operations in the +scope of the current handler. % -Ultimately, the computation terminates successfully (as indicated by -$0$ in the first component of the result) with global file containing -the three user names. +Correspondingly, we have both \emph{pure continuations} ($k$) and +\emph{effect continuations} ($h$). % +As handlers can be nested, each computation executes in the context of +a \emph{stack} of pairs of pure and effect continuations. -The above example demonstrates that we now have the basic building -blocks to build a multi-user system. +On entry into a handler, the pure continuation is initialised to a +representation of the return clause and the effect continuation to a +representation of the operation clauses. As pure computation proceeds, +the pure continuation may grow, for example when executing a +$\Let$. If an operation is encountered then the effect continuation is +invoked. +% +The current continuation pair ($k$, $h$) is packaged up as a +\emph{resumption} and passed to the current handler along with the +operation and its argument. The effect continuation then either +handles the operation, invoking the resumption as appropriate, or +forwards the operation to an outer handler. In the latter case, the +resumption is modified to ensure that the context of the original +operation invocation can be reinstated upon invocation of the +resumption. % -%\dhil{Remark on the concrete layering of handlers.} -\subsection{Nondeterminism: time sharing} -\label{sec:tiny-unix-time} +\subsection{Curried translation} +\label{sec:first-order-curried-cps} -Time sharing is a mechanism that enables multiple processes to run -concurrently, and hence, multiple users to work concurrently. +We first consider a curried CPS translation that extends the +translation of Figure~\ref{fig:cps-cbv}. The extension to operations +and handlers is localised to the additional features because currying +conveniently lets us get away with a shift in interpretation: rather +than accepting a single continuation, translated computation terms now +accept an arbitrary even number of arguments representing the stack of +pure and effect continuations. Thus, we can conservatively extend the +translation in Figure~\ref{fig:cps-cbv} to cover $\HCalc$, where we +imagine there being some number of extra continuation arguments that +have been $\eta$-reduced. The translation of operations and handlers +is as follows. % -Thus far in our system there is exactly one process. +\begin{equations} +\cps{-} &:& \CompCat \to \UCompCat\\ +\cps{\Do\;\ell\;V} &\defas& \lambda k.\lambda h.h~\Record{\ell,\Record{\cps{V}, \lambda x.k~x~h}} \\ +\cps{\Handle \; M \; \With \; H} &\defas& \cps{M}~\cps{\hret}~\cps{\hops} \medskip\\ +\cps{-} &:& \HandlerCat \to \UCompCat\\ +\cps{\{ \Return \; x \mapsto N \}} &\defas& \lambda x . \lambda h . \cps{N} \\ +\cps{\{ \ell~p~r \mapsto N_\ell \}_{\ell \in \mathcal{L}}} +&\defas& +\lambda \Record{z,\Record{p,r}}. \Case~z~ + \{ (\ell \mapsto \cps{N_\ell})_{\ell \in \mathcal{L}}; y \mapsto \hforward(y,p,r) \} \\ +\hforward(y,p,r) &\defas& \lambda k. \lambda h. h\,\Record{y,\Record{p, \lambda x.\,r\,x\,k\,h}} +\end{equations} % -In \UNIX{} there exists only a single process whilst the system is -bootstrapping itself into operation. After bootstrapping is complete -the system duplicates the initial process to start running user -managed processes, which may duplicate themselves to create further -processes. +The translation of $\Do\;\ell\;V$ abstracts over the current pure +($k$) and effect ($h$) continuations passing an encoding of the +operation into the latter. The operation is encoded as a triple +consisting of the name $\ell$, parameter $\cps{V}$, and resumption +$\lambda x.k~x~h$, which passes the same effect continuation $h$ to +ensure deep handler semantics. + +The translation of $\Handle~M~\With~H$ invokes the translation of $M$ +with new pure and effect continuations for the return and operation +clauses of $H$. % -The process duplication primitive in \UNIX{} is called -\emph{fork}~\cite{RitchieT74}. +The translation of a return clause is a term which garbage collects +the current effect continuation $h$. % -The fork-invoking process is typically referred to as the parent -process, whilst its clone is referred to as the child process. +The translation of a set of operation clauses is a function which +dispatches on encoded operations, and in the default case forwards to +an outer handler. % -Following an invocation of fork, the parent process is provided with a -nonzero identifier for the child process and the child process is -provided with the zero identifier. This enables processes to determine -their respective role in the parent-child relationship, e.g. +In the forwarding case, the resumption is extended by the parent +continuation pair to ensure that an eventual invocation of the +resumption reinstates the handler stack. + +The translation of complete programs feeds the translated term the +identity pure continuation (which discards its handler argument), and +an effect continuation that is never intended to be called. % -\[ - \bl - \Let\;i\revto fork~\Unit\;\In\\ - \If\;i = 0\;\Then\; - ~\textit{child's code}\\ - \Else\;~\textit{parent's code} - \el -\] +\begin{equations} +\pcps{-} &:& \CompCat \to \UCompCat\\ +\pcps{M} &\defas& \cps{M}~(\lambda x.\lambda h.x)~(\lambda \Record{z,\_}.\Absurd~z) \\ +\end{equations} % -In our system, we can model fork as an effectful operation, that -returns a boolean to indicate the process role; by convention we will -interpret the return value $\True$ to mean that the process assumes -the role of parent. +Conceptually, this translation encloses the translated program in a +top-level handler with an empty collection of operation clauses and an +identity return clause. + +A pleasing property of this particular CPS translation is that it is a +conservative extension to the CPS translation for $\BCalc$. Alas, this +translation also suffers two displeasing properties which makes it +unviable in practice. + +\begin{enumerate} +\item The image of the translation is not \emph{properly + tail-recursive}~\citep{Danvy06,DanvyF92,Steele78}, meaning not + every function application occur in tail position in the image, and + thus the image is not stackless. Consequently, the translation + cannot readily be used as the basis for an implementation. This + deficiency is essentially due to the curried representation of the + continuation stack. + + \item The image of the translation yields static administrative + redexes, i.e. redexes that could be reduced statically. This is a + classic problem with CPS translations. This problem can be dealt + with by introducing a second pass to clean up the + image~\cite{Plotkin75}. By clever means the clean up pass and the + translation pass can be fused together to make an one-pass + translation~\cite{DanvyF92,DanvyN03}. +\end{enumerate} + +The following minimal example readily illustrates both issues. % -\[ - \bl - \fork : \UnitType \to \Bool \eff \{\Fork : \UnitType \opto \Bool\}\\ - \fork~\Unit \defas \Do\;\Fork~\Unit - \el -\] +\begin{align*} +\pcps{\Return\;\Record{}} + = & (\lambda k.k\,\Record{})\,(\lambda x.\lambda h.x)\,(\lambda \Record{z,\_}.\Absurd\,z) \\ + \reducesto& ((\lambda x.\lambda h.x)\,\Record{})\,(\lambda \Record{z,\_}.\Absurd\,z) \numberthis\label{eq:cps-admin-reduct-1}\\ + \reducesto& (\lambda h.\Record{})\,(\lambda \Record{z,\_}.\Absurd\,z) \numberthis\label{eq:cps-admin-reduct-2}\\ + \reducesto& \Record{} +\end{align*} % -In \UNIX{} the parent process \emph{continues} execution after the -fork point, and the child process \emph{begins} its execution after -the fork point. +The second and third reductions simulate handling $\Return\;\Record{}$ +at the top level. The second reduction partially applies the curried +function term $\lambda x.\lambda h.x$ to $\Record{}$, which must +return a value such that the third reduction can be +applied. Consequently, evaluation is not tail-recursive. % -Thus, operationally, we may understand fork as returning twice to its -invocation site. We can implement this behaviour by invoking the -resumption arising from an invocation of $\Fork$ twice: first with -$\True$ to continue the parent process, and subsequently with $\False$ -to start the child process (or the other way around if we feel -inclined). +The lack of tail-recursion is also apparent in our relaxation of +fine-grain call-by-value in Figure~\ref{fig:cps-cbv-target} as the +function position of an application can be a computation. % -The following handler implements this behaviour. +In Section~\ref{sec:first-order-uncurried-cps} we will refine this +translation to be properly tail-recursive. % -\[ - \bl - \nondet : (\UnitType \to \alpha \eff \{\Fork:\UnitType \opto \Bool\}) \to \List~\alpha\\ - \nondet~m \defas - \ba[t]{@{}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& [res]\\ - \OpCase{\Fork}{\Unit}{resume} &\mapsto& resume~\True \concat resume~\False - \ea - \ea - \el -\] +As for administrative redexes, observe that the first reduction is +administrative. It is an artefact introduced by the translation, and +thus it has nothing to do with the dynamic semantics of the original +term. We can eliminate such redexes statically. We will address this +issue in Section~\ref{sec:higher-order-uncurried-deep-handlers-cps}. + +Nevertheless, we can show that the image of this CPS translation +simulates the preimage. Due to the presence of administrative +reductions, the simulation is not on the nose, but instead up to +congruence. % -The $\Return$-case returns a singleton list containing a result of -running $m$. +For reduction in the untyped target calculus we write +$\reducesto_{\textrm{cong}}$ for the smallest relation containing +$\reducesto$ that is closed under the term formation constructs. % -The $\Fork$-case invokes the provided resumption $resume$ twice. Each -invocation of $resume$ effectively copies $m$ and runs each copy to -completion. Each copy returns through the $\Return$-case, hence each -invocation of $resume$ returns a list of the possible results obtained -by interpreting $\Fork$ first as $\True$ and subsequently as -$\False$. The results are joined by list concatenation ($\concat$). +\begin{theorem}[Simulation] + \label{thm:fo-simulation} + If $M \reducesto N$ then $\pcps{M} \reducesto_{\textrm{cong}}^+ + \pcps{N}$. +\end{theorem} + +\begin{proof} +The result follows by composing a call-by-value variant of +\citeauthor{ForsterKLP19}'s translation from effect handlers to +delimited continuations~\citeyearpar{ForsterKLP19} with +\citeauthor{MaterzokB12}'s CPS translation for delimited +continuations~\citeyearpar{MaterzokB12}. +\end{proof} + +% \paragraph*{Remark} +% We originally derived this curried CPS translation for effect handlers +% by composing \citeauthor{ForsterKLP17}'s translation from effect +% handlers to delimited continuations~\citeyearpar{ForsterKLP17} with +% \citeauthor{MaterzokB12}'s CPS translation for delimited +% continuations~\citeyearpar{MaterzokB12}. + +\subsection{Uncurried translation} +\label{sec:first-order-uncurried-cps} % -Thus the handler returns a list of all the possible results of $m$. % -In fact, this handler is exactly the standard handler for -nondeterministic choice, which satisfies the standard semi-lattice -equations~\cite{PlotkinP09,PlotkinP13}. -% \dhil{This is an instance of non-blind backtracking~\cite{FriedmanHK84}} +\begin{figure} + \flushleft + \textbf{Syntax} + \begin{syntax} + \slab{Computations} &M,N \in \UCompCat &::= & \cdots \mid \XCancel{M\,W} \mid V\,W \mid U\,V\,W \smallskip \\ + \XCancel{\slab{Evaluation contexts}} &\XCancel{\EC \in \UEvalCat} &::= & \XCancel{[~] \mid \EC\;W} \\ + \end{syntax} -Let us consider $\nondet$ together with the previously defined -handlers. But first, let us define two computations. + \textbf{Reductions} + \begin{reductions} + \usemlab{App_1} & (\lambda x . M) V &\reducesto& M[V/x] \\ + \usemlab{App_2} & (\lambda x . \lambda y. \, M) V\, W &\reducesto& M[V/x,W/y] \\ + \XCancel{\usemlab{Lift}} & \XCancel{\EC[M]} &\reducesto& \XCancel{\EC[N], \hfill \text{if } M \reducesto N} + \end{reductions} + \caption{Adjustments to the syntax and semantics of $\UCalc$.} + \label{fig:refined-cps-cbv-target} +\end{figure} % -\[ - \bl - \quoteRitchie,\;\quoteHamlet : \UnitType \to \UnitType \eff \{\Write: \Record{\UFD;\String} \opto \UnitType\} \smallskip\\ - \quoteRitchie\,\Unit \defas - \ba[t]{@{~}l} - \echo~\strlit{UNIX is basically };\\ - \echo~\strlit{a simple operating system, };\\ - \echo~\strlit{but };\\ - \echo~\texttt{"} - \ba[t]{@{}l} - \texttt{you have to be a genius }\\ - \texttt{to understand the simplicity.\nl{}"} - \ea - \ea \smallskip\\ - \quoteHamlet\,\Unit \defas - \ba[t]{@{}l} - \echo~\strlit{To be, or not to be, };\\ - \echo~\strlit{that is the question:\nl};\\ - \echo~\strlit{Whether 'tis nobler in the mind to suffer\nl} - \ea - \el -\] +In this section we will refine the CPS translation for deep handlers +to make it properly tail-recursive. As remarked in the previous +section, the lack of tail-recursion is apparent in the syntax of the +target calculus $\UCalc$ as it permits an arbitrary computation term +in the function position of an application term. % -The computation $\quoteRitchie$ writes a quote by Dennis Ritchie to -the file, whilst the computation $\quoteHamlet$ writes a few lines of -William Shakespeare's \emph{The Tragedy of Hamlet, Prince of Denmark}, -Act III, Scene I~\cite{Shakespeare6416} to the file. + +As a first step we may restrict the syntax of the target calculus such +that the term in function position must be a value. With this +restriction the syntax of $\UCalc$ implements the property that any +term constructor features at most one computation term, and this +computation term always appears in tail position. This restriction +suffices to ensure that every function application will be in tail +position. % -Using $\nondet$ and $\fork$ together with the previously defined -infrastructure, we can fork the initial process such that both of the -above computations are run concurrently. -% -\[ - \ba{@{~}l@{~}l} - &\bl - \basicIO\,(\lambda\Unit.\\ - \qquad\nondet\,(\lambda\Unit.\\ - \qquad\qquad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ - \qquad\qquad\qquad\status\,(\lambda\Unit. - \ba[t]{@{}l} - \If\;\fork\,\Unit\;\Then\; - \su~\Alice;\, - \quoteRitchie~\Unit\\ - \Else\; - \su~\Bob;\, - \quoteHamlet~\Unit)})) - \ea - \el \smallskip\\ - \reducesto^+& - \Record{ - \ba[t]{@{}l} - [0, 0]; - \texttt{"}\ba[t]{@{}l} - \texttt{UNIX is basically a simple operating system, but }\\ - \texttt{you have to be a genius to understand the simplicity.\nl}\\ - \texttt{To be, or not to be, that is the question:\nl}\\ - \texttt{Whether 'tis nobler in the mind to suffer\nl"}} : \Record{\List~\Int; \UFile} - \ea - \ea - \ea -\] -% -The computation running under the $\status$ handler immediately -performs an invocation of fork, causing $\nondet$ to explore both the -$\Then$-branch and the $\Else$-branch. In the former, $\Alice$ signs -in and quotes Ritchie, whilst in the latter Bob signs in and quotes a -Hamlet. +Figure~\ref{fig:refined-cps-cbv-target} contains the adjustments to +syntax and semantics of $\UCalc$. The target calculus now supports +both unary and binary application forms. As we shall see shortly, +binary application turns out be convenient when we enrich the notion +of continuation. Both application forms are comprised only of value +terms. As a result the dynamic semantics of $\UCalc$ no longer makes +use of evaluation contexts. The reduction rule $\usemlab{App_1}$ +applies to unary application and it is the same as the +$\usemlab{App}$-rule in Figure~\ref{fig:cps-cbv-target}. The new +$\usemlab{App_2}$-rule applies to binary application: it performs a +simultaneous substitution of the arguments $V$ and $W$ for the +parameters $x$ and $y$, respectively, in the function body $M$. % -Looking at the output there is supposedly no interleaving of -computation, since the individual writes have not been -interleaved. From the stack of handlers, we \emph{know} that there has -been no interleaving of computation, because no handler in the stack -handles interleaving. Thus, our system only supports time sharing in -the extreme sense: we know from the $\nondet$ handler that every -effect of the parent process will be performed and handled before the -child process gets to run. In order to be able to share time properly -amongst processes, we must be able to interrupt them. -\paragraph{Interleaving computation} +These changes to $\UCalc$ immediately invalidate the curried +translation from the previous section as the image of the translation +is no longer well-formed. % -We need an operation for interruptions and corresponding handler to -handle interrupts in order for the system to support interleaving of -processes. +The crux of the problem is that the curried interpretation of +continuations causes the CPS translation to produce `large' +application terms, e.g. the translation rule for effect forwarding +produces a three-argument application term. % -\[ - \bl - \interrupt : \UnitType \to \UnitType \eff \{\Interrupt : \UnitType \opto \UnitType\}\\ - \interrupt~\Unit \defas \Do\;\Interrupt~\Unit - \el -\] +To rectify this problem we can adapt the technique of +\citet{MaterzokB12} to uncurry our CPS translation. Uncurrying +necessitates a change of representation for continuations: a +continuation is now an alternating list of pure continuation functions +and effect continuation functions. Thus, we move to an explicit +representation of the runtime handler stack. % -The intended behaviour of an invocation of $\Interrupt$ is to suspend -the invoking computation in order to yield time for another -computation to run. +The change of continuation representation means the CPS translation +for effect handlers is no longer a conservative extension. The +translation is adjusted as follows to account for the new +representation. % -We can achieve this behaviour by reifying the process state. For the -purpose of interleaving processes via interruptions it suffices to -view a process as being in either of two states: 1) it is done, that -is it has run to completion, or 2) it is paused, meaning it has -yielded to provide room for another process to run. +\begin{equations} +\cps{-} &:& \CompCat \to \UCompCat\\ +\cps{\Return~V} &\defas& \lambda (k \cons ks).k\,\cps{V}\,ks \\ +\cps{\Let~x \revto M~\In~N} &\defas& \lambda (k \cons ks).\cps{M}((\lambda x.\lambda ks'.\cps{N}(k \cons ks')) \cons ks) +\smallskip \\ +\cps{\Do\;\ell\;V} &\defas& \lambda (k \cons h \cons ks).h\,\Record{\ell,\Record{\cps{V}, \lambda x.\lambda ks'.k\,x\,(h \cons ks')}}\,ks +\smallskip \\ +\cps{\Handle \; M \; \With \; H} &\defas& \lambda ks . \cps{M} (\cps{\hret} \cons \cps{\hops} \cons ks) \medskip\\ +\cps{-} &:& \HandlerCat \to \UCompCat\\ +\cps{\{\Return \; x \mapsto N\}} &\defas& \lambda x.\lambda ks.\Let\; (h \cons ks') = ks \;\In\; \cps{N}\,ks' +\\ +\cps{\{\ell \; p \; r \mapsto N_\ell\}_{\ell \in \mathcal{L}}} +&\defas& +\bl +\lambda \Record{z,\Record{p,r}}. \lambda ks. \Case \; z \; + \{( \bl\ell \mapsto \cps{N_\ell}\,ks)_{\ell \in \mathcal{L}};\,\\ + y \mapsto \hforward((y,p,r),ks) \}\el \\ +\el \\ +\hforward((y,p,r),ks) &\defas& \bl + \Let\; (k' \cons h' \cons ks') = ks \;\In\; \\ + h'\,\Record{y, \Record{p, \lambda x.\lambda ks''.\,r\,x\,(k' \cons h' \cons ks'')}}\,ks'\\ + \el \medskip\\ +\pcps{-} &:& \CompCat \to \UCompCat\\ +\pcps{M} &\defas& \cps{M}~((\lambda x.\lambda ks.x) \cons (\lambda \Record{z,\Record{p,r}}. \lambda ks.\,\Absurd~z) \cons \nil) +\end{equations} % -We can model the state using a recursive variant type parameterised by -some return value $\alpha$ and a set of effects $\varepsilon$ that the -process may perform. +The other cases are as in the original CPS translation in +Figure~\ref{fig:cps-cbv}. % -\[ - \Pstate~\alpha~\varepsilon~\theta \defas - \ba[t]{@{}l@{}l} - [&\Done:\alpha;\\ - &\Suspended:\UnitType \to \Pstate~\alpha~\varepsilon~\theta \eff \{\Interrupt:\theta;\varepsilon\} ] - \ea -\] +Since we now use a list representation for the stacks of +continuations, we have had to modify the translations of all the +constructs that manipulate continuations. For $\Return$ and $\Let$, we +extract the top continuation $k$ and manipulate it analogously to the +original translation in Figure~\ref{fig:cps-cbv}. For $\Do$, we +extract the top pure continuation $k$ and effect continuation $h$ and +invoke $h$ in the same way as the curried translation, except that we +explicitly maintain the stack $ks$ of additional continuations. The +translation of $\Handle$, however, pushes a continuation pair onto the +stack instead of supplying them as arguments. Handling of operations +is the same as before, except for explicit passing of the +$ks$. Forwarding now pattern matches on the stack to extract the next +continuation pair, rather than accepting them as arguments. % -This data type definition is an instance of the \emph{resumption - monad}~\cite{Papaspyrou01}. The $\Done$-tag simply carries the -return value of type $\alpha$. The $\Suspended$-tag carries a -suspended computation, which returns another instance of $\Pstate$, -and may or may not perform any further invocations of -$\Interrupt$. Payload type of $\Suspended$ is precisely the type of a -resumption originating from a handler that handles only the operation -$\Interrupt$ such as the following handler. +% Proper tail recursion coincides with a refinement of the target +% syntax. Now applications are either of the form $V\,W$ or of the form +% $U\,V\,W$. We could also add a rule for applying a two argument lambda +% abstraction to two arguments at once and eliminate the +% $\usemlab{Lift}$ rule, but we defer this until our higher order +% translation in Section~\ref{sec:higher-order-uncurried-cps}. + +Let us revisit the example from +Section~\ref{sec:first-order-curried-cps} to see first hand that our +refined translation makes the example properly tail-recursive. % -\[ - \bl - \reifyP : (\UnitType \to \alpha \eff \{\Interrupt: \UnitType \opto \UnitType;\varepsilon\}) \to \Pstate~\alpha~\varepsilon\\ - \reifyP~m \defas - \ba[t]{@{}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& \Done~res\\ - \OpCase{\Interrupt}{\Unit}{resume} &\mapsto& \Suspended~resume - \ea - \ea - \el -\] +\begin{equations} +\pcps{\Return\;\Record{}} + &= & (\lambda (k \cons ks).k\,\Record{}\,ks)\,((\lambda x.\lambda ks.x) \cons (\lambda \Record{z, \_}.\lambda ks.\Absurd\,z) \cons \nil) \\ + &\reducesto& (\lambda x.\lambda ks.x)\,\Record{}\,((\lambda \Record{z,\_}.\lambda ks.\Absurd\,z) \cons \nil)\\ + &\reducesto& \Record{} +\end{equations} % -This handler tags and returns values with $\Done$. It also tags and -returns the resumption provided by the $\Interrupt$-case with -$\Suspended$. +The reduction sequence in the image of this uncurried translation has +one fewer steps (disregarding the administrative steps induced by +pattern matching) than in the image of the curried translation. The +`missing' step is precisely the reduction marked +\eqref{eq:cps-admin-reduct-2}, which was a partial application of the +initial pure continuation function that was not in tail +position. Note, however, that the first reduction (corresponding to +\eqref{eq:cps-admin-reduct-1}) remains administrative, the reduction +is entirely static, and as such, it can be dealt with as part of the +translation. % -This particular implementation is amounts to a handler-based variation -of \citeauthor{Harrison06}'s non-reactive resumption -monad~\cite{Harrison06}. + +\paragraph{Administrative redexes} + +We can determine whether a redex is administrative in the image by +determining whether it corresponds to a redex in the preimage. If +there is no corresponding redex, then the redex is said to be +administrative. We can further classify an administrative redex as to +whether it is \emph{static} or \emph{dynamic}. + +A static administrative redex is a by-product of the translation that +does not contribute to the implementation of the dynamic behaviour of +the preimage. % -If we compose this handler with the nondeterminism -handler, then we obtain a term with the following type. +The separation between value and computation terms in fine-grain +call-by-value makes it evident where static administrative redexes can +arise. They arise from computation terms, which can clearly be seen +from the translation where each computation term induces a +$\lambda$-abstraction. Each induced $\lambda$-abstraction must +necessarily be eliminated by a unary application. These unary +applications are administrative; they do not correspond to reductions +in the preimage. The applications that do correspond to reductions in +the preimage are the binary (continuation) applications. + +A dynamic administrative redex is a genuine implementation detail that +supports some part of the dynamic behaviour of the preimage. An +example of such a detail is the implementation of effect +forwarding. In $\HCalc$ effect forwarding involves no auxiliary +reductions, any operation invocation is instantaneously dispatched to +a suitable handler (if such one exists). % -\[ - \nondet\,(\lambda\Unit.\reifyP~m) : \List~(\Pstate~\alpha~\{\Fork: \UnitType \opto \Bool;\varepsilon\}) -\] +The translation presented above realises effect forwarding by +explicitly applying the next effect continuation. This application is +an example of a dynamic administrative reduction. Not every dynamic +administrative reduction is necessary, though. For instance, the +implementation of resumptions as a composition of +$\lambda$-abstractions gives rise to administrative reductions upon +invocation. As we shall see in +Section~\ref{sec:first-order-explicit-resump} administrative +reductions due to resumption invocation can be dealt with by choosing +a more clever implementation of resumptions. + +\subsection{Resumptions as explicit reversed stacks} +\label{sec:first-order-explicit-resump} % -for some $m : \UnitType \to \{\Proc;\varepsilon\}$ where -$\Proc \defas \{\Fork: \UnitType \opto \Bool;\Interrupt: \UnitType -\opto \UnitType\}$. +% \dhil{Show an example involving administrative redexes produced by resumptions} % -The composition yields a list of process states, some of which may be -in suspended state. In particular, the suspended computations may have -unhandled instances of $\Fork$ as signified by it being present in the -effect row. The reason for this is that in the above composition when -$\reifyP$ produces a $\Suspended$-tagged resumption, it immediately -returns through the $\Return$-case of $\nondet$, meaning that the -resumption escapes the $\nondet$. Recall that a resumption is a -delimited continuation that captures the extent from the operation -invocation up to and including the nearest enclosing suitable -handler. In this particular instance, it means that the $\nondet$ -handler is part of the extent. -% -We ultimately want to return just a list of $\alpha$s to ensure every -process has run to completion. To achieve this, we need a function -that keeps track of the state of every process, and in particular it -must run each $\Suspended$-tagged computation under the $\nondet$ -handler to produce another list of process state, which must be -handled recursively. -% -\[ - \bl - \schedule : \List~(\Pstate~\alpha~\{\Fork:\Bool;\varepsilon\}~\theta) \to \List~\alpha \eff \varepsilon\\ - \schedule~ps \defas - \ba[t]{@{}l} - \Let\;run \revto - \Rec\;sched\,\Record{ps;done}.\\ - \qquad\Case\;ps\;\{ - \ba[t]{@{}r@{~}c@{~}l} - \nil &\mapsto& done\\ - (\Done~res) \cons ps' &\mapsto& sched\,\Record{ps';res \cons done}\\ - (\Suspended~m) \cons ps' &\mapsto& sched\,\Record{ps' \concat (\nondet~m);\, done} \} - \ea\\ - \In\;run\,\Record{ps;\nil} - \ea - \el -\] +Thus far resumptions have been represented as functions, and +forwarding has been implemented using function composition. The +composition of resumption gives rise to unnecessary dynamic +administrative redexes as function composition necessitates the +introduction of an additional lambda abstraction. % -The function $\schedule$ implements a process scheduler. It takes as -input a list of process states, where $\Suspended$-tagged computations -may perform the $\Fork$ operation. Locally it defines a recursive -function $sched$ which carries a list of active processes $ps$ and the -results of completed processes $done$. The function inspects the -process list $ps$ to test whether it is empty or nonempty. If it is -empty it returns the list of results $done$. Otherwise, if the head is -$\Done$-tagged value, then the function is recursively invoked with -tail of processes $ps'$ and the list $done$ augmented with the value -$res$. If the head is a $\Suspended$-tagged computation $m$, then -$sched$ is recursively invoked with the process list $ps'$ -concatenated with the result of running $m$ under the $\nondet$ -handler. - +As an illustration of how and where these administrative redexes arise +let us consider an example with an operation $\Ask : \Unit \opto \Int$ +and two handlers $H_\Reader$ and $H_\Other$ such that +$H_\Reader^\Ask = \{\OpCase{\Ask}{\Unit}{r} \mapsto r~42\}$ whilst +$\Ask \not\in \dom(H_\Other)$. We denote the top-level continuation by +$ks_\top$. % -Using the above machinery, we can define a function which adds -time-sharing capabilities to the system. +% \[ +% \bl +% \Reader \defas \{\Ask : \Unit \opto \Int\} \smallskip\\ +% H_{\Reader} : \alpha \eff \Reader \Harrow \alpha, \{ \OpCase{\Ask}{\Unit}{r} \mapsto r~42 \} \in H_{\Reader}\\ +% H_{\Other} : \alpha \eff \varepsilon \Harrow \alpha, \Ask \not\in \dom(H_{\Reader}) +% \el +% \] % -\[ - \bl - \timeshare : (\UnitType \to \alpha \eff \Proc) \to \List~\alpha\\ - \timeshare~m \defas \schedule\,[\Suspended\,(\lambda\Unit.\reifyP~m)] - \el -\] +\begin{derivation} + &\pcps{\Handle\; (\Handle\; \Do\;\Ask\,\Unit\;\With\;H_{\Other})\;\With\;H_{\Reader}}\\ + % =& \reason{definition of $\cps{-}$}\\ + % % &\lambda ks.\cps{\Handle\; \Do\;\Ask\,\Unit\;\With\;H_{\Other}}(\cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks)\\ + % % =& \reason{}\\ + % &(\lambda ks.(\lambda ks'.\cps{\Do\;\Ask\,\Unit}(\cps{H_{\Other}^\mret} \cons \cps{H_{\Other}^\mops} \cons ks'))(\cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks))\,ks_\top\\ + =& \reason{definition of $\pcps{-}$}\\ + &(\lambda ks. + \bl + (\lambda ks'. + \bl + (\lambda (k \cons h \cons ks'').h\,\Record{\Ask,\Record{\Unit,\lambda x.\lambda ks'''.k~x~(h \cons ks''')}}\,ks'')\\ + (\cps{H_{\Other}^\mret} \cons \cps{H_{\Other}^\mops} \cons ks')) + \el\\ + (\cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks))\,ks_\top + \el\\ + % \reducesto^\ast& \reason{apply continuation}\\ + % & (\lambda (k \cons h \cons ks'').h\,\Record{\Ask,\Record{\Unit,\lambda x.\lambda ks'''.k~x~(h \cons ks''')}})(\cps{H_{\Other}^\mret} \cons \cps{H_{\Other}^\mops} \cons \cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks_\top)\\ + \reducesto^\ast & \reason{multiple applications of \usemlab{App}, activation of $H_\Other$}\\ + & \cps{H_{\Other}^\mops}\,\Record{\Ask,\Record{\Unit,\lambda x.\lambda ks'''.\cps{H_{\Other}^\mret}~x~(\cps{H_{\Other}^\mops} \cons ks''')}}\,(\cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks_\top)\\ + \reducesto^\ast & \reason{effect forwarding to $H_\Reader$}\\ + & \bl + H_{\Reader}^\mops\,\Record{\Ask,\Record{\Unit,\lambda x.\lambda ks''. r_\dec{admin}~x~(H_{\Reader}^\mret \cons H_{\Reader}^\mops \cons ks'')}}\,ks_\top\\ + \where~r_\dec{admin} \defas \lambda x.\lambda + ks'''.\cps{H_{\Other}^\mret}~x~(\cps{H_{\Other}^\mops} \cons ks''') + \el\\ + \reducesto^\ast & \reason{invocation of the administrative resumption} \\ + & r_\dec{admin}~42~(H_{\Reader}^\mret \cons H_{\Reader}^\mops \cons ks_\top)\\ + \reducesto^\ast & \reason{invocation of the resumption of the operation invocation site}\\ + & \cps{H_{\Other}^\mret}~42~(\cps{H_{\Other}^\mops} \cons + H_{\Reader}^\mret \cons H_{\Reader}^\mops \cons ks_\top) +\end{derivation} % -The function $\timeshare$ handles the invocations of $\Fork$ and -$\Interrupt$ in some computation $m$ by starting it in suspended state -under the $\reifyP$ handler. The $\schedule$ actually starts the -computation, when it runs the computation under the $\nondet$ handler. +Effect forwarding introduces the administrative abstraction +$r_{\dec{admin}}$, whose sole purpose is to forward the interpretation +of the operation to the operation invocation site. In a certain sense +$r_{\dec{admin}}$ is a sort of identity frame. The insertion of +identities ought to always trigger the alarm bells as an identity +computation is typically extraneous. % +The amount of identity frames being generated scales linearly with the +number of handlers the operation needs to pass through before reaching +a suitable handler. -The question remains how to inject invocations of $\Interrupt$ such -that computation gets interleaved. - -\paragraph{Interruption via interception} +We can avoid generating these administrative resumption redexes by +applying a variation of the technique that we used in the previous +section to uncurry the curried CPS translation. % -To implement process preemption operating systems typically to rely on -the underlying hardware to asynchronously generate some kind of -interruption signals. These signals can be caught by the operating -system's process scheduler, which can then decide to which processes -to suspend and continue. +Rather than representing resumptions as functions, we move to an +explicit representation of resumptions as \emph{reversed} stacks of +pure and effect continuations. By choosing to reverse the order of +pure and effect continuations, we can construct resumptions +efficiently using regular cons-lists. We augment the syntax and +semantics of $\UCalc$ with a computation term +$\Let\;r=\Res\,V\;\In\;N$ which allow us to convert these reversed +stacks to actual functions on demand. % -If our core calculi had an integrated notion of asynchrony and effects -along the lines of \citeauthor{AhmanP21}'s core calculus -$\lambda_{\text{\ae}}$~\cite{AhmanP21}, then we could potentially -treat interruption signals as asynchronous effectful operations, which -can occur spontaneously and, as suggested by \citet{DolanEHMSW17} and -realised by \citet{Poulson20}, be handled by a user-definable handler. +\begin{reductions} + \usemlab{Res} + & \Let\;r=\Res\,(V_n \cons \dots \cons V_1 \cons \nil)\;\In\;N + & \reducesto + & N[\lambda x\,k.V_1\,x\,(V_2 \cons \dots \cons V_n \cons k)/r] +\end{reductions} % - -In the absence of asynchronous effects we have to inject synchronous -interruptions ourselves. +This reduction rule reverses the stack, extracts the top continuation +$V_1$, and prepends the remainder onto the current stack $W$. The +stack representing a resumption and the remaining stack $W$ are +reminiscent of the zipper data structure for representing cursors in +lists~\cite{Huet97}. Thus we may think of resumptions as representing +pointers into the stack of handlers. % -One extreme approach is to trust the user to perform invocations of -$\Interrupt$ periodically. +The translations of $\Do$, handling, and forwarding need to be +modified to account for the change in representation of +resumptions. % -Another approach is based on the fact that every effect (except for -divergence) occurs via some operation invocation, and every-so-often -the user is likely to perform computational effect, thus the basic -idea is to bundle $\Interrupt$ with invocations of other -operations. For example, we can insert an instance of $\Interrupt$ in -some of the wrapper functions for operation invocations that we have -defined so conscientiously thus far. The problem with this approach is -that it requires a change of type signatures. To exemplify this -problem consider type of the $\echo$ function if we were to bundle an -invocation of $\Interrupt$ along side $\Write$. +\begin{equations} +\cps{-} &:& \CompCat \to \UCompCat\\ + \cps{\Do\;\ell\;V} + &\defas& \lambda k \cons h \cons ks.\,h\, \Record{\ell,\Record{\cps{V}, h \cons k \cons \nil}}\, ks + \medskip\\ % -\[ - \bl - \echo' : \String \to \UnitType \eff \{\Interrupt : \UnitType \opto \UnitType;\Write : \Record{\UFD;\String} \opto \UnitType\}\\ - \echo'~cs \defas \Do\;\Interrupt\,\Unit;\,\Do\;\Write\,\Record{\stdout;cs} - \el -\] +\cps{-} &:& \HandlerCat \to \UCompCat\\ + \cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}} + &\defas& \bl + \lambda \Record{z,\Record{p,rs}}.\lambda ks.\Case \;z\; \{ + \bl + (\ell \mapsto \Let\;r=\Res\;rs \;\In\; \cps{N_{\ell}}\, ks)_{\ell \in \mathcal{L}};\,\\ + y \mapsto \hforward((y,p,rs),ks) \} \\ + \el \\ + \el \\ + \hforward((y,p,rs),ks) + &\defas&\Let\; (k' \cons h' \cons ks') = ks \;\In\; h'\,\Record{y,\Record{p,h' \cons k' \cons rs}} \,ks' +\end{equations} +% +The translation of $\Do$ constructs an initial resumption stack, +operation clauses extract and convert the current resumption stack +into a function using the $\Res$ construct, and $\hforward$ augments +the current resumption stack with the current continuation pair. % -In addition to $\Write$ the effect row must now necessarily mention -the $\Interrupt$ operation. As a consequence this approach is not -backwards compatible, since the original definition of $\echo$ can be -used in a context that prohibits occurrences of $\Interrupt$. Clearly, -this alternative definition cannot be applied in such a context. -There is backwards-compatible way to bundle the two operations -together. We can implement a handler that \emph{intercepts} -invocations of $\Write$ and handles them by performing an interrupt -and, crucially, reperforming the intercepted write operation. +\subsection{Higher-order translation for deep effect handlers} +\label{sec:higher-order-uncurried-deep-handlers-cps} % -\[ - \bl - \dec{interruptWrite} : - \ba[t]{@{~}l@{~}l} - &(\UnitType \to \alpha \eff \{\Interrupt : \UnitType \opto \UnitType;\Write : \Record{\UFD;\String} \opto \UnitType\})\\ - \to& \alpha \eff \{\Interrupt : \UnitType \opto \UnitType;\Write : \Record{\UFD;\String} \opto \UnitType\} - \ea\\ - \dec{interruptWrite}~m \defas - \ba[t]{@{~}l} - \Handle\;m~\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& res\\ - \OpCase{\Write}{\Record{fd;cs}}{resume} &\mapsto& - \ba[t]{@{}l} - \interrupt\,\Unit;\\ - resume\,(\Do\;\Write~\Record{fd;cs}) - \ea - \ea - \ea - \el -\] +\begin{figure} % -This handler is not `self-contained' as the other handlers we have -defined previously. It gives in some sense a `partial' interpretation -of $\Write$ as it leaves open the semantics of $\Interrupt$ and -$\Write$, i.e. this handler must be run in a suitable context of other -handlers. - -Let us plug this handler into the previous example to see what -happens. +\textbf{Values} % -\[ - \ba{@{~}l@{~}l} - &\bl - \basicIO\,(\lambda\Unit.\\ - \qquad\timeshare\,(\lambda\Unit.\\ - \qquad\qquad\dec{interruptWrite}\,(\lambda\Unit.\\ - \qquad\qquad\qquad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ - \qquad\qquad\qquad\qquad\status\,(\lambda\Unit. - \ba[t]{@{}l} - \If\;\fork\,\Unit\;\Then\; - \su~\Alice;\, - \quoteRitchie~\Unit\\ - \Else\; - \su~\Bob;\, - \quoteHamlet~\Unit)}))) - \ea - \el \smallskip\\ - \reducesto^+& - \bl - \Record{ - \ba[t]{@{}l} - [0, 0]; - \texttt{"}\ba[t]{@{}l} - \texttt{UNIX is basically To be, or not to be,\nl{}}\\ - \texttt{a simple operating system, that is the question:\nl{}}\\ - \texttt{but Whether 'tis nobler in the mind to suffer\nl{}}\\ - \texttt{you have to be a genius to understand the simplicity.\nl{}"}} - \ea - \ea\\ - : \Record{\List~\Int; \UFile} - \el - \ea -\] +\begin{displaymath} +\begin{eqs} +\cps{-} &:& \ValCat \to \UValCat\\ +\cps{x} &\defas& x \\ +\cps{\lambda x.M} &\defas& \dlam x\,ks.\Let\;(k \dcons h \dcons ks') = ks \;\In\;\cps{M} \sapp (\reflect k \scons \reflect h \scons \reflect ks') \\ +% \cps{\Rec\,g\,x.M} &\defas& \Rec\;f\,x\,ks.\cps{M} \sapp \reflect ks\\ +\cps{\Lambda \alpha.M} &\defas& \dlam \Unit\,ks.\Let\;(k \dcons h \dcons ks') = ks \;\In\;\cps{M} \sapp (\reflect k \scons \reflect h \scons \reflect ks') \\ +\cps{\Record{}} &\defas& \Record{} \\ +\cps{\Record{\ell = V; W}} &\defas& \Record{\ell = \cps{V}; \cps{W}} \\ +\cps{\ell~V} &\defas& \ell~\cps{V} \\ +\end{eqs} +\end{displaymath} % -Evidently, each write operation has been interleaved, resulting in a -mishmash poetry of Shakespeare and \UNIX{}. +\textbf{Computations} % -I will leave it to the reader to be the judge of whether this new -poetry belongs under the category of either classic arts vandalism or -novel contemporary reinterpretations. As the saying goes: \emph{art - is in the eye of the beholder}. - -\subsection{State: file i/o} -\label{sec:tiny-unix-io} - -Thus far the system supports limited I/O, abnormal process -termination, multiple user sessions, and multi-tasking via concurrent -processes. At this stage we have most of core features in place. We -still have to complete the I/O model. The current I/O model provides -an incomplete file system consisting of a single write-only file. +\begin{equations} +\cps{-} &:& \CompCat \to \SValCat^\ast \to \UCompCat\\ +\cps{V\,W} &\defas& \slam \sks.\cps{V} \dapp \cps{W} \dapp \reify \sks \\ +\cps{V\,T} &\defas& \slam \sks.\cps{V} \dapp \Record{} \dapp \reify \sks \\ +\cps{\Let\; \Record{\ell=x;y} = V \; \In \; N} &\defas& \slam \sks.\Let\; \Record{\ell=x;y} = \cps{V} \; \In \; \cps{N} \sapp \sks \\ +\cps{\Case~V~\{\ell~x \mapsto M; y \mapsto N\}} &\defas& + \slam \sks.\Case~\cps{V}~\{\ell~x \mapsto \cps{M} \sapp \sks; y \mapsto \cps{N} \sapp \sks\} \\ +\cps{\Absurd~V} &\defas& \slam \sks.\Absurd~\cps{V} \\ +\cps{\Return~V} &\defas& \slam \sk \scons \sks.\reify \sk \dapp \cps{V} \dapp \reify \sks \\ +\cps{\Let~x \revto M~\In~N} &\defas& \slam \sk \scons \sks.\cps{M} \sapp + (\reflect (\dlam x\,\dhk. + \ba[t]{@{}l} + \Let\;(h \dcons \dhk') = \dhk\;\In\\ + \cps{N} \sapp (\sk \scons \reflect h \scons \reflect \dhk')) \scons \sks) + \ea\\ +\cps{\Do\;\ell\;V} + &\defas& \slam \sk \scons \sh \scons \sks.\reify \sh \dapp \Record{\ell,\Record{\cps{V}, \reify \sh \dcons \reify \sk \dcons \dnil}} \dapp \reify \sks\\ +\cps{\Handle \; M \; \With \; H} &\defas& \slam \sks . \cps{M} \sapp (\reflect \cps{\hret} \scons \reflect \cps{\hops} \scons \sks) % -In this section we will implement a \UNIX{}-like file system that -supports file creation, opening, truncation, read and write -operations, and file linking. +\end{equations} % - -To implement a file system we will need to use state. State can -readily be implemented with an effect handler~\cite{KammarLO13}. +\textbf{Handler definitions} % -It is a deliberate choice to leave state for last, because once you -have state it is tempting to use it excessively --- to the extent it -becomes a cliche. +\begin{equations} +\cps{-} &:& \HandlerCat \to \UValCat\\ +\cps{\{\Return \; x \mapsto N\}} &\defas& \dlam x\, \dhk. + \ba[t]{@{~}l} + \Let\; (h \dcons \dk \dcons h' \dcons \dhk') = \dhk \;\In\\ + \cps{N} \sapp (\reflect \dk \scons \reflect h' \scons \reflect \dhk') + \ea +\\ +\cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}} + &\defas& \bl + \dlam \Record{z,\Record{p,\dhkr}}\,\dhk.\Case \;z\; \{ + \ba[t]{@{}l@{}c@{~}l} + &(\ell \mapsto& + \ba[t]{@{}l} + \Let\;r=\Res\;\dhkr \;\In\\ + \Let\;(\dk \dcons h \dcons \dhk') = \dhk \;\In\\ + \cps{N_{\ell}} \sapp (\reflect \dk \scons \reflect h \scons \reflect \dhk'))_{\ell \in \mathcal{L}}; + \ea\\ + &y \mapsto& \hforward((y,p,\dhkr),\dhk) \} \\ + \ea \\ + \el \\ +\hforward((y,p,\dhkr),\dhk) + &\defas&\Let\; (\dk' \dcons h' \dcons \dhk') = \dhk \;\In\; h' \dapp \Record{y,\Record{p,h' \dcons \dk' \dcons \dhkr}} \dapp \dhk' +\end{equations} % -As demonstrated in the previous sections, it is possible to achieve -many things that have a stateful flavour without explicit state by -harnessing the implicit state provided by the program stack. - -In the following subsection, I will provide an interface for stateful -operations and their implementation in terms of a handler. The -stateful operations will be put to use in the subsequent subsection to -implement a basic sequential file system. +\textbf{Top level program} +% +\begin{equations} +\pcps{-} &:& \CompCat \to \UCompCat\\ +\pcps{M} &=& \cps{M} \sapp (\reflect (\dlam x\,\dhk.x) \scons \reflect (\dlam z\,\dhk.\Absurd~z) \scons \snil) \\ +\end{equations} -\subsubsection{Handling state} +\caption{Higher-order uncurried CPS translation of $\HCalc$.} +\label{fig:cps-higher-order-uncurried} +\end{figure} +% +In the previous sections, we have seen step-wise refinements of the +initial curried CPS translation for deep effect handlers +(Section~\ref{sec:first-order-curried-cps}) to be properly +tail-recursive (Section~\ref{sec:first-order-uncurried-cps}) and to +avoid yielding unnecessary dynamic administrative redexes for +resumptions (Section~\ref{sec:first-order-explicit-resump}). +% +There is still one outstanding issue, namely, that the translation +yields static administrative redexes. In this section we will further +refine the CPS translation to eliminate all static administrative +redexes at translation time. +% +Specifically, the translation will be adapted to a higher-order +one-pass CPS translation~\citep{DanvyF90} that partially evaluates +administrative redexes at translation time. +% +Following \citet{DanvyN03}, I will use a two-level lambda calculus +notation to distinguish between \emph{static} lambda abstraction and +application in the meta language and \emph{dynamic} lambda abstraction +and application in the target language. To disambiguate syntax +constructors in the respective calculi I will mark static constructors +with a {\color{blue}$\overline{\text{blue overline}}$}, whilst dynamic +constructors are marked with a +{\color{red}$\underline{\text{red underline}}$}. The principal idea is +that redexes marked as static are reduced as part of the translation, +whereas those marked as dynamic are reduced at runtime. To facilitate +this notation I will write application explicitly using an infix +``at'' symbol ($@$) in both calculi. -The interface for accessing and updating a state cell consists of two -operations. +\paragraph{Static terms} % -\[ - \State~\beta \defas \{\Get:\UnitType \opto \beta;\Put:\beta \opto \UnitType\} -\] +As in the dynamic target language, continuations are represented as +alternating lists of pure continuation functions and effect +continuation functions. To ease notation I will make use of pattern +matching notation. The static meta language is generated by the +following productions. % -The intended operational behaviour of $\Get$ operation is to read the -value of type $\beta$ of the state cell, whilst the $\Put$ operation -is intended to replace the current value held by the state cell with -another value of type $\beta$. As per usual business, the following -functions abstract the invocation of the operations. +\begin{syntax} + \slab{Static\text{ }patterns} &\sP \in \SPatCat &::=& \sks \mid \sk \scons \sP\\ + \slab{Static\text{ }values} & \sV, \sW \in \SValCat &::=& \reflect V \mid \sV \scons \sW \mid \slam \sP. \sM\\ + \slab{Static\text{ }computations} & \sM \in \SCompCat &::=& \sV \mid \sV \sapp \sW \mid \sV \dapp V \dapp W +\end{syntax} % -\[ - \ba{@{~}l@{\quad\qquad\quad}c@{~}l} - \Uget : \UnitType \to \beta \eff \{\Get:\UnitType \opto \beta\} - & & - \Uput : \UnitType \to \beta \eff \{\Put:\beta \opto \UnitType\}\\ - \Uget~\Unit \defas \Do\;\Get~\Unit - & & - \Uput~st \defas \Do\;\Put~st - \el -\] +The patterns comprise only static list deconstructing. We let $\sP$ +range over static patterns. % -The following handler interprets the operations. +The static values comprise reflected dynamic values, static lists, and +static lambda abstractions. We let $\sV, \sW$ range over meta language +values; by convention we shall use variables $\sk$ to denote +statically known pure continuations, $\sh$ to denote statically known +effect continuations, and $\sks$ to denote statically known +continuations. +% +I shall use $\sM$ to range over static computations, which comprise +static values, static application and binary dynamic application of a +static value to two dynamic values. +% +Static computations are subject to the following equational axioms. +% +\begin{equations} + (\slam \sks. \sM) \sapp \sV &\defas& \sM[\sV/\sks]\\ + (\slam \sk \scons \sks. \sM) \sapp (\sV \scons \sW) &\defas& (\slam \sks. \sM[\sV/\sk]) \sapp \sW\\ +\end{equations} +% +The first equation is static $\beta$-equivalence, it states that +applying a static lambda abstraction with binder $\sks$ and body $\sM$ +to a static value $\sV$ is equal to substituting $\sV$ for $\sks$ in +$\sM$. The second equation provides a means for applying a static +lambda abstraction to a static list component-wise. +% + +Reflected static values are reified as dynamic language values +$\reify \sV$ by induction on their structure. % \[ - \bl - \runState : \Record{\beta;\UnitType \to \alpha \eff \State~\beta} \to \Record{\alpha;\beta}\\ - \runState~\Record{st_0;m} \defas - \ba[t]{@{}l} - \Let\;run \revto - \ba[t]{@{}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& \lambda st.\Record{res;st}\\ - \OpCase{\Get}{\Unit}{resume} &\mapsto& \lambda st.resume~st~st\\ - \OpCase{\Put}{st'}{resume} &\mapsto& \lambda st.resume~\Unit~st' - \ea - \ea\\ - \In\;run~st_0 - \ea - \el + \ba{@{}l@{\qquad}c} + \reify \reflect V \defas V + &\reify (\sV \scons \sW) \defas \reify \sV \dcons \reify \sW + \ea \] % -The $\runState$ handler provides a generic way to interpret any -stateful computation. It takes as its first parameter the initial -value of the state cell. The second parameter is a potentially -stateful computation. Ultimately, the handler returns the value of the -input computation along with the current value of the state cell. -This formulation of state handling is analogous to the standard -monadic implementation of state handling~\citep{Wadler95}. In the -context of handlers, the implementation uses a technique known as -\emph{parameter-passing}~\citep{PlotkinP09,Pretnar15}. +\paragraph{Higher-order translation} % -Each case returns a state-accepting function. +As we shall see this translation manipulates the continuation +intricate ways; and since we maintain the interpretation of the +continuation as an alternating list of pure continuation functions and +effect continuation functions it is useful to define the `parity' of a +continuation as follows: % -The $\Return$-case returns a function that produces a pair consisting -of return value of $m$ and the final state $st$. +a continuation is said to be \emph{odd} if the top element is an +effect continuation function, otherwise it is said to \emph{even}. + % -The $\Get$-case returns a function that applies the resumption -$resume$ to the current state $st$. Recall that return type of a -resumption is the same as its handler's return type, so since the -handler returns a function, it follows that -$resume : \beta \to \beta \to \Record{\alpha, \beta}$. In other words, -the invocation of $resume$ produces another state-accepting -function. This function arises from the next activation of the handler -either by way of a subsequent operation invocation in $m$ or the -completion of $m$ to invoke the $\Return$-case. Since $\Get$ does not -modify the value of the state cell it passes $st$ unmodified to the -next handler activation. -% -In the $\Put$-case the resumption must also produce a state-accepting -function of the same type, however, the type of the resumption is -slightly different -$resume : \UnitType \to \beta \to \Record{\alpha, \beta}$. The unit -type is the expected return type of $\Put$. The state-accepting -function arising from $resume~\Unit$ is supplied with the new state -value $st'$. This application effectively discards the current state -value $st$. - -The first operation invocation in $m$, or if it completes without -invoking $\Get$ or $\Put$, the handler returns a function that accepts -the initial state. The function gets bound to $run$ which is -subsequently applied to the provided initial state $st_0$ which causes -evaluation of the stateful fragment of $m$ to continue. -\paragraph{Local state vs global state} The meaning of stateful -operations may depend on whether the ambient environment is -nondeterministic. Post-composing nondeterminism with state gives rise -to the so-called \emph{local state} phenomenon, where state -modifications are local to each strand of nondeterminism, that is each -strand maintains its own copy of the state. Local state is also known -as `backtrackable state' in the literature~\cite{GibbonsH11}, because -returning back to a branch point restores the state as it were prior -to the branch. In contrast, post-composing state with nondeterminism -results in a \emph{global state} interpretation, where the state is -shared across every strand of nondeterminism. In terms of backtracking -this means the original state does not get restored upon a return to -some branch point. +The complete CPS translation is given in +Figure~\ref{fig:cps-higher-order-uncurried}. In essence, it is the +same as the refined first-order uncurried CPS translation, although +the notation is slightly more involved due to the separation of static +and dynamic parts. -For modelling the file system we opt for the global state -interpretation such that changes made to file system are visible to -all processes. The local state interpretation could prove useful if we -were to model a virtual file system per process such that each process -would have its own unique standard out file. +As before, the translation comprises three translation functions, one +for each syntactic category: values, computations, and handler +definitions. Amongst the three functions, the translation function for +computations stands out, because it is the only one that operates on +static continuations. Its type signature, +$\cps{-} : \CompCat \to \SValCat^\ast \to \UCompCat$, signifies that +it is a binary function, taking a $\HCalc$-computation term as its +first argument and a static continuation (a list of static values) as +its second argument, and ultimately produces a $\UCalc$-computation +term. Thus the computation translation function is able to manipulate +the continuation. In fact, the translation is said to be higher-order +because the continuation parameter is a higher-order: it is a list of +functions. -The two state phenomena are inter-encodable. \citet{PauwelsSM19} give -a systematic behaviour-preserving transformation for nondeterminism -with local state into nondeterminism with global state and vice versa. +To ensure that static continuation manipulation is well-defined the +translation maintains the invariant that the statically known +continuation stack ($\sk$) always contains at least two continuation +functions, i.e. a complete continuation pair consisting of a pure +continuation function and an effect continuation function. +% +This invariant guarantees that all translations are uniform in whether +they appear statically within the scope of a handler or not, and this +also simplifies the correctness proof +(Theorem~\ref{thm:ho-simulation}). +% +Maintaining this invariant has a cosmetic effect on the presentation +of the translation. This effect manifests in any place where a +dynamically known continuation stack is passed in (as a continuation +parameter $\dhk$), as it must be deconstructed using a dynamic +language $\Let$ to expose the continuation structure and subsequently +reconstructed as a static value with reflected variable names. +The translation of $\lambda$-abstractions provides an example of this +deconstruction and reconstruction in action. The dynamic continuation +$\dhk$ is deconstructed to expose to the next pure continuation +function $\dk$ and effect continuation $h$, and the remainder of the +continuation $\dhk'$; these names are immediately reflected and put +back together to form a static continuation that is provided to the +translation of the body computation $M$. -\subsubsection{Basic serial file system} +The only translation rule that consumes a complete reflected +continuation pair is the translation of $\Do$. The effect continuation +function, $\sh$, is dynamically applied to an operation package and +the reified remainder of the continuation $\sks$. As usual, the +operation package contains the payload and the resumption, which is +represented as a reversed continuation slice. % -\begin{figure}[t] - \centering - \begin{tabular}[t]{| l |} - \hline - \multicolumn{1}{| c |}{\textbf{Directory}} \\ - \hline - \strlit{hamlet}\tikzmark{hamlet}\\ - \hline - \strlit{ritchie.txt}\tikzmark{ritchie}\\ - \hline - \multicolumn{1}{| c |}{$\vdots$}\\ - \hline - \strlit{stdout}\tikzmark{stdout}\\ - \hline - \multicolumn{1}{| c |}{$\vdots$}\\ - \hline - \strlit{act3}\tikzmark{act3}\\ - \hline - \end{tabular} - \hspace{1.5cm} - \begin{tabular}[t]{| c |} - \hline - \multicolumn{1}{| c |}{\textbf{I-List}} \\ - \hline - 1\tikzmark{ritchieino}\\ - \hline - 2\tikzmark{hamletino}\\ - \hline - \multicolumn{1}{| c |}{$\vdots$}\\ - \hline - 1\tikzmark{stdoutino}\\ - \hline - \end{tabular} - \hspace{1.5cm} - \begin{tabular}[t]{| l |} - \hline - \multicolumn{1}{| c |}{\textbf{Data region}} \\ - \hline - \tikzmark{stdoutdr}\strlit{}\\ - \hline - \tikzmark{hamletdr}\strlit{To be, or not to be...}\\ - \hline - \multicolumn{1}{| c |}{$\vdots$}\\ - \hline - \tikzmark{ritchiedr}\strlit{UNIX is basically...}\\ - \hline - \end{tabular} - %% Hamlet arrows. - \tikz[remember picture,overlay]\draw[->,thick,out=30,in=160] ([xshift=1.23cm,yshift=0.1cm]pic cs:hamlet) to ([xshift=-0.85cm,yshift=0.1cm]pic cs:hamletino) node[] {}; - \tikz[remember picture,overlay]\draw[->,thick,out=30,in=180] ([xshift=0.62cm,yshift=0.1cm]pic cs:hamletino) to ([xshift=-0.23cm,yshift=0.1cm]pic cs:hamletdr) node[] {}; - %% Ritchie arrows. - \tikz[remember picture,overlay]\draw[->,thick,out=-30,in=180] ([xshift=0.22cm,yshift=0.1cm]pic cs:ritchie) to ([xshift=-0.85cm,yshift=0.1cm]pic cs:ritchieino) node[] {}; - \tikz[remember picture,overlay]\draw[->,thick,out=30,in=180] ([xshift=0.62cm,yshift=0.1cm]pic cs:ritchieino) to ([xshift=-0.23cm,yshift=0.1cm]pic cs:ritchiedr) node[] {}; - %% Act3 arrow. - \tikz[remember picture,overlay]\draw[->,thick,out=10,in=210] ([xshift=1.64cm,yshift=0.1cm]pic cs:act3) to ([xshift=-0.85cm,yshift=-0.5mm]pic cs:hamletino) node[] {}; - %% Stdout arrows. - \tikz[remember picture,overlay]\draw[->,thick,out=30,in=180] ([xshift=1.23cm,yshift=0.1cm]pic cs:stdout) to ([xshift=-0.85cm,yshift=0.1cm]pic cs:stdoutino) node[] {}; - \tikz[remember picture,overlay]\draw[->,thick,out=30,in=180] ([xshift=0.62cm,yshift=0.1cm]pic cs:stdoutino) to ([xshift=-0.23cm,yshift=0.1cm]pic cs:stdoutdr) node[] {}; - \caption{\UNIX{} directory, i-list, and data region mappings.}\label{fig:unix-mappings} -\end{figure} +The only other translation rules that manipulate the continuation are +$\Return$ and $\Let$, which only consume the pure continuation +function $\sk$. For example, the translation of $\Return$ is a dynamic +application of $\sk$ to the translation of the value $V$ and the +remainder of the continuation $\sks$. % -A file system provide an abstraction over storage media in a computer -system by organising the storage space into a collection of files. -This abstraction facilities typical file operations: allocation, -deletion, reading, and writing. +The shape of $\sks$ is odd, meaning that the top element is an effect +continuation function. Thus the pure continuation $\sk$ has to account +for this odd shape. Fortunately, the possible instantiations of the +pure continuation are few. We can derive the all possible +instantiations systematically by using the operational semantics of +$\HCalc$. According to the operational semantics the continuation of a +$\Return$-computation is either the continuation of a +$\Let$-expression or a $\Return$-clause (a bare top-level +$\Return$-computation is handled by the $\pcps{-}$ translation). % -\UNIX{} dogmatises the notion of file to the point where -\emph{everything is a file}. A typical \UNIX{}-style file system -differentiates between ordinary files, directory files, and special -files~\cite{RitchieT74}. An ordinary file is a sequence of -characters. A directory file is a container for all kinds of files. A -special file is an interface for interacting with an i/o device. - -We will implement a \emph{basic serial file system}, which we dub -\fsname{}. +The translations of $\Let$-expressions and $\Return$-clauses each +account for odd continuations. For example, the translation of $\Let$ +consumes the current pure continuation function and generates a +replacement: a pure continuation function which expects an odd dynamic +continuation $\dhk$, which it deconstructs to expose the effect +continuation $h$ along with the current pure continuation function in +the translation of $N$. The modified continuation is passed to the +translation of $M$. % -It will be basic in the sense that it models the bare minimum to pass -as a file system, that is we will implement support for the four basic -operations: file allocation, file deletion, file reading, and file -writing. +To provide a flavour of how this continuation manipulation functions +in practice, consider the following example term. % -The read and write operations will be serial, meaning every file is -read in order from its first character to its last character, and -every file is written to by appending the new content. +\begin{derivation} + &\pcps{\Let\;x \revto \Return\;V\;\In\;N}\\ + =& \reason{definition of $\pcps{-}$}\\ + &\ba[t]{@{}l}(\slam \sk \scons \sks.\cps{\Return\;V} \sapp + (\reflect(\dlam x\,ks. + \ba[t]{@{}l} + \Let\;(h \dcons ks') = ks \;\In\\ + \cps{N} \sapp (\sk \scons \reflect h \scons \reflect ks')) \scons \sks) + \ea\\ + \sapp (\reflect (\dlam x\,ks.x) \scons \reflect (\dlam z\,ks.\Absurd~z) \scons \snil)) + \ea\\ + =& \reason{definition of $\cps{-}$}\\ + &\ba[t]{@{}l}(\slam \sk \scons \sks.(\slam \sk \scons \sks. \reify \sk \dapp \cps{V} \dapp \reify \sks) \sapp + (\reflect(\dlam x\,ks. + \ba[t]{@{}l} + \Let\;(h \dcons ks') = ks \;\In\\ + \cps{N} \sapp (\sk \scons \reflect h \scons \reflect ks')) \scons \sks) + \ea\\ + \sapp (\reflect (\dlam x\,ks.x) \scons \reflect (\dlam z\,ks.\Absurd~z) \scons \snil)) + \ea\\ + =& \reason{static $\beta$-reduction}\\ + &(\slam \sk \scons \sks. \reify \sk \dapp \cps{V} \dapp \reify \sks) + \sapp + (\reflect(\dlam x\,\dhk. + \ba[t]{@{}l} + \Let\;(h \dcons \dhk') = \dhk \;\In\\ + \cps{N} \sapp + \ba[t]{@{}l} + (\reflect (\dlam x\,\dhk.x) \scons \reflect h \scons \reflect \dhk'))\\ + ~~\scons \reflect (\dlam z\,\dhk.\Absurd~z) \scons \snil)) + \ea + \ea\\ + =& \reason{static $\beta$-reduction}\\ + &\ba[t]{@{}l@{~}l} + &(\dlam x\,\dhk. + \Let\;(h \dcons \dhk') = \dhk \;\In\; + \cps{N} \sapp + (\reflect (\dlam x\,\dhk.x) \scons \reflect h \scons \reflect \dhk'))\\ + \dapp& \cps{V} \dapp ((\dlam z\,\dhk.\Absurd~z) \dcons \dnil)\\ + \ea\\ +\reducesto& \reason{\usemlab{App_2}}\\ + &\Let\;(h \dcons \dhk') = (\dlam z\,\dhk.\Absurd~z) \dcons \dnil \;\In\; + \cps{N[V/x]} \sapp + (\reflect (\dlam x\,\dhk.x) \scons \reflect h \scons \reflect \dhk'))\\ +\reducesto^+& \reason{dynamic pattern matching and substitution}\\ + &\cps{N[V/x]} \sapp + (\reflect (\dlam x\,\dhk.x) \scons \reflect (\dlam z\,\dhk.\Absurd~z) \scons \reflect \dnil) +\end{derivation} % -\fsname{} will only contain ordinary files, and as a result -the file hierarchy will be entirely flat. Although, the system can -readily be extended to be hierarchical, it comes at the expense of -extra complexity, that blurs rather than illuminates the model. +The translation of $\Return$ provides the generated dynamic pure +continuation function with the odd continuation +$((\dlam z\,ks.\Absurd~z) \dcons \dnil)$. After the \usemlab{App_2} +reduction, the pure continuation function deconstructs the odd +continuation in order to bind the current effect continuation function +to the name $h$, which would have been used during the translation of +$N$. -\paragraph{Directory, i-list, and data region} +The translation of $\Handle$ applies the translation of $M$ to the +current continuation extended with the translation of the +$\Return$-clause, acting as a pure continuation function, and the +translation of operation-clauses, acting as an effect continuation +function. % -A storage medium is an array of bytes. An \UNIX{} file system is -implemented on top of this array by interpreting certain intervals of -the array differently. These intervals provide the space for the -essential administrative structures for file organisation. -% -\begin{enumerate} - \item The \emph{directory} is a collection of human-readable names for - files. In general, a file may have multiple names. Each name is - stored along with a pointer into the i-list. - \item The \emph{i-list} is a collection of i-nodes. Each i-node - contains the meta data for a file along with a pointer into the data - region. - \item The \emph{data region} contains the actual file contents. -\end{enumerate} +The translation of a $\Return$-clause discards the effect continuation +$h$ and in addition exposes the next pure continuation $\dk$ and +effect continuation $h'$ which are reflected to form a static +continuation for the translation of $N$. % -These structures make up the \fsname{}. +The translation of operation clauses unpacks the provided operation +package to perform a case-split on the operation label $z$. The branch +for $\ell$ deconstructs the continuation $\dhk$ in order to expose the +continuation structure. The forwarding branch also deconstructs the +continuation, but for a different purpose; it augments the resumption +$\dhkr$ with the next pure and effect continuation functions. + +Let us revisit the example from +Section~\ref{sec:first-order-curried-cps} to see that the higher-order +translation eliminates the static redex at translation time. % -Figure~\ref{fig:unix-mappings} depicts an example with the three -structures and a mapping between them. +\begin{equations} +\pcps{\Return\;\Record{}} + &=& (\slam \sk \scons \sks. \sk \dapp \Record{} \dapp \reify \sks) \sapp (\reflect (\dlam x\,\dhk.x) \scons \reflect (\dlam z\,\dhk.\Absurd\;z) \scons \snil)\\ + &=& (\dlam x\,\dhk.x) \dapp \Record{} \dapp (\reflect (\dlam z\,\dhk.\Absurd\;z) \dcons \dnil)\\ + &\reducesto& \Record{} +\end{equations} % -The only file meta data tracked by \fsname{} is the number of names for -a file. +In contrast with the previous translations, the reduction sequence in +the image of this translation contains only a single dynamic reduction +(disregarding the dynamic administrative reductions arising from +continuation construction and deconstruction); both +\eqref{eq:cps-admin-reduct-1} and \eqref{eq:cps-admin-reduct-2} +reductions have been eliminated as part of the translation. + +The elimination of static redexes coincides with a refinements of the +target calculus. Unary application is no longer a necessary +primitive. Every unary application dealt with by the metalanguage, +i.e. all unary applications are static. + +\paragraph{Implicit lazy continuation deconstruction} % -The three structures and their mappings can be implemented using -association lists. Although, a better practical choice may be a -functional map or functional array~\cite{Okasaki99}, association lists -have the advantage of having a simple, straightforward implementation. +An alternative to the explicit deconstruction of continuations is to +implicitly deconstruct continuations on demand when static pattern +matching fails. I took this approach in \citet{HillerstromLAS17}. On +one hand this approach leads to a slightly slicker presentation. On +the other hand it complicates the proof of correctness as one must +account for static pattern matching failure. % -\[ - \ba{@{~}l@{\qquad}c@{~}l} - \Directory \defas \List\,\Record{\String;\Int} &&% - \DataRegion \defas \List\,\Record{\Int;\UFile} \smallskip\\ - \INode \defas \Record{lno:\Int;loc:\Int} &&% - \IList \defas \List\,\Record{\Int;\INode} - \ea -\] +A practical argument in favour of the explicit eager continuation +deconstruction is that it is more accessible from an implementation +point of view. No implementation details are hidden away in side +conditions. % -Mathematically, we may think the type $\dec{Directory}$ as denoting a -partial function $\C^\ast \pto \Z$, where $\C$ is a suitable -alphabet. The function produces an index into the i-list. +Also, it is not clear that lazy deconstruction has any advantage over +eager deconstruction, as the translation must reify the continuation +when it transitions from computations to values and reflect the +continuation when it transitions from values to computations, in which +case static pattern matching would fail. + +\subsubsection{Correctness} +\label{sec:higher-order-cps-deep-handlers-correctness} + +We establish the correctness of the higher-order uncurried CPS +translation via a simulation result in the style of +Plotkin~\cite{Plotkin75} (Theorem~\ref{thm:ho-simulation}). However, +before we can state and prove this result, we first several auxiliary +lemmas describing how translated terms behave. First, the higher-order +CPS translation commutes with substitution. % -Similarly, the type $\dec{IList}$ denotes a partial function -$\Z \pto \Z \times \Z$, where the codomain is the denotation of -$\dec{INode}$. The first component of the pair is the number of names -linked to the i-node, and as such $\Z$ is really an overapproximation -as an i-node cannot have a negative number of names. The second -component is an index into the data region. +\begin{lemma}[Substitution]\label{lem:ho-cps-subst} + % + The higher-order uncurried CPS translation commutes with + substitution in value terms + % + \[ + \cps{W}[\cps{V}/x] = \cps{W[V/x]}, + \] + % + and with substitution in computation terms + \[ + (\cps{M} \sapp (\sk \scons \sh \scons \sks))[\cps{V}/x] + = \cps{M[V/x]} \sapp (\sk \scons \sh \scons \sks)[\cps{V}/x], + \] + % + and with substitution in handler definitions + % + \begin{equations} + \cps{\hret}[\cps{V}/x] + &=& \cps{\hret[V/x]},\\ + \cps{\hops}[\cps{V}/x] + &=& \cps{\hops[V/x]}. + \end{equations} +\end{lemma} % -The denotation of the type $\dec{DataRegion}$ is another partial -function $\Z \pto \C^\ast$. - -We define the type of the file system to be a record of the three -association lists along with two counters for the next available index -into the data region and i-list, respectively. +\begin{proof} + By mutual induction on the structure of $W$, $M$, $\hret$, and + $\hops$. +\end{proof} % -\[ - \FileSystem \defas \Record{ - \ba[t]{@{}l} - dir:\Directory;ilist:\IList;dreg:\DataRegion;\\ - dnext:\Int;inext:\Int} - \ea -\] +It follows as a corollary that top-level substitution is well-behaved. % -We can then give an implementation of the initial state of the file -system. +\begin{corollary}[Top-level substitution] + \[ + \pcps{M}[\cps{V}/x] = \pcps{M[V/x]}. + \] +\end{corollary} % -\[ - \dec{fs}_0 \defas \Record{ - \ba[t]{@{}l} - dir=[\Record{\strlit{stdout};0}];ilist=[\Record{0;\Record{lno=1;loc=0}}];dreg=[\Record{0;\strlit{}}];\\ - dnext=1;inext=1} - \ea -\] +\begin{proof} + Follows immediately by the definitions of $\pcps{-}$ and + Lemma~\ref{lem:ho-cps-subst}. +\end{proof} % -Initially the file system contains a single, empty file with the name -$\texttt{stdout}$. Next we will implement the basic operations on the -file system separately. - -We have made a gross simplification here, as a typical file system -would provide some \emph{file descriptor} abstraction for managing -access open files. In \fsname{} we will operate directly on i-nodes, -meaning we define $\UFD \defas \Int$, meaning the file open operation -will return an i-node identifier. As consequence it does not matter -whether a file is closed after use as file closing would be a no-op -(closing a file does not change the state of its i-node). Therefore -\fsname{} will not provide a close operation. As a further consequence -the file system will have no resource leakage. - -\paragraph{File reading and writing} +In order to reason about the behaviour of the \semlab{Op} rule, which +is defined in terms of an evaluation context, we need to extend the +CPS translation to evaluation contexts. % -Let us begin by giving a semantics to file reading and writing. We -need an abstract operation for each file operation. +\begin{equations} +\cps{-} &:& \EvalCat \to \SValCat\\ +\cps{[~]} &\defas& \slam \sks.\sks \\ +\cps{\Let\; x \revto \EC \;\In\; N} &\defas& \slam \sk \scons \sks.\cps{\EC} \sapp + (\reflect(\dlam x\,ks. + \ba[t]{@{}l} + \Let\;(h \dcons ks') = ks\;\In\;\\ + \cps{N} \sapp (\sk \scons \reflect h \scons \reflect ks')) \scons \sks) + \ea\\ +\cps{\Handle\; \EC \;\With\; H} &\defas& \slam \sks. \cps{\EC} \sapp (\cps{\hret} \scons \cps{\hops} \scons \sks) +\end{equations} % -\[ - \dec{FileRW} \defas \{\URead : \Int \opto \Option~\String;\UWrite : \Record{\Int;\String} \opto \UnitType\} -\] +The following lemma is the characteristic property of the CPS +translation on evaluation contexts. % -The operation $\URead$ is parameterised by an i-node number -(i.e. index into the i-list) and possibly returns the contents of the -file pointed to by the i-node. The operation may fail if it is -provided with a stale i-node number. Thus the option type is used to -signal failure or success to the caller. +It provides a means for decomposing an evaluation context, such that +we can focus on the computation contained within the evaluation +context. % -The $\UWrite$ operation is parameterised by an i-node number and some -strings to be appended onto the file pointed to by the i-node. The -operation returns unit, and thus the operation does not signal to its -caller whether it failed or succeed. +\begin{lemma}[Decomposition] +\label{lem:decomposition} % -Before we implement a handler for the operations, we will implement -primitive read and write operations that operate directly on the file -system. We will use the primitive operations to implement the -semantics for $\URead$ and $\UWrite$. To implement the primitive the -operations we will need two basic functions on association lists. I -will only their signatures here. +\begin{equations} +\cps{\EC[M]} \sapp (\sV \scons \sW) &=& \cps{M} \sapp (\cps{\EC} \sapp (\sV \scons \sW)) \\ +\end{equations} % -\[ - \bl - \lookup : \Record{\alpha;\List\,\Record{\alpha;\beta}} \to \beta \eff \{\Fail : \UnitType \opto \ZeroType\} \smallskip\\ - \modify : \Record{\alpha;\beta;\List\,\Record{\alpha;\beta}} \to \Record{\alpha;\beta} - \el -\] +\end{lemma} % -Given a key of type $\alpha$ the $\lookup$ function returns the -corresponding value of type $\beta$ in the given association list. If -the key does not exists, then the function invokes the $\Fail$ -operation to signal failure. +\begin{proof} + By structural induction on the evaluation context $\EC$. +\end{proof} % -The $\modify$ function takes a key and a value. If the key exists in -the provided association list, then it replaces the value bound by the -key with the provided value. +Even though we have eliminated the static administrative redexes, we +still need to account for the dynamic administrative redexes that +arise from pattern matching against a reified continuation. To +properly account for these administrative redexes it is convenient to +treat list pattern matching as a primitive in $\UCalc$, therefore we +introduce a new reduction rule $\usemlab{SplitList}$ in $\UCalc$. % -Using these functions we can implement the primitive read and write -operations. +\begin{reductions} +\usemlab{SplitList} & \Let\; (k \dcons ks) = V \dcons W \;\In\; M &\reducesto& M[V/k, W/ks] \\ +\end{reductions} % -\[ - \bl - \fread : \Record{\Int;\FileSystem} \to \String \eff \{\Fail : \UnitType \opto \ZeroType\}\\ - \fread\,\Record{ino;fs} \defas - \ba[t]{@{}l} - \Let\;inode \revto \lookup\,\Record{ino; fs.ilist}\;\In\\ - \lookup\,\Record{inode.loc; fs.dreg} - \el - \el -\] +Note this rule is isomorphic to the \usemlab{Split} rule with lists +encoded as right nested pairs using unit to denote nil. % -The function $\fread$ takes as input the i-node number for the file to -be read and a file system. First it looks up the i-node structure in -the i-list, and then it uses the location in the i-node to look up the -file contents in the data region. Since $\fread$ performs no exception -handling it will fail if either look up fails. The implementation of -the primitive write operation is similar. -% -\[ - \bl - \fwrite : \Record{\Int;\String;\FileSystem} \to \FileSystem \eff \{\Fail : \UnitType \opto \ZeroType\}\\ - \fwrite\,\Record{ino;cs;fs} \defas - \ba[t]{@{}l} - \Let\;inode \revto \lookup\,\Record{ino; fs.ilist}\;\In\\ - \Let\;file \revto \lookup\,\Record{inode.loc; fs.dreg}\;\In\\ - \Record{\,fs\;\keyw{with}\;dreg = \modify\,\Record{inode.loc;file \concat cs;fs}} - \el - \el -\] +We write $\areducesto$ for the compatible closure of +\usemlab{SplitList}. + +We also need to be able to reason about the computational content of +reflection after reification. By definition we have that +$\reify \reflect V = V$, the following lemma lets us reason about the +inverse composition. % -The first two lines grab hold of the file, whilst the last line -updates the data region in file system by appending the string $cs$ -onto the file. +\begin{lemma}[Reflect after reify] +\label{lem:reflect-after-reify} % -Before we can implement the handler, we need an exception handling -mechanism. The following exception handler interprets $\Fail$ as some -default value. +Reflection after reification may give rise to dynamic administrative +reductions, i.e. % \[ - \bl - \faild : \Record{\alpha;\UnitType \to \alpha \eff \{\Fail : \UnitType \opto \ZeroType\}} \to \alpha\\ - \faild\,\Record{default;m} \defas - \ba[t]{@{~}l} - \Handle\;m~\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;x &\mapsto& x\\ - \OpCase{\Fail}{\Unit}{\_} &\mapsto& default - \ea - \ea - \el +\cps{M} \sapp (\sV_1 \scons \dots \sV_n \scons \reflect \reify \sW) + \areducesto^\ast \cps{M} \sapp (\sV_1 \scons \dots \sV_n \scons \sW) \] +\end{lemma} % -The $\Fail$-case is simply the default value, whilst the -$\Return$-case is the identity. -% -Now we can use all the above pieces to implement a handler for the -$\URead$ and $\UWrite$ operations. -% -\[ - \bl - \fileRW : (\UnitType \to \alpha \eff \dec{FileRW}) \to \alpha \eff \State~\FileSystem\\ - \fileRW~m \defas - \ba[t]{@{}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& res\\ - \OpCase{\URead}{ino}{resume} &\mapsto& - \bl - \Let\;cs\revto \faild\,\Record{\None;\lambda\Unit.\\ - \quad\Some\,(\fread\,\Record{ino;\Uget\,\Unit})}\\ - \In\;resume~cs - \el\\ - \OpCase{\UWrite}{\Record{ino;cs}}{resume} &\mapsto& - \ba[t]{@{}l} - \faild~\Record{\Unit; \lambda \Unit.\\ - \quad\bl - \Let\;fs \revto \fwrite\,\Record{ino;cs;\Uget\,\Unit}\\ - \In\;\Uput~fs};\,resume\,\Unit - \el - \ea - \ea - \ea - \el -\] +\begin{proof} + By induction on the structure of $M$. +\end{proof} % -The $\URead$-case uses the $\fread$ function to implement reading a -file. The file system state is retrieved using the state operation -$\Uget$. The possible failure of $\fread$ is dealt with by the -$\faild$ handler by interpreting failure as $\None$. +We next observe that the CPS translation simulates forwarding. % -The $\UWrite$-case makes use of the $\fwrite$ function to implement -writing to a file. Again the file system state is retrieved using -$\Uget$. The $\Uput$ operation is used to update the file system state -with the state produced by the successful invocation of -$\fwrite$. Failure is interpreted as unit, meaning that from the -caller's perspective the operation fails silently. - -\paragraph{File creation and opening} -The signature of file creation and opening is unsurprisingly comprised -of two operations. +\begin{lemma}[Forwarding] +\label{lem:forwarding} +If $\ell \notin dom(H_1)$ then % \[ - \dec{FileCO} \defas \{\UCreate : \String \opto \Option~\Int; \UOpen : \String \opto \Option~\Int\} + \cps{\hops_1} \dapp \Record{\ell,\Record{U, V}} \dapp (V_2 \dcons \cps{\hops_2} \dcons W) +\reducesto^+ + \cps{\hops_2} \dapp \Record{\ell,\Record{U, \cps{\hops_2} \dcons V_2 \dcons V}} \dapp W \] % -The implementation of file creation and opening follows the same -pattern as the implementation of reading and writing. As before, we -implement a primitive routine for each operation that interacts -directly with the file system structure. We first implement the -primitive file opening function as the file creation function depends -on this function. +\end{lemma} % -\[ - \bl - \fopen : \Record{\String;\FileSystem} \to \Int \eff \{\Fail : \UnitType \opto \ZeroType\}\\ - \fopen\,\Record{fname;fs} \defas \lookup\,\Record{fname; fs.dir} - \el -\] +\begin{proof} + By direct calculation. +\end{proof} % -Opening a file in the file system simply corresponds to returning the -i-node index associated with the filename in the directory table. - -The \UNIX{} file create command does one of two things depending on -the state of the file system. If the create command is provided with -the name of a file that is already present in the directory, then the -system truncates the file, and returns the file descriptor for the -file. Otherwise the system allocates a new empty file and returns its -file descriptor~\cite{RitchieT74}. To check whether a file already -exists in the directory we need a function $\dec{has}$ that given a -filename and the file system state returns whether there exists a file -with the given name. This function can be built completely generically -from the functions we already have at our disposal. +Now we show that the translation simulates the \semlab{Op} +rule. +% +\begin{lemma}[Handling] +\label{lem:handle-op} +If $\ell \notin BL(\EC)$ and $\hell = \{\ell\,p\,r \mapsto N_\ell\}$ then % \[ - \bl - \dec{has} : \Record{\alpha;\List\,\Record{\alpha;\beta}} \to \Bool\\ - \dec{has}\,\Record{k;xs} \defas \faild\,\Record{\False;(\lambda\Unit.\lookup\,\Record{k;xs};\True)} - \el +\bl +\cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\reflect\cps{\hret} \scons \reflect\cps{\hops} \scons \sV)) \reducesto^+\areducesto^\ast \\ +\quad + (\cps{N_\ell} \sapp \sV)[\cps{V}/p, (\lambda y\,ks.\cps{\Return\;y} \sapp (\cps{\EC} \sapp (\reflect\cps{\hret} \scons \reflect\cps{\hops} \scons \reflect ks)))/] +\el \] % -The function $\dec{has}$ applies $\lookup$ under the failure handler -with default value $\False$. If $\lookup$ returns successfully then -its result is ignored, and the computation returns $\True$, otherwise -the computation returns the default value $\False$. +\end{lemma} % -With this function we can implement the semantics of create. +\begin{proof} +Follows from Lemmas~\ref{lem:decomposition}, +\ref{lem:reflect-after-reify}, and \ref{lem:forwarding}. +\end{proof} % -\[ - \bl - \fcreate : \Record{\String;\FileSystem} \to \Record{\Int;\FileSystem} \eff \{\Fail : \UnitType \opto \ZeroType\}\\ - \fcreate\,\Record{fname;fs} \defas - \ba[t]{@{}l} - \If\;\dec{has}\,\Record{fname;fs.dir}\;\Then\\ - \quad\bl - \Let\;ino \revto \fopen\,\Record{fname;fs}\;\In\\ - \Let\;inode \revto \lookup\,\Record{ino;fs}\;\In\\ - \Let\;dreg' \revto \modify\,\Record{inode.loc; \strlit{}; fs.dreg}\;\In\\ - \Record{ino;\Record{fs\;\With\;dreg = dreg'}} - \el\\ - \Else\\ - \quad\bl - \Let\;loc \revto fs.lnext \;\In\\ - \Let\;dreg \revto \Record{loc; \strlit{}} \cons fs.dreg\;\In\\ - \Let\;ino \revto fs.inext \;\In\\ - \Let\;inode \revto \Record{loc=loc;lno=1}\;\In\\ - \Let\;ilist \revto \Record{ino;inode} \cons fs.ilist \;\In\\ - \Let\;dir \revto \Record{fname; ino} \cons fs.dir \;\In\\ - \Record{ino;\Record{ - \bl - dir=dir;ilist=ilist;dreg=dreg;\\ - lnext=loc+1;inext=ino+1}} - \el - \el - \el - \el -\] +Finally, we have the ingredients to state and prove the simulation +result. The following theorem shows that the only extra behaviour +exhibited by a translated term is the bureaucracy of deconstructing +the continuation stack. % -The $\Then$-branch accounts for the case where the filename $fname$ -already exists in the directory. First we retrieve the i-node for the -file to obtain its location in the data region such that we can -truncate the file contents. +\begin{theorem}[Simulation] +\label{thm:ho-simulation} +If $M \reducesto N$ then $\pcps{M} \reducesto^+ \areducesto^* \pcps{N}$. +\end{theorem} % -The branch returns the i-node index along with the modified file -system. The $\Else$-branch allocates a new empty file. First we -allocate a location in the data region by copying the value of -$fs.lnext$ and consing the location and empty string onto -$fs.dreg$. The next three lines allocates the i-node for the file in a -similar fashion. The second to last line associates the filename with -the new i-node. The last line returns the identifier for the i-node -along with the modified file system, where the next location ($lnext$) -and next i-node identifier ($inext$) have been incremented. +\begin{proof} + By case analysis on the reduction relation using Lemmas + \ref{lem:decomposition}--\ref{lem:handle-op}. The \semlab{Op} case + follows from Lemma~\ref{lem:handle-op}. +\end{proof} % -It is worth noting that the effect signature of $\fcreate$ mentions -$\Fail$ even though it will never fail. It is present in the effect -row due to the use of $\fopen$ and $\lookup$ in the -$\Then$-branch. Either application can only fail if the file system is -in an inconsistent state, where the index $ino$ has become stale. The -$\dec{f}$-family of functions have been carefully engineered to always -leave the file system in a consistent state. +% In common with most CPS translations, full abstraction does not +% hold. However, as our semantics is deterministic it is straightforward +% to show a backward simulation result. +% % +% \begin{corollary}[Backwards simulation] +% If $\pcps{M} \reducesto^+ \areducesto^* V$ then there exists $W$ such that +% $M \reducesto^* W$ and $\pcps{W} = V$. +% \end{corollary} +% % +% \begin{proof} +% TODO\dots +% \end{proof} % -Now we can implement the semantics for the $\UCreate$ and $\UOpen$ -effectful operations. The implementation is similar to the -implementation of $\fileRW$. -% -\[ - \bl - \fileAlloc : (\UnitType \to \alpha \eff \dec{FileCO}) \to \alpha \eff \State~\FileSystem\\ - \fileAlloc~m \defas - \ba[t]{@{}l} - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& res\\ - \OpCase{\UCreate}{fname}{resume} &\mapsto& - \bl - \Let\;ino \revto \faild\,\Record{\None; \lambda\Unit.\\ - \quad\bl - \Let\;\Record{ino;fs} = \fcreate\,\Record{\,fname;\Uget\,\Unit}\\ - \In\;\Uput~fs;\,\Some~ino} - \el\\ - \In\; resume~ino - \el\\ - \OpCase{\UOpen}{fname}{resume} &\mapsto& - \ba[t]{@{}l} - \Let\; ino \revto \faild~\Record{\None; \lambda \Unit.\\ - \quad\Some\,(\fopen\,\Record{fname;\Uget\,\Unit})}\\ - \In\;resume~ino - \ea - \ea - \ea - \el -\] -% +\section{Transforming shallow effect handlers} +\label{sec:cps-shallow} -\paragraph{Stream redirection} -% -The processes we have defined so far use the $\echo$ utility to write -to the $\stdout$ file. The target file $\stdout$ is hardwired into the -definition of $\echo$ (Section~\ref{sec:tiny-unix-bio}). To take -advantage of the capabilities of the new file system we could choose -to modify the definition of $\echo$ such that it is parameterised by -the target file. However, such a modification is a breaking -change. Instead we can define a \emph{stream redirection} operator -that allow us to redefine the target of $\Write$ operations locally. -% -\[ - \bl - \redirect : - \bl - \Record{\UnitType \to \alpha \eff \{\Write : \Record{\Int;\String} \opto \UnitType\}; \String}\\ - \to \alpha \eff \{\UCreate : \String \opto \Option~\Int;\Exit : \Int \opto \ZeroType;\Write : \Record{\Int;\String} \opto \UnitType\} - \el\\ - m~\redirect~fname \defas - \ba[t]{@{}l} - \Let\;ino \revto \Case\;\Do\;\UCreate~fname\;\{ - \ba[t]{@{~}l@{~}c@{~}l} - \None &\mapsto& \exit~1\\ - \Some~ino &\mapsto& ino\} - \ea\\ - \In\;\Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& res\\ - \OpCase{\Write}{\Record{\_;cs}}{resume} &\mapsto& resume\,(\Do\;\Write\,\Record{ino;cs}) - \ea - \ea - \el -\] +In this section we will continue to build upon the higher-order +uncurried CPS translation +(Section~\ref{sec:higher-order-uncurried-deep-handlers-cps}) in order +to add support for shallow handlers. The dynamic nature of shallow +handlers pose an interesting challenge, because unlike deep resumption +capture, a shallow resumption capture discards the handler leaving +behind a dangling pure continuation. The dangling pure continuation +must be `adopted' by whichever handler the resumption invocation occur +under. This handler is determined dynamically by the context, meaning +the CPS translation must be able to modify continuation pairs. + +In Section~\ref{sec:cps-shallow-flawed} I will discuss an attempt at a +`natural' extension of the higher-order uncurried CPS translation for +deep handlers, but for various reasons this extension is +flawed. However, the insights gained by attempting this extension +leads to yet another change of the continuation representation +(Section~\ref{sec:generalised-continuations}) resulting in the notion +of a \emph{generalised continuation}. % -The operator $\redirect$ first attempts to create a new target file -with name $fname$. If it fails it simply exits with code -$1$. Otherwise it continues with the i-node reference $ino$. The -handler overloads the definition of $\Write$ inside the provided -computation $m$. The new definition drops the i-node reference of the -initial target file and replaces it by the reference to new target -file. +In Section~\ref{sec:cps-gen-conts} we will see how generalised +continuations provide a basis for implementing deep and shallow effect +handlers simultaneously, solving all of the problems encountered thus +far uniformly. -This stream redirection operator is slightly more general than the -original redirection operator in the original \UNIX{} environment. As -the \UNIX{} redirection operator only redirects writes targeted at the -\emph{stdout} file~\cite{RitchieT74}, whereas the above operator -redirects writes regardless of their initial target. +\subsection{A specious attempt} +\label{sec:cps-shallow-flawed} % -It is straightforward to implement this original \UNIX{} behaviour by -inspecting the first argument of $\Write$ in the operation clause -before committing to performing the redirecting $\Write$ operation. +Initially it is tempting to try to extend the interpretation of the +continuation representation in the higher-order uncurried CPS +translation for deep handlers to squeeze in shallow handlers. The main +obstacle one encounters is how to decouple a pure continuation from +its handler such that a it can later be picked up by another handler. + +To fully uninstall a handler, we must uninstall both the pure +continuation function corresponding to its return clause and the +effect continuation function corresponding to its operation clauses. % -Modern \UNIX{} environments typically provide more fine-grained -control over redirects, for example by allowing the user to specify on -a per file basis which writes should be redirected. Again, we can -implement this behaviour by comparing the provided file descriptor -with the descriptor in the payload of $\Write$. +In the current setup it is impossible to reliably uninstall the former +as due to the translation of $\Let$-expressions it may be embedded +arbitrarily deep within the current pure continuation and the +extensional representation of pure continuations means that we cannot +decompose them. -% ([0, 0, 0], -% (dir = [("hamlet", 2), ("ritchie.txt", 1), ("stdout", 0)], -% dregion = [(2, "To be, or not to be, -% that is the question: -% Whether 'tis nobler in the mind to suffer -% "), -% (1, "UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity. -% "), ( -% 0, "")], -% inext = 3, -% inodes = [(2, (lno = 1, loc = 2)), (1, (lno = 1, loc = 1)), (0, (lno = 1, loc = 0))], -% lnext = 3)) : ([Int], FileSystem) -% links> init(fsys0, example7); -% ([0, 0, 0], -% (dir = [("hamlet", 2), ("ritchie.txt", 1), ("stdout", 0)], -% dregion = [(2, "To be, or not to be, -% that is the question: -% Whether 'tis nobler in the mind to suffer -% "), -% (1, "UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity. -% "), ( -% 0, "")], -% inext = 3, -% inodes = [(2, (lno = 1, loc = 2)), (1, (lno = 1, loc = 1)), (0, (lno = 1, loc = 0))], -% lnext = 3)) : ([Int], FileSystem) -\medskip We can plug everything together to observe the new file -system in action. +A quick fix to this problem is to treat pure continuation functions +arising from return clauses separately from pure continuation +functions arising from $\Let$-expressions. % -\[ - \ba{@{~}l@{~}l} - &\bl - \runState\,\Record{\dec{fs}_0;\fileRW\,(\lambda\Unit.\\ - \quad\fileAlloc\,(\lambda\Unit.\\ - \qquad\timeshare\,(\lambda\Unit.\\ - \qquad\quad\dec{interruptWrite}\,(\lambda\Unit.\\ - \qquad\qquad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ - \qquad\qquad\quad\status\,(\lambda\Unit. - \ba[t]{@{}l} - \If\;\fork\,\Unit\;\Then\; - \su~\Alice;\, - \quoteRitchie~\redirect~\strlit{ritchie.txt}\\ - \Else\; - \su~\Bob;\, - \quoteHamlet~\redirect~\strlit{hamlet})}))))} - \ea - \el \smallskip\\ - \reducesto^+& - \bl - \Record{ - \ba[t]{@{}l} - [0, 0];\\ - \Record{ - \ba[t]{@{}l} - dir=[\Record{\strlit{hamlet};2}, - \Record{\strlit{ritchie.txt};1}, - \Record{\strlit{stdout};0}];\\ - ilist=[\Record{2;\Record{lno=1;loc=2}}, - \Record{1;\Record{lno=1;loc=1}}, - \Record{0;\Record{lno=1;loc=0}}];\\ - dreg=[ - \ba[t]{@{}l} - \Record{2; - \ba[t]{@{}l@{}l} - \texttt{"}&\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\ - &\texttt{Whether 'tis nobler in the mind to suffer\nl{}"}}, - \ea\\ - \Record{1; - \ba[t]{@{}l@{}l} - \texttt{"}&\texttt{UNIX is basically a simple operating system, }\\ - &\texttt{but you have to be a genius to understand the simplicity.\nl{"}}}, - \ea\\ - \Record{0; \strlit{}}]; lnext=3; inext=3}}\\ - \ea - \ea - \ea\\ - : \Record{\List~\Int; \FileSystem} - \el - \ea -\] +Thus we can interpret the continuation as a sequence of triples +consisting of two pure continuation functions followed by an effect +continuation function, where the first pure continuation function +corresponds the continuation induced by some $\Let$-expression and the +second corresponds to the return clause of the current handler. % -The writes of the processes $\quoteRitchie$ and $\quoteHamlet$ are now -being redirected to designated files \texttt{ritchie.txt} and -\texttt{hamlet}, respectively. The operating system returns the -completion status of all the processes along with the current state of -the file system such that it can be used as the initial file system -state on the next start of the operating system. - -\subsubsection{File linking and unlinking} +To distinguish between the two kinds of pure continuations, we shall +write $\svhret$ for statically known pure continuations arising from +return clauses, and $\vhret$ for dynamically known ones. Similarly, we +write $\svhops$ and $\vhops$, respectively, for statically and +dynamically, known effect continuations. With this notation in mind, +we may translate operation invocation and handler installation using +the new interpretation of the continuation representation as follows. % -At this point the implementation of \fsname{} is almost feature -complete. However, we have yet to implement two dual file operations: -linking and unlinking. The former enables us to associate a new -filename with an existing i-node, thus providing a mechanism for -making soft copies of files (i.e. the file contents are -shared). The latter lets us dissociate a filename from an i-node, thus -providing a means for removing files. The interface of linking and -unlinking is given below. +\begin{equations} +\cps{-} &:& \CompCat \to \SValCat^\ast \to \UCompCat \smallskip\\ +\cps{\Do\;\ell\;V} &\defas& \slam \sk \scons \svhret \scons \svhops \scons \sks. + \reify\svhops \ba[t]{@{}l} + \dapp \Record{\ell, \Record{\cps{V}, \reify\svhops \dcons \reify\svhret \dcons \reify\sk \dcons \dnil}}\\ + \dapp \reify \sks + \ea\smallskip\\ +\cps{\ShallowHandle \; M \; \With \; H} &\defas& +\slam \sks . \cps{M} \sapp (\reflect\kid \scons \reflect\cps{\hret} \scons \reflect\cps{\hops}^\dagger \scons \sks) \medskip\\ +\kid &\defas& \dlam x\, \dhk.\Let\; (\vhret \dcons \dhk') = \dhk \;\In\; \vhret \dapp x \dapp \dhk' +\end{equations} % -\[ - \dec{FileLU} \defas \{\ULink : \Record{\String;\String} \opto \UnitType; \UUnlink : \String \opto \UnitType\} -\] +The only change to the translation of operation invocation is the +extra bureaucracy induced by the additional pure continuation. +% +The translation of handler installation is a little more interesting +as it must make up an initial pure continuation in order to maintain +the sequence of triples interpretation of the continuation +structure. As the initial pure continuation we use the administrative +function $\kid$, which amounts to a dynamic variation of the +translation rule for the trivial computation term $\Return$: it +invokes the next pure continuation with whatever value it was +provided. % -The $\ULink$ operation is parameterised by two strings. The first -string is the name of the \emph{source} file and the second string is -the \emph{destination} name (i.e. the new name). The $\UUnlink$ -operation takes a single string argument, which is the name of the -file to be removed. -As before, we bundle the low level operations on the file system state -into their own functions. We start with file linking. +Although, I will not demonstrate it here, the translation rules for +$\lambda$-abstractions, $\Lambda$-abstractions, and $\Let$-expressions +must also be adjusted accordingly to account for the extra +bureaucracy. The same is true for the translation of $\Return$-clause, +thus it is rather straightforward to adapt it to the new continuation +interpretation. % -\[ - \bl - \flink : \Record{\String;\String;\FileSystem} \to \FileSystem \eff \{\Fail : \UnitType \opto \ZeroType\}\\ - \flink\,\Record{src;dest;fs} \defas - \bl - \If\;\dec{has}\,\Record{dest;fs.dir}\;\Then\;\Absurd~\Do\;\Fail\,\Unit\\ - \Else\; - \bl - \Let\;ino \revto \lookup~\Record{src;fs.dir}\;\In\\ - \Let\;dir' \revto \Record{dest;ino} \cons fs.dir\;\In\\ - \Let\;inode \revto \lookup~\Record{ino;fs.ilist}\;\In\\ - \Let\;inode' \revto \Record{inode\;\With\;lno = inode.lno + 1}\;\In\\ - \Let\;ilist' \revto \modify\,\Record{ino;inode';fs.ilist}\;\In\\ - \Record{fs\;\With\;dir = dir';ilist = ilist'} - \el - \el - \el -\] +\begin{equations} + \cps{-} &:& \HandlerCat \to \UValCat\\ + \cps{\{\Return \; x \mapsto N\}} &\defas& \dlam x\, \dhk. + \ba[t]{@{}l} + \Let\; (\_ \dcons \dk \dcons \vhret \dcons \vhops \dcons \dhk') = \dhk \;\In\\ + \cps{N} \sapp (\reflect \dk \scons \reflect \vhret \scons \reflect \vhops \scons \reflect \dhk') + \ea +\end{equations} % -The function $\flink$ checks whether the destination filename, $dest$, -already exists in the directory. If it exists then the function raises -the $\Fail$ exception. Otherwise it looks up the index of the i-node, -$ino$, associated with the source file, $src$. Next, the directory is -extended with the destination filename, which gets associated with -this index, meaning $src$ and $dest$ both share the same -i-node. Finally, the link count of the i-node at index $ino$ gets -incremented, and the function returns the updated file system state. +As before, the translation ensures that the associated effect +continuation is discarded (the first element of the dynamic +continuation $ks$). In addition the next continuation triple is +extracted and reified as a static continuation triple. % - -The semantics of file unlinking is slightly more complicated as an -i-node may become unlinked, meaning that it needs to garbage collected -along with its file contents in the data region. To implement file -removal we make use of another standard operation on association -lists. +The interesting rule is the translation of operation clauses. % -\[ - \remove : \Record{\alpha;\Record{\alpha;\beta}} \to \Record{\alpha;\beta} -\] +\begin{equations} +\cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}}^\dagger +&\defas& +\bl +\dlam \Record{z,\Record{p,\dhkr}}\,\dhk.\\ + \qquad\Case \;z\; \{ + \ba[t]{@{}l@{}c@{~}l} + (&\ell &\mapsto + \ba[t]{@{}l} + \Let\;(\dk \dcons \vhret \dcons \vhops \dcons \dhk') = \dhk\;\In\\ + \Let\;(\_ \dcons \_ \dcons \dhkr') = \dhkr \;\In\\ + \Let\;r = \Res\,(\hid \dcons \rid \dcons \dhkr') \;\In \\ + \cps{N_{\ell}} \sapp (\reflect\dk \scons \reflect\vhret \scons \reflect\vhops \scons \reflect \dhk'))_{\ell \in \mathcal{L}} \\ + \ea \\ + &y &\mapsto \hforward((y,p,\dhkr),\dhk) \} \\ + \ea +\el \medskip\\ +\hforward((y, p, \dhkr), \dhk) &\defas& \bl + \Let\; (\dk \dcons \vhret \dcons \vhops \dcons \dhk') = \dhk \;\In \\ + \vhops \dapp \Record{y, \Record{p, \vhops \dcons \vhret \dcons \dk \dcons \dhkr}} \dapp \dhk' \\ + \el \smallskip\\ +\hid &\defas& \dlam\,\Record{z,\Record{p,\dhkr}}\,\dhk.\hforward((z,p,\dhkr),\dhk) \smallskip\\ +\rid &\defas& \dlam x\, \dhk.\Let\; (\vhops \dcons \dk \dcons \dhk') = \dhk \;\In\; \dk \dapp x \dapp \dhk' +% \pcps{-} &:& \CompCat \to \UCompCat\\ +% \pcps{M} &\defas& \cps{M} \sapp (\reflect \kid \scons \reflect (\dlam x\,\dhk.x) \scons \reflect (\dlam z\,ks.\Absurd~z) \scons \snil) \\ +\end{equations} % -The first parameter to $\remove$ is the key associated with the entry -to be removed from the association list, which is given as the second -parameter. If the association list does not have an entry for the -given key, then the function behaves as the identity. The behaviour of -the function in case of multiple entries for a single key does not -matter as our system is carefully set up to ensure that each key has -an unique entry. +The main difference between this translation rule and the translation +rule for deep handler operation clauses is the realisation of +resumptions. % -\[ - \bl - \funlink : \Record{\String;\FileSystem} \to \FileSystem \eff \{\Fail : \UnitType \opto \ZeroType\}\\ - \funlink\,\Record{fname;fs} \defas - \bl - \If\;\dec{has}\,\Record{fname;fs.dir}\;\Then\\ - \quad - \bl - \Let\;ino \revto \lookup\,\Record{fname;fs.dir}\;\In\\ - \Let\;dir' \revto \remove\,\Record{fname;fs.dir}\;\In\\ - \Let\;inode \revto \lookup\,\Record{ino;fs.ilist}\;\In\\ - \Let\;\Record{ilist';dreg'} \revto - \bl - \If\;inode.lno > 1\;\Then\\ - \quad\bl - \Let\;inode' \revto \Record{\bl inode\;\With\\lno = inode.lno - 1}\el\\ - \In\;\Record{\modify\,\Record{ino;inode';fs.ilist};fs.dreg} - \el\\ - \Else\; - \Record{\bl\remove\,\Record{ino;fs.ilist};\\ - \remove\,\Record{inode.loc;fs.dreg}} - \el - \el\\ - \In\;\Record{fs\;\With\;dir = dir'; ilist = ilist'; dreg = dreg'} - \el\\ - \Else\; \Absurd~\Do\;\Fail\,\Unit - \el - \el -\] +Recall that a resumption is represented as a reversed slice of a +continuation. Thus the deconstruction of the resumption $\dhkr$ +effectively ensures that the current handler gets properly +uninstalled. However, it presents a new problem as the remainder +$\dhkr'$ is not a well-formed continuation slice, because the top +element is a pure continuation without a handler. % -The $\funlink$ function checks whether the given filename $fname$ -exists in the directory. If it does not, then it raises the $\Fail$ -exceptions. However, if it does exist then the function proceeds to -lookup the index of the i-node for the file, which gets bound to -$ino$, and subsequently remove the filename from the -directory. Afterwards it looks up the i-node with index $ino$. Now one -of two things happen depending on the current link count of the -i-node. If the count is greater than one, then we need only decrement -the link count by one, thus we modify the i-node structure. If the -link count is 1, then i-node is about to become stale, thus we must -garbage collect it by removing both the i-node from the i-list and the -contents from the data region. Either branch returns the new state of -i-list and data region. Finally, the function returns the new file -system state. -With the $\flink$ and $\funlink$ functions, we can implement the -semantics for $\ULink$ and $\UUnlink$ operations following the same -patterns as for the other file system operations. -% -\[ - \bl - \fileLU : (\UnitType \to \alpha \eff \FileLU) \to \alpha \eff \State~\FileSystem\\ - \fileLU~m \defas - \bl - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;res &\mapsto& res\\ - \OpCase{\ULink}{\Record{src;dest}}{resume} &\mapsto& - \bl - \faild\,\Record{\Unit; \lambda\Unit.\\ - \quad\bl - \Let\;fs = \flink\,\Record{src;dest;\Uget\,\Unit}\\ - \In\;\Uput~fs}; resume\,\Unit - \el - \el\\ - \OpCase{\UUnlink}{fname}{resume} &\mapsto& - \bl - \faild\,\Record{\Unit; \lambda\Unit.\\ - \quad\bl - \Let\;fs = \funlink\,\Record{fname;\Uget\,\Unit}\\ - \In\;\Uput~fs}; resume\,\Unit - \el - \el - \ea - \el - \el -\] +To rectify this problem, we can insert a dummy identity handler +composed from $\hid$ and $\rid$. The effect continuation $\hid$ +forwards every operation, and the pure continuation $\rid$ is an +identity clause. Thus every operation and the return value will +effectively be handled by the next handler. % -The composition of $\fileRW$, $\fileAlloc$, and $\fileLU$ complete the -implementation of \fsname{}. +Unfortunately, insertion of such identity handlers lead to memory +leaks~\cite{Kiselyov12,HillerstromL18}. % -\[ - \bl - \FileIO \defas \{\FileRW;\FileCO;\FileLU\} \medskip\\ - \fileIO : (\UnitType \to \alpha \eff \FileIO) \to \alpha \eff \State~\FileSystem \\ - \fileIO~m \defas \fileRW\,(\lambda\Unit. \fileAlloc\,(\lambda\Unit.\fileLU~m)) - \el -\] +% \dhil{Give the counting example} % -The three handlers may as well be implemented as a single monolithic -handler, since they implement different operations, return the same -value, and make use of the same state cell. In practice a monolithic -handler may have better performance. However, a sufficiently clever -compiler would be able to take advantage of the fusion laws of deep -handlers to fuse the three handlers into one (e.g. using the technique -of \citet{WuS15}), and thus allow modular composition without -composition. -We now have the building blocks to implement a file copying -utility. We will implement the utility such that it takes an argument -to decide whether it should make a soft copy such that the source file -and destination file are linked, or it should make a hard copy such -that a new i-node is allocated and the bytes in the data regions gets -duplicated. +The use of identity handlers is symptomatic for the need of a more +general notion of resumptions. During resumption invocation the +dangling pure continuation should be composed with the current pure +continuation which suggests the need for a shallow variation of the +resumption construction primitive $\Res$. % \[ \bl - \dec{cp} : \Record{\Bool;\String;\String} \to \UnitType \eff \{\FileIO;\Exit : \Int \opto \ZeroType\}\\ - \dec{cp}~\Record{link;src;dest} \defas - \bl - \If\;link\;\Then\;\Do\;\ULink\,\Record{src;dest}\;\\ - \Else\; \bl - \Case\;\Do\;\UOpen~src\\ - \{ \ba[t]{@{~}l@{~}c@{~}l} - \None &\mapsto& \exit~1\\ - \Some~ino &\mapsto& \\ - \multicolumn{3}{l}{\quad\Case\;\Do\;\URead~ino\;\{ - \ba[t]{@{~}l@{~}c@{~}l} - \None &\mapsto& \exit~1\\ - \Some~cs &\mapsto& \echo~cs~\redirect~dest \} \} - \ea} - \ea - \el - \el + \Let\; r = \Res^\dagger (\_ \dcons \_ \dcons \dk \dcons h_n^{\mathrm{ops}} \dcons h_n^{\mathrm{ret}} \dcons \dk_n \dcons \cdots \dcons h_1^{\mathrm{ops}} \dcons h_1^{\mathrm{ret}} \dcons \dk_1 \dcons \dnil)\;\In\;N \reducesto\\ + \quad N[(\dlam x\,\dhk. + \ba[t]{@{}l} + \Let\; (\dk' \dcons \dhk') = \dhk\;\In\\ + \dk_1 \dapp x \dapp (h_1^{\mathrm{ret}} \dcons h_1^{\mathrm{ops}} \cdots \dcons \dk_n \dcons h_n^{\mathrm{ret}} \dcons h_n^{\mathrm{ops}} \dcons (\dk' \circ \dk) \dcons \dhk'))/r] + \ea \el \] % -If the $link$ parameter is $\True$, then the utility makes a soft copy -by performing the operation $\ULink$ to link the source file and -destination file. Otherwise the utility makes a hard copy by first -opening the source file. If $\UOpen$ returns the $\None$ (i.e. the -open failed) then the utility exits with code $1$. If the open -succeeds then the entire file contents are read. If the read operation -fails then we again just exit, however, in the event that it succeeds -we apply the $\echo$ to the file contents and redirects the output to -the file $dest$. - -The logic for file removal is part of the semantics for -$\UUnlink$. Therefore the implementation of a file removal utility is -simply an application of the operation $\UUnlink$. +where $\circ$ is defined to be function composition in continuation +passing style. % \[ - \bl - \dec{rm} : \String \to \UnitType \eff \{\UUnlink : \String \opto \UnitType\}\\ - \dec{rm}~fname \defas \Do\;\UUnlink~fname - \el + g \circ f \defas \lambda x\,\dhk. + \ba[t]{@{}l} + \Let\;(\dk \dcons \dhk') = \dhk\; \In\\ + f \dapp x \dapp ((\lambda x\,\dhk. g \dapp x \dapp (\dk \dcons \dhk)) \dcons \dhk') + \ea \] % -We can now plug it all together. +The idea is that $\Res^\dagger$ uninstalls the appropriate handler and +composes the dangling pure continuation $\dk$ with the next +\emph{dynamically determined} pure continuation $\dk'$, and reverses +the remainder of the resumption and composes it with the modified +dynamic continuation ($(\dk' \circ \dk) \dcons ks'$). % -\[ - \ba{@{~}l@{~}l} - &\bl - \runState\,\Record{\dec{fs}_0;\fileIO\,(\lambda\Unit.\\ - \quad\timeshare\,(\lambda\Unit.\\ - \qquad\dec{interruptWrite}\,(\lambda\Unit.\\ - \qquad\quad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ - \qquad\qquad\status\,(\lambda\Unit. - \ba[t]{@{}l} - \If\;\fork\,\Unit\;\\ - \Then\; - \bl - \su~\Alice;\, - \quoteRitchie~\redirect~\strlit{ritchie.txt};\\ - \dec{cp}\,\Record{\False;\strlit{ritchie.txt};\strlit{ritchie}};\\ - \dec{rm}\,\strlit{ritchie.txt} - \el\\ - \Else\; - \bl - \su~\Bob;\, - \quoteHamlet~\redirect~\strlit{hamlet};\\ - \dec{cp}\,\Record{\True;\strlit{hamlet};\strlit{act3}} - )}))))} - \el - \ea - \el \smallskip\\ - \reducesto^+& - \bl - \Record{ - \ba[t]{@{}l} - [0, 0];\\ - \Record{ - \ba[t]{@{}l} - dir=[\Record{\strlit{ritchie};3},\Record{\strlit{act3};2},\Record{\strlit{hamlet};2}, - \Record{\strlit{stdout};0}];\\ - ilist=[\Record{3;\Record{lno=1;loc=3}}, - \Record{2;\Record{lno=2;loc=2}}, - \Record{0;\Record{lno=1;loc=0}}];\\ - dreg=[ - \ba[t]{@{}l} - \Record{3; - \ba[t]{@{}l@{}l} - \texttt{"}&\texttt{UNIX is basically a simple operating system, }\\ - &\texttt{but you have to be a genius }\\ - &\texttt{to understand the simplicity.\nl{"}}}, - \ea\\ - \Record{2; - \ba[t]{@{}l@{}l} - \texttt{"}&\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\ - &\texttt{Whether 'tis nobler in the mind to suffer\nl{}"}}, - \ea\\ - \Record{0; \strlit{}}]; lnext=4; inext=4}}\\ - \ea - \ea - \ea\\ - : \Record{\List~\Int; \FileSystem} - \el - \ea -\] -% -Alice copies the file \texttt{ritchie.txt} as \texttt{ritchie}, and -subsequently removes the original file, which effectively amounts to a -roundabout way of renaming a file. It is evident from the file system -state that the file is a hard copy as the contents of -\texttt{ritchie.txt} now reside in location $3$ rather than location -$1$ in the data region. Bob makes a soft copy of the file -\texttt{hamlet} as \texttt{act3}, which is evident by looking at the -directory where the two filenames point to the same i-node (with index -$2$), whose link counter has value $2$. -\paragraph{Summary} Throughout this section we have used effect -handlers to give a semantics to a \UNIX{}-style operating system by -treating system calls as effectful operations, whose semantics are -given by handlers, acting as composable micro-kernels. Starting from a -simple bare minimum file I/O model we seen how the modularity of -effect handlers enable us to develop a feature-rich operating system -in an incremental way by composing several handlers to implement a -basic file system, multi-user environments, and multi-tasking -support. Each incremental change to the system has been backwards -compatible with previous changes in the sense that we have not -modified any previously defined interfaces in order to support a new -feature. It serves as a testament to demonstrate the versatility of -effect handlers, and it suggests that handlers can be a viable option -to use with legacy code bases to retrofit functionality. The operating -system makes use of fourteen operations, which are being handled by -twelve handlers, some of which are used multiple times, e.g. the -$\environment$ and $\redirect$ handlers. +While the underlying idea is correct, this particular realisation of +the idea is problematic as the use of function composition +reintroduces a variation of the dynamic administrative redexes that we +dealt with in Section~\ref{sec:first-order-explicit-resump}. +% +In order to avoid generating these administrative redexes we need a +more intensional continuation representation. +% +Another telltale sign that we require a more intensional continuation +representation is the necessary use of the administrative function +$\kid$ in the translation of $\Handle$ as a placeholder for the empty +pure continuation. +% +In terms of aesthetics, the non-uniform continuation deconstructions +also suggest that we could benefit from a more structured +interpretation of continuations. +% +Although it is seductive to program with lists, it quickly gets +unwieldy. +\subsection{Generalised continuations} +\label{sec:generalised-continuations} -% \begin{figure}[t] -% \centering -% \begin{tikzpicture}[node distance=4cm,auto,>=stealth'] -% \node[] (server) {\bfseries Bob (server)}; -% \node[left = of server] (client) {\bfseries Alice (client)}; -% \node[below of=server, node distance=5cm] (server_ground) {}; -% \node[below of=client, node distance=5cm] (client_ground) {}; -% % -% \draw (client) -- (client_ground); -% \draw (server) -- (server_ground); -% \draw[->,thick] ($(client)!0.25!(client_ground)$) -- node[rotate=-6,above,scale=0.7,midway]{SYN 42} ($(server)!0.40!(server_ground)$); -% \draw[<-,thick] ($(client)!0.56!(client_ground)$) -- node[rotate=6,above,scale=0.7,midway]{SYN 84;ACK 43} ($(server)!0.41!(server_ground)$); -% \draw[->,thick] ($(client)!0.57!(client_ground)$) -- node[rotate=-6,above,scale=0.7,midway]{ACK 85} ($(server)!0.72!(server_ground)$); -% \end{tikzpicture} -% \caption{Sequence diagram for the TCP handshake example.}\label{fig:tcp-handshake} -% \end{figure} +One problem is that the continuation representation used by the +higher-order uncurried translation for deep handlers is too +extensional to support shallow handlers efficiently. Specifically, the +representation of pure continuations needs to be more intensional to +enable composition of pure continuations without having to materialise +administrative continuation functions. +% -% \paragraph{TCP threeway handshake} +Another problem is that the continuation representation integrates the +return clause into the pure continuations, but the semantics of +shallow handlers demands that this return clause is discarded when any +of the operations is invoked. -% The existing literature already contain an extensive amount of -% introductory examples of programming with (deep) effect -% handlers~\cite{KammarLO13,Pretnar15,Leijen17}. +The solution to the first problem is to reuse the key idea of +Section~\ref{sec:first-order-explicit-resump} to avoid administrative +continuation functions by representing a pure continuation as an +explicit list consisting of pure continuation functions. As a result +the composition of pure continuation functions can be realised as a +simple cons-operation. +% +The solution to the second problem is to pair the continuation +functions corresponding to the $\Return$-clause and operation clauses +in order to distinguish the pure continuation function induced by a +$\Return$-clause from those induced by $\Let$-expressions. +% -% \subsubsection{Exception handling} -% \label{sec:exn-handling-in-action} +Plugging these two solutions yields the notion of \emph{generalised + continuations}. A generalised continuation is a list of +\emph{continuation frames}. A continuation frame is a triple +$\Record{fs, \Record{\vhret, \vhops}}$, where $fs$ is list of stack +frames representing the pure continuation for the computation +occurring between the current execution and the handler, $\vhret$ is +the (translation of the) return clause of the enclosing handler, and +$\vhops$ is the (translation of the) operation clauses. +% -% Effect handlers subsume a variety of control abstractions, and -% arguably the simplest such abstraction is exception handling. +The change of representation of pure continuations does mean that we +can no longer invoke them by simple function application. Instead, we +must inspect the structure of the pure continuation $fs$ and act +appropriately. To ease notation it is convenient introduce a new +computation form for pure continuation application $\kapp\;V\;W$ that +feeds a value $W$ into the continuation represented by $V$. There are +two reduction rules. +% +\begin{reductions} + \usemlab{KAppNil} + & \kapp\;(\dRecord{\dnil, \dRecord{\vhret, \vhops}} \dcons \dhk)\,W + & \reducesto + & \vhret\,W\,\dhk + \\ + \usemlab{KAppCons} + & \kapp\;(\dRecord{f \cons fs, h} \dcons \dhk)\,W + & \reducesto + & f\,W\,(\dRecord{fs, h} \dcons \dhk) +\end{reductions} +% +%\dhil{Say something about skip frames?} +% +The first rule describes what happens when the pure continuation is +exhausted and the return clause of the enclosing handler is +invoked. The second rule describes the case when the pure continuation +has at least one element: this pure continuation function is invoked +and the remainder of the continuation is passed in as the new +continuation. -% \begin{example}[Option monad, directly] -% % -% To handle the possibility of failure in a pure functional -% programming language (e.g. Haskell~\cite{JonesABBBFHHHHJJLMPRRW99}), -% a computation is run under a particular monad $m$ with some monadic -% operation $\fail : \Unit \to m~\alpha$ for signalling failure. -% % -% Concretely, one can use the \emph{option monad} which interprets the -% success of as $\Some$ value and failure as $\None$. -% % -% For good measure, let us first define the option type constructor. -% % -% \[ -% \Option~\alpha \defas [\Some:\alpha;\None] -% \] -% % -% The constructor is parameterised by a type variable $\alpha$. The -% data constructor $\Some$ carries a payload of type $\alpha$, whilst -% the other data constructor $\None$ carries no payload. -% % -% The monadic interface and its implementation for $\Option$ is as -% follows. -% % -% \[ -% \bl -% \return : \alpha \to \Option~\alpha\\ -% \return~x \defas \Some~x \smallskip\\ -% - \bind - : \Option~\alpha \to (\alpha \to \Option~\beta) \to \Option~\beta\\ -% m \bind k \defas \Case\;m\;\{ -% \ba[t]{@{}l@{~}c@{~}l} -% \None &\mapsto& \None\\ -% \Some~x &\mapsto& k~x \} -% \ea \smallskip\\ -% \fail : \Unit \to \Option~\alpha\\ -% \fail~\Unit \defas \None -% \el -% \] -% % -% The $\return$ operation lifts a given value into monad by tagging it -% with $\Some$. The $\bind$ operation (pronounced bind) is the monadic -% sequencing operation. It takes $m$, an instance of the monad, along -% with a continuation $k$ and pattern matches on $m$ to determine -% whether to apply the continuation. If $m$ is $\None$ then (a new -% instance of) $\None$ is returned -- this essentially models -% short-circuiting of failed computations. Otherwise if $m$ is $\Some$ -% we apply the continuation $k$ to the payload $x$. The $\fail$ -% operation is simply the constant $\None$. -% % +We must also change how resumptions (i.e. reversed continuations) are +converted into functions that can be applied. Resumptions for deep +handlers ($\Res\,V$) are similar to +Section~\ref{sec:first-order-explicit-resump}, except that we now use +$\kapp$ to invoke the continuation. Resumptions for shallow handlers +($\Res^\dagger\,V$) are more complex. Instead of taking all the frames +and reverse appending them to the current stack, we remove the current +handler $h$ and move the pure continuation +($f_1 \dcons \dots \dcons f_m \dcons \dnil$) into the next frame. This +captures the intended behaviour of shallow handlers: they are removed +from the stack once they have been invoked. The following two +reduction rules describe their behaviour. +% +\[ + \ba{@{}l@{\quad}l} + \usemlab{Res} + & \Let\;r=\Res\,(V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N + \reducesto N[\dlam x\, \dhk.\kapp\;(V_1 \dcons \dots V_n \dcons \dhk)\,x/r] \\ + \usemlab{Res^\dagger} + & \Let\;r=\Res^\dagger\,(\dRecord{f_1 \dcons \dots \dcons f_m \dcons \nil, h} \dcons V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N \reducesto \\ + & \qquad N[\dlam x\,\dhk.\bl + \Let\,\dRecord{fs',h'} \dcons \dhk' = \dhk\;\In\;\\ + \kapp\,(V_1 \dcons \dots \dcons V_n \dcons \dRecord{f_1 \dcons \dots \dcons f_m \dcons fs', h'} \dcons \dhk')\,x/r] + \el + \ea +\] +% +These constructs along with their reduction rules are +macro-expressible in terms of the existing constructs. +% +I choose here to treat them as primitives in order to keep the +presentation relatively concise. -% Let us put the monad to use. An illustrative example is safe -% division. Mathematical division is not defined when the denominator -% is zero. It is standard for implementations of division in safe -% programming languages to raise an exception, which we can code -% monadically as follows. -% % -% \[ -% \bl -% -/_{\dec{m}}- : \Record{\Float, \Float} \to \Option~\Float\\ -% n/_{\dec{m}}\,d \defas -% \If\;d = 0 \;\Then\;\fail~\Unit\; -% \Else\;\return~(n \div d) -% \el -% \] -% % -% If the provided denominator $d$ is zero, then the implementation -% signals failure by invoking the $\fail$ operation. Otherwise, the -% implementation performs the mathematical division and lifts the -% result into the monad via $\return$. -% % -% A monadic reading of the type signature tells us not only that the -% computation may fail, but also that failure is interpreted using the -% $\Option$ monad. +Essentially, a generalised continuation amounts to a sort of +\emph{defunctionalised} continuation, where $\kapp$ acts as an +interpreter for the continuation +structure~\cite{Reynolds98a,DanvyN01}. -% Let us use this safe implementation of division to implement a safe -% function for computing the reciprocal plus one for a given number -% $x$, i.e. $\frac{1}{x} + 1$. -% % -% \[ -% \bl -% \dec{rpo_m} : \Float \to \Option~\Float\\ -% \dec{rpo_m}~x \defas (1.0 /_{\dec{m}}\,x) \bind (\lambda y. \return~(y + 1.0)) -% \el -% \] -% % -% The implementation first computes the (monadic) result of dividing 1 -% by $x$, and subsequently sequences this result with a continuation -% that adds one to the result. -% % -% Consider some example evaluations. -% % -% \[ -% \ba{@{~}l@{~}c@{~}l} -% \dec{rpo_m}~1.0 &\reducesto^+& \Some~2.0\\ -% \dec{rpo_m}~(-1.0) &\reducesto^+& \Some~0.0\\ -% \dec{rpo_m}~0.0 &\reducesto^+& \None -% \el -% \] -% % -% The first (respectively second) evaluation follows because -% $1.0/_{\dec{m}}\,1.0$ returns $\Some~1.0$ (respectively -% $\Some~(-1.0)$), meaning that $\bind$ applies the continuation to -% produce the result $\Some~2.0$ (respectively $\Some~0.0$). -% % -% The last evaluation follows because $1.0/_{\dec{m}}\,0.0$ returns -% $\None$, consequently $\bind$ does not apply the continuation, thus -% producing the result $\None$. - -% This idea of using a monad to model failure stems from denotational -% semantics and is due to Moggi~\cite{Moggi89}. - -% Let us now change perspective and reimplement the above example -% using effect handlers. We can translate the monadic interface into -% an effect signature consisting of a single operation $\Fail$, whose -% codomain is the empty type $\Zero$. We also define an auxiliary -% function $\fail$ which packages an invocation of $\Fail$. -% % -% \[ -% \bl -% \Exn \defas \{\Fail: \Unit \opto \Zero\} \medskip\\ -% \fail : \Unit \to \alpha \eff \Exn\\ -% \fail \defas \lambda\Unit.\Absurd\;(\Do\;\Fail) -% \el -% \] -% % -% The $\Absurd$ computation term is used to coerce the return type -% $\Zero$ of $\Fail$ into $\alpha$. This coercion is safe, because an -% invocation of $\Fail$ can never return as there are no inhabitants -% of type $\Zero$. +\subsection{Dynamic terms: the target calculus revisited} +\label{sec:target-calculus-revisited} -% We can now reimplement the safe division operator. -% % -% \[ -% \bl -% -/- : \Record{\Float, \Float} \to \Float \eff \Exn\\ -% n/d \defas -% \If\;d = 0 \;\Then\;\fail~\Unit\; -% \Else\;n \div d -% \el -% \] -% % -% The primary difference is that the interpretation of failure is no -% longer fixed. The type signature conveys that the computation may -% fail, but it says nothing about how failure is interpreted. -% % -% It is straightforward to implement the reciprocal function. -% % -% \[ -% \bl -% \dec{rpo} : \Float \to \Float \eff \Exn\\ -% \dec{rpo}~x \defas (1.0 / x) + 1.0 -% \el -% \] -% % -% The monadic bind and return are now gone. We still need to provide -% an interpretation of $\Fail$. We do this by way of a handler that -% interprets success as $\Some$ and failure as $\None$. -% \[ -% \bl -% \optionalise : (\Unit \to \alpha \eff \Exn) \to \Option~\alpha\\ -% \optionalise \defas \lambda m. -% \ba[t]{@{~}l} -% \Handle\;m~\Unit\;\With\\ -% ~\ba{@{~}l@{~}c@{~}l} -% \Return\;x &\mapsto& \Some~x\\ -% \Fail~\Unit~resume &\mapsto& \None -% \ea -% \ea -% \el -% \] -% % -% The $\Return$-case injects the result of $m~\Unit$ into the option -% type. The $\Fail$-case discards the provided resumption and returns -% $\None$. Discarding the resumption effectively short-circuits the -% handled computation. In fact, there is no alternative to discard the -% resumption in this case as $resume : \Zero \to \Option~\alpha$ -% cannot be invoked. Let us use this handler to interpret the -% examples. -% % -% \[ -% \ba{@{~}l@{~}c@{~}l} -% \optionalise\,(\lambda\Unit.\dec{rpo}~1.0) &\reducesto^+& \Some~2.0\\ -% \optionalise\,(\lambda\Unit.\dec{rpo}~(-1.0)) &\reducesto^+& \Some~0.0\\ -% \optionalise\,(\lambda\Unit.\dec{rpo}~0.0) &\reducesto^+& \None -% \el -% \] -% % -% The first two evaluations follow because $\dec{rpo}$ returns -% successfully, and hence, the handler tags the respective results -% with $\Some$. The last evaluation follows because the safe division -% operator ($-/-$) inside $\dec{rpo}$ performs an invocation of $\Fail$, -% causing the handler to halt the computation and return $\None$. - -% It is worth noting that we are free to choose another the -% interpretation of $\Fail$. To conclude this example, let us exercise -% this freedom and interpret failure outside of $\Option$. For -% example, we can interpret failure as some default constant. -% % -% \[ -% \bl -% \faild : \Record{\alpha,\Unit \to \alpha \eff \Exn} \to \alpha\\ -% \faild \defas \lambda \Record{default,m}. -% \ba[t]{@{~}l} -% \Handle\;m~\Unit\;\With\\ -% ~\ba{@{~}l@{~}c@{~}l} -% \Return\;x &\mapsto& x\\ -% \Fail~\Unit~\_ &\mapsto& default -% \ea -% \ea -% \el -% \] -% % -% Now the $\Return$-case is just the identity and $\Fail$ is -% interpreted as the provided default value. -% % -% We can reinterpret the above examples using $\faild$ and, for -% instance, choose the constant $0.0$ as the default value. -% % -% \[ -% \ba{@{~}l@{~}c@{~}l} -% \faild\,\Record{0.0,\lambda\Unit.\dec{rpo}~1.0} &\reducesto^+& 2.0\\ -% \faild\,\Record{0.0,\lambda\Unit.\dec{rpo}~(-1.0)} &\reducesto^+& 0.0\\ -% \faild\,\Record{0.0,\lambda\Unit.\dec{rpo}~0.0} &\reducesto^+& 0.0 -% \el -% \] -% % -% Since failure is now interpreted as $0.0$ the second and third -% computations produce the same result, even though the second -% computation is successful and the third computation is failing. -% \end{example} - -% \begin{example}[Catch~\cite{SussmanS75}] -% \end{example} - -% \subsubsection{Blind and non-blind backtracking} - -% \begin{example}[Non-blind backtracking] -% \dhil{Nondeterminism} -% \end{example} - -% \subsubsection{Stateful computation} - -% \begin{example}[Dynamic binding] -% \dhil{Reader} -% \end{example} - -% \begin{example}[State handling] -% \dhil{State} -% \end{example} - -% \subsubsection{Generators and iterators} - -% \begin{example}[Inversion of control] -% \dhil{Inversion of control: generator from iterator} -% \end{example} - -% \begin{example}[Cooperative routines] -% \dhil{Coop} -% \end{example} - -% \subsection{Coding nontermination} - -\section{Shallow handlers} -\label{sec:unary-shallow-handlers} - -Shallow handlers are an alternative to deep handlers. Shallow handlers -are defined as case-splits over computation trees, whereas deep -handlers are defined as folds. Consequently, a shallow handler -application unfolds only a single layer of the computation tree. -% -Semantically, the difference between deep and shallow handlers is -analogous to the difference between \citet{Church41} and -\citet{Scott62} encoding techniques for data types in the sense that -the recursion is intrinsic to the former, whilst recursion is -extrinsic to the latter. -% -Thus a fixpoint operator is necessary to make programming with shallow -handlers practical. - -Shallow handlers offer more flexibility than deep handlers as they do -not hard wire a particular recursion scheme. Shallow handlers are -favourable when catamorphisms are not the natural solution to the -problem at hand. -% -A canonical example of when shallow handlers are desirable over deep -handlers is \UNIX{}-style pipes, where the natural implementation is -in terms of two mutually recursive functions (specifically -\emph{mutumorphisms}~\cite{Fokkinga90}), which is convoluted to -implement with deep -handlers~\cite{KammarLO13,HillerstromL18,HillerstromLA20}. - -In this section we take the full $\BCalc$ as our starting point and -extend it with shallow handlers, resulting in the calculus $\SCalc$. -The calculus borrows some syntax and semantics from \HCalc{}, whose -presentation will not be duplicated in this section. -% Often deep handlers are attractive because they are semantically -% well-behaved and provide appropriate structure for efficient -% implementations using optimisations such as fusion~\cite{WuS15}, and -% as we saw in the previous they codify a wide variety of applications. -% % -% However, they are not always convenient for implementing other -% structural recursion schemes such as mutual recursion. - -\subsection{Syntax and static semantics} -The syntax and semantics for effectful operation invocations are the -same as in $\HCalc$. Handler definitions and applications also have -the same syntax as in \HCalc{}, although we shall annotate the -application form for shallow handlers with a superscript $\dagger$ to -distinguish it from deep handler application. -% +\begin{figure}[t] +\textbf{Syntax} \begin{syntax} -\slab{Computations} &M,N \in \CompCat &::=& \cdots \mid \ShallowHandle \; M \; \With \; H\\[1ex] +\slab{Values} &V, W \in \UValCat &::= & x \mid \dlam x\,\dhk.M \mid \Rec\,g\,x\,\dhk.M \mid \ell \mid \dRecord{V, W} +\smallskip \\ +\slab{Computations} &M,N \in \UCompCat &::= & V \mid U \dapp V \dapp W \mid \Let\; \dRecord{x, y} = V \; \In \; N \\ +& &\mid& \Case\; V\, \{\ell \mapsto M; x \mapsto N\} \mid \Absurd\;V\\ +& &\mid& \kapp\,V\,W \mid \Let\;r=\Res^\depth\;V\;\In\;M \end{syntax} +\textbf{Syntactic sugar} +\begin{displaymath} +\bl +\begin{eqs} +\Let\; x = V \;\In\; N &\equiv& N[V/x] \\ +\ell\;V &\equiv& \dRecord{\ell, V} \\ +\end{eqs} +\qquad +\begin{eqs} +\Record{} &\equiv& \ell_{\Record{}} \\ +\Record{\ell=V; W} &\equiv& \ell\;\dRecord{V, W} \\ +\end{eqs} +\qquad +\begin{eqs} +\dnil &\equiv& \ell_{\dnil} \\ +V \dcons W &\equiv& \ell_{\dcons}\;\dRecord{V, W} \\ +\end{eqs} +\smallskip \\ +\ba{@{}c@{\quad}c@{}} +\Case\;V\;\{\ell\,x \mapsto M; y \mapsto N\} \equiv \\ + \qquad \bl + \Let\; y=V \;\In\; + \Let\; \dRecord{z, x} = y \;\In \\ + \Case\;z\;\{\ell \mapsto M; z \mapsto N\} \\ + \el \\ +\ea +\qquad +\ba{@{}l@{\quad}l@{}} +\Let\;\Record{\ell=x; y} = V \;\In\; N \equiv \\ + \qquad \bl + \Let\; \dRecord{z, z'} = V \;\In\; + \Let\; \dRecord{x, y} = z' \;\In \\ + \Case\; z \;\{\ell \mapsto N; z \mapsto \ell_{\bot}\} \\ + \el \\ +\ea \\ +\el +\end{displaymath} % -The static semantics for $\Handle^\dagger$ are the same as the static -semantics for $\Handle$. -% -\begin{mathpar} - \inferrule*[Lab=\tylab{Handle^\dagger}] - { - \typ{\Gamma}{M : C} \\ - \typ{\Gamma}{H : C \Harrow D} - } - {\Gamma \vdash \ShallowHandle \; M \; \With\; H : D} - - -%\mprset{flushleft} - \inferrule*[Lab=\tylab{Handler^\dagger}] - {{\bl - C = A \eff \{(\ell_i : A_i \opto B_i)_i; R\} \\ - D = B \eff \{(\ell_i : P_i)_i; R\}\\ - H = \{\Return\;x \mapsto M\} \uplus \{ \OpCase{\ell_i}{p_i}{r_i} \mapsto N_i \}_i - \el}\\\\ - \typ{\Delta;\Gamma, x : A}{M : D}\\\\ - [\typ{\Delta;\Gamma,p_i : A_i, r_i : B_i \to C}{N_i : D}]_i - } - {\typ{\Delta;\Gamma}{H : C \Harrow D}} -\end{mathpar} +\textbf{Standard reductions} % -The \tylab{Handler^\dagger} rule is remarkably similar to the -\tylab{Handler} rule. In fact, the only difference is the typing of -resumptions $r_i$. The codomain of $r_i$ is $C$ rather than $D$, -meaning that a resumption returns a value of the same type as the -input computation. In general the type $C$ may be different from the -output type $D$, thus it is evident from this typing rule that the -handler does not guard invocations of resumptions $r_i$. - - -\subsection{Dynamic semantics} - -There are two reduction rules. -%{\small{ \begin{reductions} -\semlab{Ret^\dagger} & - \ShallowHandle \; (\Return \; V) \; \With \; H &\reducesto& N[V/x], \hfill\text{where } \hret = \{ \Return \; x \mapsto N \} \\ -\semlab{Op^\dagger} & - \ShallowHandle \; \EC[\Do \; \ell \, V] \; \With \; H - &\reducesto& N[V/p, \lambda y . \, \EC[\Return \; y]/r], \\ -\multicolumn{4}{@{}r@{}}{ - \hfill\ba[t]{@{~}r@{~}l} - \text{where}& \hell = \{ \OpCase{\ell}{p}{r} \mapsto N \}\\ - \text{and} & \ell \notin \BL(\EC) - \ea -} -\end{reductions}%}}% +%% Standard reductions +\usemlab{App} & (\dlam x\,\dhk.M) \dapp V \dapp W &\reducesto& M[V/x, W/\dhk] \\ +\usemlab{Rec} & (\Rec\,g\,x\,\dhk.M) \dapp V \dapp W &\reducesto& M[\Rec\,g\,x\,\dhk.M/g, V/x, W/\dhk] \smallskip\\ +\usemlab{Split} & \Let \; \dRecord{x, y} = \dRecord{V, W} \; \In \; N &\reducesto& N[V/x, W/y] \\ +\usemlab{Case_1} & + \Case \; \ell \,\{ \ell \; \mapsto M; x \mapsto N\} &\reducesto& M \\ +\usemlab{Case_2} & + \Case \; \ell \,\{ \ell' \; \mapsto M; x \mapsto N\} &\reducesto& N[\ell/x], \hfill\quad \text{if } \ell \neq \ell' \smallskip\\ +\end{reductions} % -The rule \semlab{Ret^\dagger} is the same as the \semlab{Ret} rule for -deep handlers --- there is no difference in how the return value is -handled. The \semlab{Op^\dagger} rule is almost the same as the -\semlab{Op} rule the crucial difference being the construction of the -resumption $r$. The resumption consists entirely of the captured -context $\EC$. Thus an invocation of $r$ does not reinstall its -handler as in the setting of deep handlers, meaning is up to the -programmer to supply the handler the next invocation of $\ell$ inside -$\EC$. This handler may be different from $H$. - -The basic metatheoretic properties of $\SCalc$ are a carbon copy of -the basic properties of $\HCalc$. +\textbf{Continuation reductions} % -\begin{theorem}[Progress] - Suppose $\typ{}{M : C}$, then either there exists $\typ{}{N : C}$ - such that $M \reducesto^+ N$ and $N$ is normal, or $M$ diverges. -\end{theorem} +\begin{reductions} +\usemlab{KAppNil} & + \kapp \; (\dRecord{\dnil, \dRecord{v, e}} \dcons \dhk) \, V &\reducesto& v \dapp V \dapp \dhk \\ +\usemlab{KAppCons} & + \kapp \; (\dRecord{\dlf \dcons \dlk, h} \dcons \dhk) \, V &\reducesto& \dlf \dapp V \dapp (\dRecord{\dlk, h} \dcons \dhk) \\ +\end{reductions} % -\begin{proof} - By induction on the typing derivations. -\end{proof} +\textbf{Resumption reductions} % -\begin{theorem}[Subject reduction] - Suppose $\typ{\Gamma}{M : C}$ and $M \reducesto M'$, then - $\typ{\Gamma}{M' : C}$. -\end{theorem} +\[ +\ba{@{}l@{\quad}l@{}} +\usemlab{Res} & +\Let\;r=\Res(V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N \reducesto \\ +&\quad N[\dlam x\,\dhk. \bl\Let\;\dRecord{fs, \dRecord{\vhret, \vhops}}\dcons \dhk' = \dhk\;\In\\ + \kapp\;(V_1 \dcons \dots \dcons V_n \dcons \dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhk')\;x/r]\el +\\ +\usemlab{Res^\dagger} & +\Let\;r=\Res^\dagger(\dRecord{\dlf_1 \dcons \dots \dcons \dlf_m \dcons \dnil, h} \dcons V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N \reducesto\\ + & \quad N[\dlam x\,k. \bl + \Let\;\dRecord{\dlk', h'} \dcons \dhk' = \dhk \;\In \\ + \kapp\;(V_1 \dcons \dots \dcons V_n \dcons \dRecord{\dlf_1 \dcons \dots \dcons \dlf_m \dcons \dlk', h'} \dcons \dhk')\;x/r] \\ + \el +\ea +\] % -\begin{proof} - By induction on the typing derivations. -\end{proof} - -\subsection{\UNIX{}-style pipes} -\label{sec:pipes} +\caption{Untyped target calculus supporting generalised continuations.} +\label{fig:cps-target-gen-conts} +\end{figure} -A \UNIX{} pipe is an abstraction for streaming communication between -two processes. Technically, a pipe works by connecting the standard -out file descriptor of the first process to the standard in file -descriptor of the second process. The second process can then process -the output of the first process by reading its own standard in -file~\cite{RitchieT74}. +Let us revisit the target +calculus. Figure~\ref{fig:cps-target-gen-conts} depicts the untyped +target calculus with support for generalised continuations. +% +This is essentially the same as the target calculus used for the +higher-order uncurried CPS translation for deep effect handlers in +Section~\ref{sec:higher-order-uncurried-deep-handlers-cps}, except for +the addition of recursive functions. The calculus also includes the +$\kapp$ and $\Let\;r=\Res^\depth\;V\;\In\;N$ constructs described in +Section~\ref{sec:generalised-continuations}. There is a small +difference in the reduction rules for the resumption constructs: for +deep resumptions we do an additional pattern match on the current +continuation ($\dhk$). This is required to make the simulation proof +for the CPS translation with generalised continuations +(Section~\ref{sec:cps-gen-conts}) go through, because it makes the +functions that resumptions get converted to have the same shape as the +translation of source level functions -- this is required because the +operational semantics does not treat resumptions as distinct +first-class objects, but rather as a special kinds of functions. -We could implement pipes using the file system, however, it would -require us to implement a substantial amount of bookkeeping as we -would have to generate and garbage collect a standard out file and a -standard in file per process. Instead we can represent the files as -effectful operations and connect them via handlers. +\subsection{Translation with generalised continuations} +\label{sec:cps-gen-conts} % -With shallow handlers we can implement a demand-driven Unix pipeline -operator as two mutually recursive handlers. +\begin{figure} % -\[ -\bl - \Pipe : \Record{\UnitType \to \alpha \eff \{ \Yield : \beta \opto \UnitType \}; \UnitType \to \alpha\eff\{ \Await : \UnitType \opto \beta \}} \to \alpha \\ - \Pipe\, \Record{p; c} \defas - \bl - \ShallowHandle\; c\,\Unit \;\With\; \\ - ~\ba[m]{@{}l@{~}c@{~}l@{}} - \Return~x &\mapsto& x \\ - \OpCase{\Await}{\Unit}{resume} &\mapsto& \Copipe\,\Record{resume; p} \\ - \ea - \el\medskip\\ - - \Copipe : \Record{\beta \to \alpha\eff\{ \Await : \UnitType \opto \beta\}; \UnitType \to \alpha\eff\{ \Yield : \beta \opto \UnitType\}} \to \alpha \\ - \Copipe\, \Record{c; p} \defas - \bl - \ShallowHandle\; p\,\Unit \;\With\; \\ - ~\ba[m]{@{}l@{~}c@{~}l@{}} - \Return~x &\mapsto& x \\ - \OpCase{\Yield}{y}{resume} &\mapsto& \Pipe\,\Record{resume; \lambda \Unit. c\, y} \\ - \ea \\ - \el \\ -\el -\] +\textbf{Values} % -A $\Pipe$ takes two suspended computations, a producer $p$ and a -consumer $c$. +\begin{equations} + \cps{-} &:& \ValCat \to \UValCat\\ + \cps{x} &\defas& x\\ + \cps{\lambda x.M} &\defas& \dlam x\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{M} \sapp (\reflect\dk \scons \reflect \dhk') \\ + \cps{\Lambda \alpha.M} &\defas& \dlam \Unit\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{M} \sapp (\reflect\dk \scons \reflect \dhk') \\ + \cps{\Rec\,g\,x.M} &\defas& \Rec\,g\,x\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{M} \sapp (\reflect\dk \scons \reflect \dhk') \\ + \multicolumn{3}{c}{ + \cps{\Record{}} \defas \Record{} + \qquad + \cps{\Record{\ell = \!\!V; W}} \defas \Record{\ell = \!\cps{V}; \cps{W}} + \qquad + \cps{\ell\,V} \defas \ell\,\cps{V} + } +\end{equations} % -Each of the computations returns a value of type $\alpha$. +\textbf{Computations} % -The producer can perform the $\Yield$ operation, which yields a value -of type $\beta$ and the consumer can perform the $\Await$ operation, -which correspondingly awaits a value of type $\beta$. The $\Yield$ -operation corresponds to writing to standard out, whilst $\Await$ -corresponds to reading from standard in. +\begin{equations} +\cps{-} &:& \CompCat \to \SValCat^\ast \to \UCompCat\\ +\cps{V\,W} &\defas& \slam \shk.\cps{V} \dapp \cps{W} \dapp \reify \shk \\ +\cps{V\,T} &\defas& \slam \shk.\cps{V} \dapp \Record{} \dapp \reify \shk \\ +\cps{\Let\; \Record{\ell=x;y} = V \; \In \; N} &\defas& \slam \shk.\Let\; \Record{\ell=x;y} = \cps{V} \; \In \; \cps{N} \sapp \shk \\ +\cps{\Case~V~\{\ell~x \mapsto M; y \mapsto N\}} &\defas& + \slam \shk.\Case~\cps{V}~\{\ell~x \mapsto \cps{M} \sapp \shk; y \mapsto \cps{N} \sapp \shk\} \\ +\cps{\Absurd~V} &\defas& \slam \shk.\Absurd~\cps{V} \\ +\end{equations} +\begin{equations} +\cps{\Return\,V} &\defas& \slam \shk.\kapp\;(\reify \shk)\;\cps{V} \\ +\cps{\Let~x \revto M~\In~N} &\defas& + \bl\slam \sRecord{\shf, \sv} \scons \shk. + \ba[t]{@{}l} + \cps{M} \sapp (\sRecord{\bl\reflect((\dlam x\,\dhk.\bl\Let\;(\dk \dcons \dhk') = \dhk\;\In\\ + \cps{N} \sapp (\reflect\dk \scons \reflect \dhk')) \el\\ + \dcons \reify\shf), \sv} \scons \shk)\el + \ea + \el\\ +\cps{\Do\;\ell\;V} &\defas& + \slam \sRecord{\shf, \sRecord{\svhret, \svhops}} \scons \shk.\, + \reify\svhops \bl\dapp \dRecord{\ell,\dRecord{\cps{V}, \dRecord{\reify \shf, \dRecord{\reify\svhret, \reify\svhops}} \dcons \dnil}}\\ + \dapp \reify \shk\el \\ +\cps{\Handle^\depth \, M \; \With \; H} &\defas& +\slam \shk . \cps{M} \sapp (\sRecord{\snil, \sRecord{\reflect \cps{\hret}, \reflect \cps{\hops}^\depth}} \scons \shk) \\ +\end{equations} % -The shallow handler $\Pipe$ runs the consumer first. If the consumer -terminates with a value, then the $\Return$ clause is executed and -returns that value as is. If the consumer performs the $\Await$ -operation, then the $\Copipe$ handler is invoked with the resumption -of the consumer ($resume$) and the producer ($p$) as arguments. This -models the effect of blocking the consumer process until the producer -process provides some data. - -The $\Copipe$ function runs the producer to get a value to feed to the -waiting consumer. -% The arguments are swapped and the consumer component -% now expects a value. -If the producer performs the $\Yield$ operation, then $\Pipe$ is -invoked with the resumption of the producer along with a thunk that -applies the consumer's resumption to the yielded value. +\textbf{Handler definitions} % -For aesthetics, we define a right-associative infix alias for pipe: -$p \mid c \defas \lambda\Unit.\Pipe\,\Record{p;c}$. +\begin{equations} +\cps{-} &:& \HandlerCat \to \UValCat\\ +% \cps{H}^\depth &=& \sRecord{\reflect \cps{\hret}, \reflect \cps{\hops}^\depth}\\ +\cps{\{\Return \; x \mapsto N\}} &\defas& \dlam x\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{N} \sapp (\reflect\dk \scons \reflect \dhk') \\ +\cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}}^\depth +&\defas& +\dlam \dRecord{z,\dRecord{p,\dhkr}}\,\dhk. + \Case \;z\; \{ + \ba[t]{@{}l@{}c@{~}l} + (&\ell &\mapsto + \ba[t]{@{}l} + \Let\;r=\Res^\depth\,\dhkr\;\In\; \\ + \Let\;(\dk \dcons \dhk') = \dhk\;\In\\ + \cps{N_{\ell}} \sapp (\reflect\dk \scons \reflect \dhk'))_{\ell \in \mathcal{L}} + \ea\\ + &y &\mapsto \hforward((y, p, \dhkr), \dhk) \} \\ + \ea \\ +\hforward((y, p, \dhkr), \dhk) &\defas& \bl + \Let\; \dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk \;\In \\ + \vhops \dapp \dRecord{y,\dRecord{p, \dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhkr}} \dapp \dhk' \\ + \el +\end{equations} -Let us put the pipe operator to use by performing a simple string -frequency analysis on a file. We will implement the analysis as a -collection of small single-purpose utilities which we connect by way -of pipes. We will build a collection of small utilities. We will make -use of two standard list iteration functions. -% -\[ - \ba{@{~}l@{~}c@{~}l} - \map &:& \Record{\alpha \to \beta;\List~\alpha} \to \List~\beta\\ - \iter &:& \Record{\alpha \to \beta; \List~\alpha} \to \UnitType - \ea -\] +\textbf{Top-level program} % -The function $\map$ applies its function argument to each element of -the provided list in left-to-right order and returns the resulting -list. The function $\iter$ is simply $\map$ where the resulting list -is ignored. Our first utility is a simplified version of the GNU -coreutil utility \emph{cat}, which copies the contents of files to -standard out~\cite[Section~3.1]{MacKenzieMPPBYS20}. Our version will -open a single file and stream its contents one character at a time. +\begin{equations} +\pcps{-} &:& \CompCat \to \UCompCat\\ +\pcps{M} &\defas& \cps{M} \sapp (\sRecord{\snil, \sRecord{\reflect \dlam x\,\dhk. x, \reflect \dlam \dRecord{z,\dRecord{p,\dhkr}}\,\dhk.\Absurd~z}} \scons \snil) \\ +\end{equations} % -\[ - \bl - \cat : \String \to \UnitType \eff \{\FileIO;\Yield : \Char \opto \UnitType;\Exit : \Int \opto \ZeroType\}\\ - \cat~fname \defas - \bl - \Case\;\Do\;\UOpen~fname~\{\\ - ~\ba[t]{@{~}l@{~}c@{~}l} - \None &\mapsto& \exit\,1\\ - \Some~ino &\mapsto& \bl \Case\;\Do\;\URead~ino~\{\\ - ~\ba[t]{@{~}l@{~}c@{~}l} - \None &\mapsto& \exit\,1\\ - \Some~cs &\mapsto& \iter\,\Record{\lambda c.\Do\;\Yield~c;cs}; \Do\;\Yield~\charlit{\textnil} \}\} - \ea - \el - \ea - \el - \el -\] +\caption{Higher-order uncurried CPS translation for effect handlers.} +\label{fig:cps-higher-order-uncurried-simul} +\end{figure} % -The last line is the interesting line of code. The contents of the -file gets bound to $cs$, which is supplied as an argument to the list -iteration function $\iter$. The function argument yields each -character. Each invocation of $\Yield$ effectively suspends the -iteration until the next character is awaited. + +The CPS translation is given in +Figure~\ref{fig:cps-higher-order-uncurried-simul}. In essence, it is +the same as the CPS translation for deep effect handlers as described +in Section~\ref{sec:higher-order-uncurried-deep-handlers-cps}, though +it is adjusted to account for generalised continuation +representation. For notational convenience, we write $\chi$ to denote +a statically known effect continuation frame +$\sRecord{\svhret,\svhops}$. % -This is an example of inversion of control as the iterator $\iter$ has -been turned into a generator. +The translation of $\Return$ invokes the continuation $\shk$ using the +continuation application primitive $\kapp$. % -We use the character $\textnil$ to identify the end of a stream. It is -essentially a character interpretation of the empty list (file) -$\nil$. +The translations of deep and shallow handlers differ only in their use +of the resumption construction primitive. -The $\cat$ utility processes the entire contents of a given -file. However, we may only be interested in some parts. The GNU -coreutil \emph{head} provides a way to process only a fixed amount of -lines and ignore subsequent -lines~\cite[Section~5.1]{MacKenzieMPPBYS20}. +A major aesthetic improvement due to the generalised continuation +representation is that continuation construction and deconstruction +are now uniform: only a single continuation frame is inspected at a +time. + +\subsubsection{Correctness} +\label{sec:cps-gen-cont-correctness} % -We will implement a simplified version of this utility which lets us -keep the first $n$ lines of a stream and discard the remainder. This -process will act as a \emph{filter}, which is an intermediary process -in a pipeline that both awaits and yields data. +The correctness of this CPS translation +(Theorem~\ref{thm:ho-simulation-gen-cont}) follows closely the +correctness result for the higher-order uncurried CPS translation for +deep handlers (Theorem~\ref{thm:ho-simulation}). Save for the +syntactic difference, the most notable difference is the extension of +the operation handling lemma (Lemma~\ref{lem:handle-op-gen-cont}) to +cover shallow handling in addition to deep handling. Each lemma is +stated in terms of static continuations, where the shape of the top +element is always known statically, i.e., it is of the form +$\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons +\sW$. Moreover, the static values $\sV_{fs}$, $\sV_{\mret}$, and +$\sV_{\mops}$ are all reflected dynamic terms (i.e., of the form +$\reflect V$). This fact is used implicitly in the proofs. For brevity +we write $\sV_f$ to denote a statically known continuation frame +$\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}}$. The full +proof details are given in Appendix~\ref{sec:proofs-cps-gen-cont}. + % -\[ - \bl - \head : \Int \to \UnitType \eff \{\Await : \UnitType \opto \Char;\Yield : \Char \opto \UnitType\}\\ - \head~n \defas - \bl - \If\;n = 0\;\Then\;\Do\;\Yield~\charlit{\textnil}\\ - \Else\; - \bl - \Let\;c \revto \Do\;\Await~\Unit\;\In\\ - \Do\;\Yield~c;\\ - \If\;c = \charlit{\textnil}\;\Then\;\Unit\\ - \Else\;\If\;c = \charlit{\nl}\;\Then\;\head~(n-1)\\ - \Else\;\head~n - \el - \el - \el -\] +\begin{lemma}[Substitution]\label{lem:subst-gen-cont} + % + The CPS translation commutes with substitution in value terms + % + \[ + \cps{W}[\cps{V}/x] = \cps{W[V/x]}, + \] + % + and with substitution in computation terms + \[ + \ba{@{}l@{~}l} + % &(\cps{M} \sapp (\sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW))[\cps{V}/x]\\ + % = &\cps{M[V/x]} \sapp (\sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons\sW)[\cps{V}/x], + &(\cps{M} \sapp (\sV_f \scons \sW))[\cps{V}/x]\\ + = &\cps{M[V/x]} \sapp (\sV_f \scons \sW)[\cps{V}/x], + \ea + \] + % + and with substitution in handler definitions + % + \begin{equations} + \cps{\hret}[\cps{V}/x] + &=& \cps{\hret[V/x]},\\ + \cps{\hops}[\cps{V}/x] + &=& \cps{\hops[V/x]}. + \end{equations} +\end{lemma} % -The function first checks whether more lines need to be processed. If -$n$ is zero, then it yields the nil character to signify the end of -stream. This has the effect of ignoring any future instances of -$\Yield$ in the input stream. Otherwise it awaits a character. Once a -character has been received the function yields the character in order -to include it in the output stream. After the yield, it checks whether -the character was nil in which case the process -terminates. Alternatively, if the character was a newline the function -applies itself recursively with $n$ decremented by one. Otherwise it -applies itself recursively with the original $n$. - -The $\head$ filter does not transform the shape of its data stream. It -both awaits and yields a character. However, the awaits and yields -need not operate on the same type within the same filter, meaning we -can implement a filter that transforms the shape of the data. Let us -implement a variation of the GNU coreutil \emph{paste} which merges -lines of files~\cite[Section~8.2]{MacKenzieMPPBYS20}. Our -implementation will join characters in its input stream into strings -separated by spaces and newlines such that the string frequency -analysis utility need not operate on the low level of characters. +In order to reason about the behaviour of the \semlab{Op} and +\semlab{Op^\dagger} rules, which are defined in terms of evaluation +contexts, we extend the CPS translation to evaluation contexts, using +the same translations as for the corresponding constructs in $\SCalc$. % -\[ - \bl - \paste : \UnitType \to \UnitType \eff \{\Await : \UnitType \opto \Char;\Yield : \String \opto \UnitType\}\\ - \paste\,\Unit \defas - \bl - pst\,\Record{\Do\;\Await~\Unit;\strlit{}}\\ - \where - \ba[t]{@{~}l@{~}c@{~}l} - pst\,\Record{\charlit{\textnil};str} &\defas& \Do\;\Yield~str;\Do\;\Yield~\strlit{\textnil}\\ - pst\,\Record{\charlit{\nl};str} &\defas& \Do\;\Yield~str;\Do\;\Yield~\strlit{\nl};pst\,\Record{\Do\;\Await~\Unit;\strlit{}}\\ - pst\,\Record{\charlit{~};str} &\defas& \Do\;\Yield~str;pst\,\Record{\Do\;\Await~\Unit;\strlit{}}\\ - pst\,\Record{c;str} &\defas& pst\,\Record{\Do\;\Await~\Unit;str \concat [c]} - - \ea - \el - \el -\] +\begin{equations} + \cps{[~]} + &=& \slam \shk. \shk \\ + \cps{\Let\; x \revto \EC \;\In\; N} + &=& + \begin{array}[t]{@{}l} + \slam \sRecord{\shf, \sv} \scons \shk.\\ + \quad \cps{\EC} \sapp (\bl\sRecord{\reflect((\dlam x\,\dhk.\bl\Let\;\dRecord{fs,\dRecord{\vhret,\vhops}}\dcons \dhk'=\dhk\;\In\\ + \cps{N} \sapp (\sRecord{\reflect fs, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect \dhk')) \dcons \reify\shf),\el\\ + \sv} \scons \shk)\el + \end{array} + \\ + \cps{\Handle^\depth\; \EC \;\With\; H} + &=& \slam \shk.\cps{\EC} \sapp (\sRecord{[], \cps{H}^\depth} \scons \shk) +\end{equations} % -The heavy-lifting is delegated to the recursive function $pst$ -which accepts two parameters: 1) the next character in the input -stream, and 2) a string buffer for building the output string. The -function is initially applied to the first character from the stream -(returned by the invocation of $\Await$) and the empty string -buffer. The function $pst$ is defined by pattern matching on the -character parameter. The first three definitions handle the special -cases when the received character is nil, newline, and space, -respectively. If the character is nil, then the function yields the -contents of the string buffer followed by a string with containing -only the nil character. If the character is a newline, then the -function yields the string buffer followed by a string containing the -newline character. Afterwards the function applies itself recursively -with the next character from the input stream and an empty string -buffer. The case when the character is a space is similar to the -previous case except that it does not yield a newline string. The -final definition simply concatenates the character onto the string -buffer and recurses. - -Another useful filter is the GNU stream editor abbreviated -\emph{sed}~\cite{PizziniBMG20}. It is an advanced text processing -editor, whose complete functionality we will not attempt to replicate -here. We will just implement the ability to replace a string by -another. This will be useful for normalising the input stream to the -frequency analysis utility, e.g. decapitalise words, remove unwanted -characters, etc. +The following lemma is the characteristic property of the CPS +translation on evaluation contexts. % -\[ - \bl - \sed : \Record{\String;\String} \to \UnitType \eff \{\Await : \UnitType \opto \String;\Yield : \String \opto \UnitType\}\\ - \sed\,\Record{target;str'} \defas - \bl - \Let\;str \revto \Do\;\Await~\Unit\;\In\\ - \If\;str = target\;\Then\;\Do\;\Yield~str';\sed\,\Record{target;str'}\\ - \Else\;\Do\;\Yield~str;\sed\,\Record{target;str'} - \el - \el -\] +This allows us to focus on the computation within an evaluation +context. % -The function $\sed$ takes two string arguments. The first argument is -the string to be replaced in the input stream, and the second argument -is the replacement. The function first awaits the next string from the -input stream, then it checks whether the received string is the same -as $target$ in which case it yields the replacement $str'$ and -recurses. Otherwise it yields the received string and recurses. - -Now let us implement the string frequency analysis utility. It work on -strings and count the occurrences of each string in the input stream. +\begin{lemma}[Evaluation context decomposition] + \label{lem:decomposition-gen-cont} + \[ + % \cps{\EC[M]} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW) + % = + % \cps{M} \sapp (\cps{\EC} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)) + \cps{\EC[M]} \sapp (\sV_f \scons \sW) + = + \cps{M} \sapp (\cps{\EC} \sapp (\sV_f \scons \sW)) + \] +\end{lemma} % -\[ - \bl - \freq : \UnitType \to \UnitType \eff \{\Await : \UnitType \opto \String;\Yield : \List\,\Record{\String;\Int} \opto \UnitType\}\\ - \freq\,\Unit \defas - \bl - freq'\,\Record{\Do\;\Await~\Unit;\nil}\\ - \where - \ba[t]{@{~}l@{~}c@{~}l} - freq'\,\Record{\strlit{\textnil};tbl} &\defas& \Do\;\Yield~tbl\\ - freq'\,\Record{str;tbl} &\defas& - \bl - \Let\;tbl' \revto \faild\,\Record{ - \bl - \Record{str;1} \cons tbl; \lambda\Unit.\\ - \Let\; sum \revto \lookup\,\Record{str;tbl}\;\In\\ - \modify\,\Record{str;sum+1;tbl}} - \el\\ - \In\;freq'\,\Record{\Do\;\Await~\Unit;tbl'} - \el - \ea - \el - \el -\] +By definition, reifying a reflected dynamic value is the identity +($\reify \reflect V = V$), but we also need to reason about the +inverse composition. As a result of the invariant that the translation +always has static access to the top of the handler stack, the +translated terms are insensitive to whether the remainder of the stack +is statically known or is a reflected version of a reified stack. This +is captured by the following lemma. The proof is by induction on the +structure of $M$ (after generalising the statement to stacks of +arbitrary depth), and relies on the observation that translated terms +either access the top of the handler stack, or reify the stack to use +dynamically, whereupon the distinction between reflected and reified +becomes void. Again, this lemma holds when the top of the static +continuation is known. % -The auxiliary recursive function $freq'$ implements the analysis. It -takes two arguments: 1) the next string from the input stream, and 2) -a table to keep track of how many times each string has occurred. The -table is implemented as an association list indexed by strings. The -function is initially applied to the first string from the input -stream and the empty list. The function is defined by pattern matching -on the string argument. The first definition handles the case when the -input stream has been exhausted in which case the function yields the -table. The other case is responsible for updating the entry associated -with the string $str$ in the table $tbl$. There are two subcases to -consider: 1) the string has not been seen before, thus a new entry -will have to created; or 2) the string already has an entry in the -table, thus the entry will have to be updated. We handle both cases -simultaneously by making use of the handler $\faild$, where the -default value accounts for the first subcase, and the computation -accounts for the second. The computation attempts to lookup the entry -associated with $str$ in $tbl$, if the lookup fails then $\faild$ -returns the default value, which is the original table augmented with -an entry for $str$. If an entry already exists it gets incremented by -one. The resulting table $tbl'$ is supplied to a recursive application -of $freq'$. - -We need one more building block to complete the pipeline. The utility -$\freq$ returns a value of type $\List~\Record{\String;\Int}$, we need -a utility to render the value as a string in order to write it to a -file. +\begin{lemma}[Reflect after reify] + \label{lem:reflect-after-reify-gen-cont} % -\[ - \bl - \printTable : \UnitType \to \UnitType \eff \{\Await : \UnitType \opto \List\,\Record{\String;\Int}\}\\ - \printTable\,\Unit \defas - \map\,\Record{\lambda\Record{s;i}.s \concat \strlit{:} \concat \intToString~i \concat \strlit{;};\Do\;\Await~\Unit} - \el -\] + \[ + % \cps{M} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \reflect \reify \sW) + % = + % \cps{M} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW). + \cps{M} \sapp (\sV_f \scons \reflect \reify \sW) + = + \cps{M} \sapp (\sV_f \scons \sW). + \] +\end{lemma} + +The next lemma states that the CPS translation correctly simulates +forwarding. The proof is by inspection of how the translation of +operation clauses treats non-handled operations. % -The function performs one invocation of $\Await$ to receive the table, -and then performs a $\map$ over the table. The function argument to -$\map$ builds a string from the provided string-integer pair. +\begin{lemma}[Forwarding]\label{lem:forwarding-gen-cont} + If $\ell \notin dom(H_1)$ then: + % + \[ + \bl + \cps{\hops_1}^\delta \dapp \dRecord{\ell, \dRecord{V_p, V_{\dhkr}}} \dapp (\dRecord{V_{fs}, \cps{H_2}^\delta} \dcons W) + \reducesto^+ \qquad \\ + \hfill + \cps{\hops_2}^\delta \dapp \dRecord{\ell, \dRecord{V_p, \dRecord{V_{fs}, \cps{H_2}^\delta} \dcons V_{\dhkr}}} \dapp W. \\ + \el + \] + % +\end{lemma} + +The following lemma is central to our simulation theorem. It +characterises the sense in which the translation respects the handling +of operations. Note how the values substituted for the resumption +variable $r$ in both cases are in the image of the translation of +$\lambda$-terms in the CPS translation. This is thanks to the precise +way that the reductions rules for resumption construction works in our +dynamic language, as described above. % -Here we make use of an auxiliary function, -$\intToString : \Int \to \String$, that turns an integer into a -string. The definition of this function is omitted here for brevity. -% -% -% \[ -% \bl -% \wc : \UnitType \to \UnitType \eff \{\Await : \UnitType \opto \Char;\Yield : \Int \opto \UnitType\}\\ -% \wc\,\Unit \defas -% \bl -% \Do\;\Yield~(wc'\,\Unit)\\ -% \where~ -% \bl -% wc' \Unit \defas -% \bl -% \Let\;c \revto \Do\;\Await~\Unit\;\In\\ -% \If\;c = \charlit{\textnil}\;\Then\;0\\ -% \Else\; 1 + wc'~\Unit -% \el -% \el -% \el -% \el -% \] -% - -We now have all the building blocks to construct a pipeline for -performing string frequency analysis on a file. The following performs -the analysis on the two first lines of Hamlet quote. -% -\[ - \ba{@{~}l@{~}l} - &\bl - \runState\,\Record{\dec{fs}_0;\fileIO\,(\lambda\Unit.\\ - \quad\timeshare\,(\lambda\Unit.\\ - \qquad\dec{interruptWrite}\,(\lambda\Unit.\\ - \qquad\quad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ - \qquad\qquad\status\,(\lambda\Unit. - \ba[t]{@{}l} - \quoteHamlet~\redirect~\strlit{hamlet};\\ - \Let\;p \revto - \bl - ~~(\lambda\Unit.\cat~\strlit{hamlet}) \mid (\lambda\Unit.\head~2) \mid \paste\\ - \mid (\lambda\Unit.\sed\,\Record{\strlit{be,};\strlit{be}}) \mid (\lambda\Unit.\sed\,\Record{\strlit{To};\strlit{to}})\\ - \mid (\lambda\Unit.\sed\,\Record{\strlit{question:};\strlit{question}})\\ - \mid \freq \mid \printTable - \el\\ - \In\;(\lambda\Unit.\echo~(p\,\Unit))~\redirect~\strlit{analysis})})))} - \ea - \el \smallskip\\ - \reducesto^+& - \bl - \Record{ - \ba[t]{@{}l} - [0];\\ - \Record{ - \ba[t]{@{}l} - dir=[\Record{\strlit{analysis};2},\Record{\strlit{hamlet};1}, - \Record{\strlit{stdout};0}];\\ - ilist=[\Record{2;\Record{lno=1;loc=2}}, - \Record{1;\Record{lno=1;loc=1}}, - \Record{0;\Record{lno=1;loc=0}}];\\ - dreg=[ - \ba[t]{@{}l} - \Record{2; - \ba[t]{@{}l@{}l} - \texttt{"}&\texttt{to:2;be:2;or:1;not:1;\nl:2;that:1;is:1}\\ - &\texttt{the:1;question:1;"}}, - \ea\\ - \Record{1; - \ba[t]{@{}l@{}l} - \texttt{"}&\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\ - &\texttt{Whether 'tis nobler in the mind to suffer\nl{}"}}, - \ea\\ - \Record{0; \strlit{}}]; lnext=3; inext=3}}\\ - \ea - \ea - \ea\\ - : \Record{\List~\Int; \FileSystem} - \el - \ea -\] -% -The pipeline gets bound to the variable $p$. The pipeline starts with -call to $\cat$ which streams the contents of the file -$\strlit{hamlet}$ to the process $\head$ applied to $2$, meaning it -will only forward the first two lines of the file to its -successor. The third process $\paste$ receives the first two lines one -character at a time and joins the characters into strings delimited by -whitespace. The next three instances of $\sed$ perform some string -normalisation. The first instance removes the trailing comma from the -string $\strlit{be,}$; the second normalises the capitalisation of the -word ``to''; and the third removes the trailing colon from the string -$\strlit{question:}$. The seventh process performs the frequency -analysis and outputs a table, which is being rendered as a string by -the eighth process. The output of the pipeline is supplied to the -$\echo$ utility whose output is being redirected to a file named -$\strlit{analysis}$. Contents of the file reside in location $2$ in -the data region. Here we can see that the analysis has found that the -words ``to'', ``be'', and the newline character ``$\nl$'' appear two -times each, whilst the other words appear once each. +\begin{lemma}[Handling]\label{lem:handle-op-gen-cont} +Suppose $\ell \notin BL(\EC)$ and $\hell = \{\ell\,p\,r \mapsto N_\ell\}$. If $H$ is deep then + % + % \[ + % \bl + % \cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}} \scons \sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)) \reducesto^+ \\ + % \quad (\cps{N_\ell} \sapp \sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)\\ + % \qquad \quad [\cps{V}/p, + % \dlam x\,\dhk.\bl + % \Let\;\dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk\;\In\;\\ + % \cps{\Return\;x} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}} \scons \sRecord{\reflect \dlk, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect\dhk'))/r]. \\ + % \el\\ + % \el + % \] + \[ + \bl + \cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}} \scons \sV_f \scons \sW)) \reducesto^+ \\ + \quad (\cps{N_\ell} \sapp (\sV_f \scons \sW)) + [\cps{V}/p, + \dlam x\,\dhk.\bl + \Let\;\dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk\;\In\; + \cps{\Return\;x}\\ + \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}} \scons \sRecord{\reflect \dlk, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect\dhk'))/r]. \\ + \el\\ + \el + \] + % + Otherwise if $H$ is shallow then + % + % \[ + % \bl + % \cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}^\dagger} \scons \sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)) \reducesto^+ \\ + % \quad (\cps{N_\ell} \sapp \sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)\\ + % \qquad [\cps{V}/p, \dlam x\,\dhk. \bl + % \Let\;\dRecord{\dlk, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk \;\In \\ + % \cps{\Return\;x} \sapp (\cps{\EC} \sapp (\sRecord{\reflect \dlk, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect\dhk'))/r]. \\ + % \el \\ + % \el + % \] + \[ + \bl + \cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}^\dagger} \scons \sV_f \scons \sW)) \reducesto^+ \\ + \quad (\cps{N_\ell} \sapp (\sV_f \scons \sW)) + [\cps{V}/p, \dlam x\,\dhk. \bl + \Let\;\dRecord{\dlk, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk \;\In\;\cps{\Return\;x}\\ + \sapp (\cps{\EC} \sapp (\sRecord{\reflect \dlk, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect\dhk'))/r]. \\ + \el \\ + \el + \] + % +\end{lemma} -\section{Parameterised handlers} -\label{sec:unary-parameterised-handlers} +\medskip -Parameterised handlers are a variation of ordinary deep handlers with -an embedded functional state cell. This state cell is only accessible -locally within the handler. The use of state within the handler is -opaque to both the ambient context and the context of the computation -being handled. Semantically, parameterised handlers are defined as -folds with state threading over computation trees. +With the aid of the above lemmas we can state and prove the main +result for the translation: a simulation result in the style of +\citet{Plotkin75}. +% +\begin{theorem}[Simulation] + \label{thm:ho-simulation-gen-cont} + If $M \reducesto N$ then + \[ + \cps{M} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} + \scons \sW) \reducesto^+ \cps{N} \sapp (\sRecord{\sV_{fs}, + \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW). + \] +\end{theorem} -We take the deep handler calculus $\HCalc$ as our starting point and -extend it with parameterised handlers to yield the calculus -$\HPCalc$. The parameterised handler extension interacts nicely with -shallow handlers, and as such it can be added to $\SCalc$ with low -effort. +\begin{proof} + The proof is by case analysis on the reduction relation using Lemmas + \ref{lem:decomposition-gen-cont}--\ref{lem:handle-op-gen-cont}. In + particular, the \semlab{Op} and \semlab{Op^\dagger} cases follow + from Lemma~\ref{lem:handle-op-gen-cont}. +\end{proof} -\subsection{Syntax and static semantics} -In addition to a computation, a parameterised handler also take a -value as argument. This argument is the initial value of the state -cell embedded inside the handler. +In common with most CPS translations, full abstraction does not hold +(a function could count the number of handlers it is invoked within by +examining the continuation, for example). However, as the semantics is +deterministic it is straightforward to show a backward simulation +result. % -\begin{syntax} -\slab{Handler\textrm{ }types} & F &::=& \cdots \mid \Record{C; A} \Rightarrow^\param D\\ -\slab{Computations} & M,N &::=& \cdots \mid \ParamHandle\; M \;\With\; H^\param(W)\\ -\slab{Parameterised\textrm{ }definitions} & H^\param &::=& q^A.~H -\end{syntax} +\begin{lemma}[Backwards simulation] + If $\pcps{M} \reducesto^+ V$ then there exists $W$ + such that $M \reducesto^\ast W$ and $\pcps{W} = V$. +\end{lemma} % -The syntactic category of handler types $F$ is extended with a new -kind of handler arrow for parameterised handlers. The left hand side -of the arrow is a pair, whose first component denotes the type of the -input computation and the second component denotes the type of the -handler parameter. The right hand side denotes the return type of the -handler. +\begin{corollary} +$M \reducesto^\ast V$ if and only if $\pcps{M} \reducesto^\ast \pcps{V}$. +\end{corollary} + +\section{Transforming parameterised handlers} +\label{sec:cps-param} % -The computations category is extended with a new application form for -handlers, which runs a computation $M$ under a parameterised handler -$H$ applied to the value $W$. +\begin{figure} +% \textbf{Continuation reductions} +% % +% \begin{reductions} +% \usemlab{KAppNil} & +% \kapp \; (\dRecord{\dnil, \dRecord{q, v, e}} \dcons \dhk) \, V &\reducesto& v \dapp \dRecord{q,V} \dapp \dhk \\ +% \usemlab{KAppCons} & +% \kapp \; (\dRecord{\dlf \dcons \dlk, h} \dcons \dhk) \, V &\reducesto& \dlf \dapp V \dapp (\dRecord{\dlk, h} \dcons \dhk) \\ +% \end{reductions} +% % +% \textbf{Resumption reductions} +% % +% \[ +% \ba{@{}l@{\quad}l@{}} +% \usemlab{Res}^\ddag & +% \Let\;r=\Res(V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N \reducesto \\ +% &\quad N[\dlam x\,\dhk. \bl\Let\;\dRecord{fs, \dRecord{q,\vhret, \vhops}}\dcons \dhk' = \dhk\;\In\\ +% \kapp\;(V_1 \dcons \dots \dcons V_n \dcons \dRecord{fs, \dRecord{q,\vhret, \vhops}} \dcons \dhk')\;x/r]\el +% \\ +% \ea +% \] % -Finally, a new category is added for parameterised handler -definitions. A parameterised handler definition is a new binding form -$(q^A.~H)$, where $q$ is the name of the parameter, whose type is $A$, -and $H$ is an ordinary handler definition $H$. The parameter $q$ is -accessible in the $\Return$ and operation clauses of $H$. - -As with ordinary deep handlers and shallow handlers, there are two -typing rules: one for handler application and another for handler -definitions. +\textbf{Computations} % -\begin{mathpar} - % Handle - \inferrule*[Lab=\tylab{Handle^\param}] - { - % \typ{\Gamma}{V : A} \\ - \typ{\Gamma}{M : C} \\ - \typ{\Gamma}{W : A} \\ - \typ{\Gamma}{H^\param : \Record{C; A} \Harrow^\param D} - } - {\Gamma \vdash \ParamHandle \; M \; \With\; H^\param(W) : D} -\end{mathpar} -% -The $\tylab{Handle^\param}$ rule is similar to the $\tylab{Handle}$ -and $\tylab{Handle^\dagger}$ rules, except that it has to account for -the parameter $W$, whose type has to be compatible with the second -component of the domain type of the handler definition $H^\param$. -% -The typing rule for parameterised handler definitions adapts the -corresponding typing rule $\tylab{Handler}$ for ordinary deep handlers -with the addition of a parameter. -% -\begin{mathpar} -% Parameterised handler - \inferrule*[Lab=\tylab{Handler^\param}] - {{\bl - C = A \eff \{(\ell_i : A_i \to B_i)_i; R\} \\ - D = B \eff \{(\ell_i : P)_i; R\} \\ - H = \{\Return\;x \mapsto M\} \uplus \{ \OpCase{\ell_i}{p_i}{r_i} \mapsto N_i \}_i - \el}\\\\ - \typ{\Delta;\Gamma, q : A', x : A}{M : D}\\\\ - [\typ{\Delta;\Gamma,q : A', p_i : A_i, r_i : \Record{B_i;A'} \to D}{N_i : D}]_i - } - {\typ{\Delta;\Gamma}{(q^{A'} . H) : \Record{C;A'} \Harrow^\param D}} -\end{mathpar} -%% -The key differences between the \tylab{Handler} and -\tylab{Handler^\param} rules are that in the latter the return and -operation cases are typed with respect to the parameter $q$, and that -resumptions $r_i$ have type $\Record{B_\ell;A'} \to D$, that is a -parameterised resumption is a binary function, where the first -argument is the interpretation of an operation and the second argument -is the (updated) handler state. The return type of $r_i$ is the same -as the return type of the handler, meaning that an invocation of $r_i$ -is guarded in the same way as an invocation of an ordinary deep -resumption. - -\subsection{Dynamic semantics} -The two reduction rules for parameterised handlers adapt the reduction -rules for ordinary deep handlers with a parameter. -% -\begin{reductions} -\semlab{Ret^\param} & - \ParamHandle \; (\Return \; V) \; \With \; (q.H)(W) &\reducesto& N[V/x,W/q],\\ - \multicolumn{4}{@{}r@{}}{ - \hfill\text{where } \hret = \{ \Return \; x \mapsto N \}} \\ -\semlab{Op^\param} & - \ParamHandle \; \EC[\Do \; \ell \, V] \; \With \; (q.H)(W) - &\reducesto& N[V/p,W/q,R/r],\\ -\multicolumn{4}{@{}r@{}}{ - \hfill\ba[t]{@{~}r@{~}l} - \text{where}& R = \lambda\Record{y;q'}.\ParamHandle\;\EC[\Return \; y]\;\With\;(q.H)(q')\\ - \text{and} &\hell = \{ \OpCase{\ell}{p}{r} \mapsto N \}\\ - \text{and} &\ell \notin \BL(\EC) - \ea -} -\end{reductions} +\begin{equations} +\cps{-} &:& \CompCat \to \SValCat^\ast \to \UCompCat\\ +% \cps{\Let~x \revto M~\In~N} &\defas& +% \bl\slam \sRecord{\shf, \sRecord{\xi, \svhret, \svhops}} \scons \shk. +% \ba[t]{@{}l} +% \cps{M} \sapp (\sRecord{\bl\reflect((\dlam x\,\dhk.\bl\Let\;(\dk \dcons \dhk') = \dhk\;\In\\ +% \cps{N} \sapp (\reflect\dk \scons \reflect \dhk')) \el\\ +% \dcons \reify\shf), \sRecord{\xi, \svhret, \svhops}} \scons \shk)\el +% \ea +% \el\\ +\cps{\Do\;\ell\;V} &\defas& + \slam \sRecord{\shf, \sRecord{\xi, \svhret, \svhops}} \scons \shk.\, + \reify\svhops \bl\dapp \dRecord{\reify\xi, \ell, + \dRecord{\bl + \cps{V}, \dRecord{\reify \shf, \dRecord{\reify\xi,\reify\svhret, \reify\svhops}}\\ + \dcons \dnil}} + \dapp \reify \shk\el\el \\ +\end{equations} +\begin{equations} +\cps{\ParamHandle \, M \; \With \; (q.H)(W)} &\defas& +\slam \shk . \cps{M} \sapp (\sRecord{\snil, \sRecord{\reflect\cps{W},\reflect \cps{\hret}^{\ddag}_q, \reflect \cps{\hops}^{\ddag}_q}} \scons \shk) \\ +\end{equations} +\textbf{Handler definitions} % -The rule $\semlab{Ret^\param}$ handles the return value of a -computation. Just like the rule $\semlab{Ret}$ the return value $V$ is -substituted for the binder $x$ in the return case body -$N$. Furthermore the value $W$ is substituted for the handler -parameter $q$ in $N$, meaning the handler parameter is accessible in -the return case. - -The $\semlab{Op^\param}$ handles an operation invocation. Both the -operation payload $V$ and handler argument $W$ are accessible inside -the case body $N$. As with ordinary deep handlers, the resumption -rewraps its handler, but with the slight twist that the parameterised -handler definition is applied to the updated parameter value $q'$ -rather than the original value $W$. This achieves the effect of state -passing as the value of $q'$ becomes available upon the next -activation of the handler. +\begin{equations} +\cps{-} &:& \HandlerCat \times \UValCat \to \UValCat\\ +% \cps{H}^\depth &=& \sRecord{\reflect \cps{\hret}, \reflect \cps{\hops}^\depth}\\ +\cps{\{\Return \; x \mapsto N\}}^{\ddag}_q &\defas& \dlam \dRecord{q,x}\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{N} \sapp (\reflect\dk \scons \reflect \dhk') \\ +\cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}}^{\ddag}_q +&\defas& +\dlam \dRecord{q,z,\dRecord{p,\dhkr}}\,\dhk. + \Case \;z\; \{ + \ba[t]{@{}l@{}c@{~}l} + (&\ell &\mapsto + \ba[t]{@{}l} + \Let\;r=\Res^\ddag\,\dhkr\;\In\; \\ + \Let\;(\dk \dcons \dhk') = \dhk\;\In\\ + \cps{N_{\ell}} \sapp (\reflect\dk \scons \reflect \dhk'))_{\ell \in \mathcal{L}} + \ea\\ + &y &\mapsto \hforward((y, p, \dhkr), \dhk) \} \\ + \ea \\ +\hforward((y, p, \dhkr), \dhk) &\defas& \bl + \Let\; \dRecord{fs, \dRecord{q, \vhret, \vhops}} \dcons \dhk' = \dhk \;\In \\ + \vhops \dapp \dRecord{q, y,\dRecord{p, \dRecord{fs, \dRecord{q,\vhret, \vhops}} \dcons \dhkr}} \dapp \dhk' \\ + \el +\end{equations} -The metatheoretic properties of $\HCalc$ carries over to $\HPCalc$. -\begin{theorem}[Progress] - Suppose $\typ{}{M : C}$, then either there exists $\typ{}{N : C}$ - such that $M \reducesto^+ N$ and $N$ is normal, or $M$ diverges. -\end{theorem} -% -\begin{proof} - By induction on the typing derivations. -\end{proof} -% -\begin{theorem}[Subject reduction] - Suppose $\typ{\Gamma}{M : C}$ and $M \reducesto M'$, then - $\typ{\Gamma}{M' : C}$. -\end{theorem} -% -\begin{proof} - By induction on the typing derivations. -\end{proof} +\textbf{Top-level program} +\begin{equations} +\pcps{M} &=& \cps{M} \sapp (\sRecord{\dnil, \sRecord{\reflect\dRecord{},\reflect \dlam \dRecord{q,x}\,\dhk. x, \reflect \dlam \dRecord{q,z}\,\dhk.\Absurd~z}} \scons \snil) \\ +\end{equations} -\subsection{Process synchronisation} -% -In Section~\ref{sec:tiny-unix-time} we implemented a time-sharing -system on top of a simple process model. However, the model lacks a -process synchronisation facility. It is somewhat difficult to cleanly -add support for synchronisation to the implementation as it is in -Section~\ref{sec:tiny-unix-time}. Firstly, because the interface of -$\Fork : \UnitType \to \Bool$ only gives us two possible process -identifiers: $\True$ and $\False$, meaning at any point we can only -identify two processes. Secondly, and more importantly, some state is -necessary to implement synchronisation, but the current implementation -of process scheduling is split amongst two handlers and one auxiliary -function, all of which need to coordinate their access and -manipulation of the state cell. One option is to use some global state -via the interface from Section~\ref{sec:tiny-unix-io}, which has the -advantage of making the state manipulation within the scheduler -modular, but it also has the disadvantage of exposing the state as an -implementation detail --- and it comes with all the caveats of -programming with global state. A parameterised handler provides an -elegant solution, which lets us internalise the state within the -scheduler. +\caption{CPS translation for parameterised handlers.} +\label{fig:param-cps} +\end{figure} -We will see how a parameterised handler enables us to implement a -richer process model supporting synchronisation with ease. The effect -signature of process concurrency is as follows. +Generalised continuations provide a versatile implementation strategy +for effect handlers as exemplified in the previous section. In this +section we add further emphasis on the versatility of generalised +continuations by demonstrating how to adapt the continuation structure +to accommodate parameterised handlers. In order to support +parameterised handlers, each effect continuation must store the +current value of the handler parameter. Thus, an effect continuation +becomes a triple consisting of the parameter, return clause, and +operation clause(s). Furthermore, the return clause gets transformed +into a binary function, that takes the current value of the handler +parameter as its first argument and the return value of the handled +computation as its second argument. Similarly, the operation clauses +are transformed into a binary function, that takes the handler +parameter first and the operation package second. This strategy +effectively amounts to explicit state passing as the parameter value +gets threaded through every handler continuation +function. Operationally, the pure continuation invocation rule +$\usemlab{KAppNil}$ requires a small adjustment to account for the +handler parameter. % \[ - \Co \defas \{\UFork : \UnitType \opto \Int; \Wait : \Int \opto \UnitType; \Interrupt : \UnitType \opto \UnitType\} + \kapp \; (\dRecord{\dnil, \dRecord{q, \vhret, \vhops}} \dcons \dhk) \, V \reducesto \vhret \dapp \Record{q,V} \dapp \dhk \] % -The operation $\UFork$ models \UNIX{} -\emph{fork}~\cite{RitchieT74}. It is generalisation of the $\Fork$ -operation from Section~\ref{sec:tiny-unix-time}. The operation is -intended to return twice: once to the parent process with a unique -process identifier for the child process, and a second time to the -child process with the zero identifier. The $\Wait$ operation takes a -process identifier as argument and then blocks the invoking process -until the process associated with the provided identifier has -completed. The $\Interrupt$ operation is the same as in -Section~\ref{sec:tiny-unix-time}; it temporarily suspends the invoking -process in order to let another process run. - -The main idea is to use the state cell of a parameterised handler to -manage the process queue and to keep track of the return values of -completed processes. The scheduler will return an association list of -process identifiers mapped to the return value of their respective -process when there are no more processes to be run. The process queue -will consist of reified processes, which we will represent using -parameterised resumptions. To make the type signatures understandable -we will make use of three mutually recursive type aliases. +The pure continuation $v$ is now applied to a pair consisting of the +current value of the handler parameter $q$ and the return value +$V$. Similarly, the resumption rule $\usemlab{Res}$ must also be +adapted to update the value of the handler parameter. % \[ - \ba{@{~}l@{~}l@{~}c@{~}l} - \Proc &\alpha~\varepsilon &\defas& \Sstate~\alpha~\varepsilon \to \List\,\Record{\Int;\alpha} \eff \varepsilon\\ - \Pstate &\alpha~\varepsilon &\defas& [\Ready:\Proc~\alpha~\varepsilon;\Blocked:\Record{\Int;\Proc~\alpha~\varepsilon}]\\ - \Sstate &\alpha~\varepsilon &\defas& \Record{q:\List\,\Record{\Int;\Pstate~\alpha~\varepsilon};done:\List~\alpha;pid:\Int;pnext:\Int}\\ - \ea + \bl + \Let\;r=\Res^\ddag\,(\dRecord{q,\vhret,\vhops} \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N\reducesto\\ + \qquad N[\dlam \dRecord{q',x}\,\dhk.\kapp\;(V_1 \dcons \dots \dcons \dRecord{q',\vhret,\vhops} \dcons \dhk)\;x/r] + \el \] % -The $\Proc$ alias is the type of reified processes. It is defined as a -function that takes the current scheduler state and returns an -association list of $\alpha$s indexed by integers. This is almost the -type of a parameterised resumption as the only thing missing is the a -component for the interpretation of an operation. -% -The second alias $\Pstate$ enumerates the possible process -states. Either a process is \emph{ready} to be run or it is -\emph{blocked} on some other process. The payload of the $\Ready$ tag -is the process to run. The $\Blocked$ tag is parameterised by a pair, -where the first component is the identifier of the process that is -being waited on and the second component is the process to be -continued when the other process has completed. -% -The third alias $\Sstate$ is the type of scheduler state. It is a -quadruple, where the first label $q$ is the process queue. It is -implemented as an association list indexed by process identifiers. The -second label $done$ is used to store the return values of completed -processes. The third label $pid$ is used to remember the identifier of -currently executing process, and the fourth label $pnext$ is used to -compute a unique identifier for new processes. +The rule is not much different from the original $\usemlab{Res}$ +rule. The difference is that this rule unpacks the current handler +parameter $q$ along with the return clause, $\vhret$, and operation +clauses, $\vhops$. The reduction constructs a resumption function, +whose first parameter $q'$ binds the updated value of the handler +parameter. The $q'$ is packaged with the original $\vhret$ and +$\vhops$ such that the next activation of the handler gets the +parameter value $q'$ rather than $q$. -We will abstract some of the scheduling logic into an auxiliary -function $\runNext$, which is responsible for dequeuing and running -the next process from the queue. -% -\[ - \bl - \runNext : \Sstate~\alpha~\varepsilon \to \List~\alpha \eff \varepsilon\\ - \runNext~st \defas - \bl - \Case\;st.q\;\{\\ - ~\bl - \nil \mapsto st.done\\ - \Record{pid;\Blocked\,\Record{pid';resume}} \cons q' \mapsto\\ - \quad\bl - \Let\;st' \revto \Record{st \;\With\; q = q' \concat [\Record{pid;\Blocked\,\Record{pid';resume}}]}\;\In\\ - \runNext~st' - \el\\ - \Record{pid;\Ready~resume} \cons q' \mapsto\\ - \quad\bl - \Let\;st' \revto \Record{st \;\With\; q = q'; pid = pid}\;\In\\ - resume~st'\,\} - \el - \el - \el - \el -\] -% -The function operates on the scheduler state. It first performs a case -split on the process queue. There are three cases to consider. -\begin{enumerate} - \item The queue is empty. Then the function returns the list $done$, - which is the list of process return values. - \item The next process is blocked. Then the process is appended on - to the end of the queue, and $\runNext$ is applied recursively to - the scheduler state $st'$ with the updated queue. - \item The next process is ready. Then the $q$ and $pid$ fields - within the scheduler state are updated accordingly. The reified - process $resume$ is applied to the updated scheduler state $st'$. -\end{enumerate} -% -Evidently, this function may enter an infinite loop if every process -is in blocked state. This may happen if we deadlock any two processes -by having them wait on one another. Using this function we can define -a handler that implements a process scheduler. -% -\[ - \bl - \scheduler : \Record{\alpha \eff \{\Co;\varepsilon\};\Sstate~\alpha~\varepsilon} \Harrow^\param \List\,\Record{\Int;\alpha} \eff \varepsilon\\ - \scheduler \defas - \bl - st.\\ - \bl - \Return\;x \mapsto \\ - \quad\bl - \Let\;done' \revto \Record{st.pid;x} \cons st.done\;\In\\ - \runNext\,\Record{st\;\With\;done = done'} - \el\\ - \OpCase{\UFork}{\Unit}{resume} \mapsto\\ - \quad\bl - \Let\;resume' \revto \lambda st.resume\,\Record{0;st}\;\In\\ - \Let\;pid \revto st.pnext \;\In\\ - \Let\;q' \revto st.q \concat [\Record{pid;\Ready~resume'}]\;\In\\ - \Let\;st' \revto \Record{st\;\With\;q = q'; pnext = pid + 1}\;\In\\ - resume\,\Record{pid;st'} - \el\\ - \OpCase{\Wait}{pid}{resume} \mapsto\\ - \quad\bl - \Let\;resume' \revto \lambda st.resume~\Record{\Unit;st}\;\In\\ - \Let\;q' \revto - \bl - \If\;\dec{has}\,\Record{pid;st.q}\\ - \Then\;st.q \concat [\Record{st.pid;\Blocked\,\Record{pid;resume'}}]\\ - \Else\;st.q \concat [\Record{st.pid;\Ready~resume'}] - \el\\ - \In\;\runNext\,\Record{st\;\With\;q = q'} - \el\\ - \OpCase{\Interrupt}{\Unit}{resume} \mapsto\\ - \quad\bl - \Let\;resume' \revto \lambda st.resume\,\Record{\Unit;st}\;\In\\ - \Let\;q' \revto st.q \concat [\Record{st.pid;\Ready~resume'}]\;\In\\ - \runNext~\Record{st \;\With\; q = q'} - \el - \el - \el - \el -\] +The CPS translation is updated accordingly to account for the triple +effect continuation structure. This involves updating the cases that +scrutinise the effect continuation structure as it now includes the +additional state value. The cases that need to be updated are shown in +Figure~\ref{fig:param-cps}. We write $\xi$ to denote static handler +parameters. % -The handler definition $\scheduler$ takes as input a computation that -computes a value of type $\alpha$ whilst making use of the concurrency -operations from the $\Co$ signature. In addition it takes the initial -scheduler state as input. Ultimately, the handler returns a -computation that computes a list of $\alpha$s, where all the -$\Co$-operations have been handled. +% The translation of $\Let$ unpacks and repacks the effect continuation +% to maintain the continuation length invariant. +The translation of $\Do$ invokes the effect continuation +$\reify \svhops$ with a triple consisting of the value of the handler +parameter, the operation, and the operation payload. The parameter is +also pushed onto the reversed resumption stack. This is necessary to +account for the case where the effect continuation $\reify \svhops$ +does not handle operation $\ell$. +% An alternative option is to push the parameter back +% on the resumption stack during effect forwarding. However that means +% the resumption stack will be nonuniform as the top element sometimes +% will be a pair. % -In the definition the scheduler state is bound by the name $st$. - -The $\Return$ case is invoked when a process completes. The return -value $x$ is paired with the identifier of the currently executing -process and consed onto the list $done$. Subsequently, the function -$\runNext$ is invoked in order to the next ready process. - -The $\UFork$ case implements the semantics for process forking. First -the child process is constructed by abstracting the parameterised -resumption $resume$ such that it becomes an unary state-accepting -function, which can be ascribed type $\Proc~\alpha~\varepsilon$. The -parameterised resumption applied to the process identifier $0$, which -lets the receiver know that it assumes the role of child in the -parent-child relationship amongst the processes. The next line -retrieves the unique process identifier for the child. Afterwards, the -child process is pushed on to the queue in ready state. The next line -updates the scheduler state with the new queue and a new unique -identifier for the next process. Finally, the parameterised resumption -is applied to the child process identifier and the updated scheduler -state. -The $\Wait$ case implements the synchronisation operation. The -parameter $pid$ is the identifier of the process that the invoking -process wants to wait on. First we construct an unary state-accepting -function. Then we check whether there exists a process with identifier -$pid$ in the queue. If there is one, then we enqueue the current -process in blocked state. If no such process exists (e.g. it may -already have finished), then we enqueue the current process in ready -state. Finally, we invoke $\runNext$ with the scheduler state updated -with the new process queue in order to run the next ready process. +The translation of the return and operation clauses are parameterised +by the name of the binder for the handler parameter. Each translation +yields functions that take a pair as input in addition to the current +continuation. The forwarding case is adjusted in the same way as the +translation for $\Do$. The current continuation $k$ is deconstructed +in order to identify the next effect continuation $\vhops$ and its +parameter $q$. Then $\vhops$ is invoked with the updated resumption +stack and the value of its parameter $q$. +% +The top-level translation adds a `dummy' unit value, which is ignored +by both the pure continuation and effect continuation.% The amended +% CPS translation for parameterised handlers is not a zero cost +% translation for shallow and ordinary deep handlers as they will have +% to thread a ``dummy'' parameter value through. +We can avoid the use of such values entirely if the target language +had proper sums to tag effect continuation frames +accordingly. Obviously, this entails performing a case analysis every +time an effect continuation frame is deconstructed. -The $\Interrupt$ case suspends the current process by enqueuing it in -ready state, and dequeuing the next ready process. +\section{Related work} +\label{sec:cps-related-work} -Using this handler we can implement version 2 of the time-sharing -system. +\paragraph{CPS transforms for effect handlers} % -\[ - \bl - \timesharee : (\UnitType \to \alpha \eff \Co) \to \List\,\Record{\Int;\alpha}\\ - \timesharee~m \defas - \bl - \Let\;st_0 \revto \Record{q=\nil;done=\nil;pid=1;pnext=2}\;\In\\ - \ParamHandle\;m\,\Unit\;\With\; \scheduler~st_0 - \el - \el -\] +The one-pass higher-order CPS translation for deep, shallow, and +parameterised handlers draws on insights from the literature on CPS +translations for delimited control operators such as shift and +reset~\citep{DanvyF90,DanvyF92,DanvyN03,MaterzokB12}. % -The computation $m$, which may perform any of the concurrency -operations, is handled by the parameterised handler $\scheduler$. The -parameterised handler definition is applied to the initial scheduler -state, which has an empty process queue, an empty done list, and it -assigns the first process the identifier $1$, and sets up the -identifier for the next process to be $2$. +% \citet{DybvigJS07} develop a lean monadic framework for implementing +% multi-prompt delimited continuations. +% \paragraph{CPS translations for handlers} +% +Other CPS translations for handlers use a monadic approach. For +example, \citet{Leijen17} implements deep and parameterised handlers +in Koka~\citep{Leijen14} by translating them into a free monad +primitive in the runtime. \citeauthor{Leijen17} uses a selective CPS +translation to lift code into the monad. The selective aspect is +important in practice to avoid overhead in code that does not use +effect handlers. +% +Scala Effekt~\citep{BrachthauserS17,BrachthauserSO20} provides an +implementation of effect handlers as a library for the Scala +programming language. The implementation is based closely on the +monadic delimited control framework of \citet{DybvigJS07}. +% +A variation of the Scala Effekt library is used to implement effect +handlers as an interface for programming with delimited continuations +in Java~\citep{BrachthauserSO18}. The implementation of delimited +continuations depend on special byte code instructions, inserted via a +selective type-driven CPS translation. -With $\UFork$ and $\Wait$ we can implement the \emph{init} process, -which is the initial startup process in -\UNIX{}~\cite{RitchieT74}. This process remains alive until the -operating system is shutdown. It is the ancestor of every process -created by the operating system. +The Effekt language (which is distinct from the Effekt library) +implements handlers by a translation into capability-passing style, +which may more informatively be dubbed \emph{handler-passing style} as +handlers are passed downwards to the invocation sites of their +respective operations~\cite{SchusterBO20,BrachthauserSO20b}. The +translation into capability-passing style is realised by way of a +effect-type directed iterated CPS transform, which introduces a +continuation argument per handler in scope~\cite{SchusterBO20}. The +idea of iterated CPS is due to \citet{DanvyF90}, who used it to give +develop a CPS transform for shift and reset. % -\[ - \bl - \init : (\Unit \to \alpha \eff \varepsilon) \to \alpha \eff \{\Co;\varepsilon\}\\ - \init~main \defas - \bl - \Let\;pid \revto \Do\;\UFork~\Unit\;\In\\ - \If\;pid = 0\\ - \Then\;main\,\Unit\\ - \Else\;\Do\;\Wait~pid - \el - \el -\] +\citet{XieBHSL20} have devised an \emph{evidence-passing translation} +for deep effect handlers. The basic idea is similar to +capability-passing style as evidence for handlers are passed downwards +to their operations in shape of a vector containing the handlers in +scope through computations. \citet{XieL20} have realised handlers by +evidence-passing style as a Haskell library. + +There are clear connections between the CPS translations presented in +this chapter and the continuation monad implementation of +\citet{KammarLO13}. Whereas \citeauthor{KammarLO13} present a +practical Haskell implementation depending on sophisticated features +such as type classes, which to some degree obscures the essential +structure, here we have focused on a foundational formal treatment. % -We implement $\init$ as a higher-order function. It takes a main -routine that will be applied when the system has been started. The -function first performs $\UFork$ to duplicate itself. The child branch -executes the $main$ routine, whilst the parent branch waits on the -child. +\citeauthor{KammarLO13} obtain impressive performance results by +taking advantage of the second class nature of type classes in Haskell +coupled with the aggressive fusion optimisations GHC +performs~\citep{WuS15}. -Now we can plug everything together. +\paragraph{Plotkin's colon translation} % -\[ - \ba{@{~}l@{~}l} - &\bl - \runState\,\Record{\dec{fs}_0;\fileIO\,(\lambda\Unit.\\ - \quad\timesharee\,(\lambda\Unit.\\ - \qquad\dec{interruptWrite}\,(\lambda\Unit.\\ - \qquad\quad\sessionmgr\,\Record{\Root;\lambda\Unit.\\ - \qquad\qquad\status\,(\lambda\Unit. - \init\,(\lambda\Unit. - \ba[t]{@{}l} - \Let\;pid \revto \Do\;\UFork\,\Unit\;\In\\ - \If\;pid = 0\\ - \Then\;\bl - \su~\Alice; - \quoteRitchie\,\Unit - \el\\ - \Else\; \su~\Bob; \Do\;\Wait~pid; \quoteHamlet\,\Unit))})))} - \ea - \el \smallskip\\ - \reducesto^+& - \bl - \Record{ - \ba[t]{@{}l} - [\Record{1;0};\Record{2;0};\Record{3;0}];\\ - \Record{ - \ba[t]{@{}l} - dir=[\Record{\strlit{stdout};0}];\\ - ilist=[\Record{0;\Record{lno=1;loc=0}}];\\ - dreg=[ - \ba[t]{@{}l} - \Record{0; - \ba[t]{@{}l@{}l} - \texttt{"}&\texttt{UNIX is basically a simple operating system, but }\\ - &\texttt{you have to be a genius to understand the simplicity.\nl}\\ - &\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\ - &\texttt{Whether 'tis nobler in the mind to suffer\nl{}"}}] - \ea\\ - lnext=1; inext=1}}\\ - \ea - \ea - \ea\\ - : \Record{\List\,\Record{\Int;\Int}; \FileSystem} - \el - \ea -\] +The original method for proving the correctness of a CPS +translation is by way of a simulation result. Simulation states that +every reduction sequence in a given source program is mimicked by its +CPS transformation. % -Process number $1$ is $\init$, which forks itself to run its -argument. The argument runs as process $2$, which also forks itself, -thus creating a process $3$. Process $3$ executes the child branch, -which switches user to $\Alice$ and invokes the $\quoteRitchie$ -process which writes to standard out. Process $2$ executes the parent -branch, which switches user to $\Bob$ and waits for the child process -to complete before it invokes the routine $\quoteHamlet$ which also -writes to standard out. +Static administrative redexes in the image of a CPS translation +provide hurdles for proving simulation, since these redexes do not +arise in the source program. % -It is evident from looking at the file system state that the writes to -standard out has not been interleaved as the contents of -$\strlit{stdout}$ appear in order. We can also see from the process -completion list that Alice's process (pid $3$) is the first to -complete with status $0$, and the second to complete is Bob's process -(pid $2$) with status $0$, whilst the last process to complete is the -$\init$ process (pid $1$) with status $0$. - -\paragraph{Retrofitting fork} In the previous program we replaced the -original implementation of $\timeshare$ -(Section~\ref{sec:tiny-unix-time}), which handles invocations of -$\Fork : \UnitType \opto \Bool$, by $\timesharee$, which handles the -more general operation $\UFork : \UnitType \opto \Int$. In practice, -we may be unable to dispense of the old interface so easily, meaning -we have to retain support for, say, legacy reasons. As we have seen -previously we can an operation in terms of another operation. Thus to -retain support for $\Fork$ we simply have to insert a handler under -$\timesharee$ which interprets $\Fork$ in terms of $\UFork$. The -operation case of this handler would be akin to the following. +\citet{Plotkin75} uses the so-called \emph{colon translation} to +overcome static administrative reductions. % -\[ - \OpCase{\Fork}{\Unit}{resume} \mapsto - \bl - \Let\;pid \revto \Do\;\UFork~\Unit\;\In\\ - resume\,(pid \neq 0) - \el -\] +Informally, it is defined such that given some source term $M$ and +some continuation $k$, then the term $M : k$ is the result of +performing all static administrative reductions on $\cps{M}\,k$, that +is to say $\cps{M}\,k \areducesto^* M : k$. % -The interpretation of $\Fork$ inspects the process identifier returned -by the $\UFork$ to determine the role of the current process in the -parent-child relationship. If the identifier is nonzero, then the -process is a parent, hence $\Fork$ should return $\True$ to its -caller. Otherwise it should return $\False$. This preserves the -functionality of the legacy code. +Thus this translation makes it possible to bypass administrative +reductions and instead focus on the reductions inherited from the +source program. +% +The colon translation captures precisely the intuition that drives CPS +transforms, namely, that if in the source $M \reducesto^\ast \Return\;V$ +then in the image $\cps{M}\,k \reducesto^\ast k\,\cps{V}$. -\section{Related work} -\label{sec:unix-related-work} +%\dhil{Check whether the first pass marks administrative redexes} -\paragraph{Programming languages with handlers} The work presented in -this chapter has been retrofitted on to the programming language -Links~\cite{HillerstromL16,HillerstromL18}. A closely related -programming language with handlers is \citeauthor{Leijen17}'s Koka, -which has been retrofitted with ordinary deep and parameterised effect -handlers~\cite{Leijen17}. In Koka effects are nominal, meaning an -effect and its constructors must be declared before use, which is -unlike the structural approach taken in this chapter. Koka also tracks -effects via an effect system based on \citeauthor{Leijen05}-style row -polymorphism~\cite{Leijen05,Leijen14}, where rows are interpreted as -multisets which means an effect can occur multiple times in an effect -row. The ability to repeat effects provide a form for effect scoping -in the sense that an effect instance can shadow another. A handler -handles only the first instance of a repeated effect, leaving the -remaining instances for another handler. Consequently, the order of -repeated effect instances matter and it can therefore be situational -useful to manipulate the order of repeated instances by way of -so-called \emph{effect masking}. -% -The notion of effect masking was formalised by \citet{BiernackiPPS18} -and generalised by \citet{ConventLMM20}. -% -\citet{BiernackiPPS18} designed Helium, which is a programming -language that features a rich module system, deep handlers, and -\emph{lexical} handlers~\cite{BiernackiPPS20}. Lexical handlers -\emph{bind} effectful operations to specific handler -instances. Operations remain bound for the duration of -computation. This makes the nature of lexical handlers more static -than ordinary deep handlers, as for example it is not possible to -dynamically overload the interpretation of residual effects of a -resumption invocation as in Section~\ref{sec:tiny-unix-env}. -% -The mathematical foundations for lexical handlers has been developed -by \citet{Geron19}. +% CPS The colon translation captures the +% intuition tThe colon translation is itself a CPS translation which +% yields -The design of the Effekt language by \citet{BrachthauserSO20b} -resolves around the idea of lexical handlers for efficiency. Effekt -takes advantage of the static nature of lexical handlers to eliminate -the dynamic handler lookup at runtime by tying the correct handler -instance directly to an operation -invocation~\cite{BrachthauserS17,SchusterBO20}. The effect system of -Effekt is based on intersection types, which provides a limited form -of effect polymorphism~\cite{BrachthauserSO20b}. A design choice that -means it does not feature first-class functions. -The Frank language by \citet{LindleyMM17} is born and bred on shallow -effect handlers. One of the key novelties of Frank is $n$-ary shallow -handlers, which generalise ordinary unary shallow handlers to be able -to handle multiple computations simultaneously. Another novelty is the -effect system, which is based on a variation of -\citeauthor{Leijen05}-style row polymorphism, where the programmer -rarely needs to mention effect variables. This is achieved by -insisting that the programmer annotates each input argument with the -particular effects handled at the particular argument position as well -as declaring what effects needs to be handled by the ambient -context. Each annotation is essentially an incomplete row. They are -made complete by concatenating them and inserting a fresh effect -variable. +% In his seminal work, \citet{Plotkin75} devises CPS translations for +% call-by-value lambda calculus into call-by-name lambda calculus and +% vice versa. \citeauthor{Plotkin75} establishes the correctness of his +% translations by way of simulations, which is to say that every +% reduction sequence in a given source program is mimicked by the +% transformed program. +% % +% His translations generate static administrative redexes, and as argued +% previously in this chapter from a practical view point this is an +% undesirable property in practice. However, it is also an undesirable +% property from a theoretical view point as the presence of +% administrative redexes interferes with the simulation proofs. -\citeauthor{BauerP15}'s Eff language was the first programming -language designed from the ground up with effect handlers in mind. It -features only deep handlers~\cite{BauerP15}. A previous iteration of -the language featured an explicit \emph{effect instance} system. An -effect instance is a sort of generative interface, where the -operations are unique to each instance. As a result it is possible to -handle two distinct instances of the same effect differently in a -single computation. Their system featured a type-and-effect system -with support for effect inference~\cite{Pretnar13,BauerP13}, however, -the effect instance system was later dropped to in favour of a vanilla -nominal approach to effects and handlers. +% To handle the static administrative redexes, \citeauthor{Plotkin75} +% introduced the so-called \emph{colon translation} to bypass static +% administrative reductions, thus providing a means for focusing on +% reductions induced by abstractions inherited from the source program. +% % +% The colon translation is itself a CPS translation, that given a source +% expression, $e$, and some continuation, $K$, produces a CPS term such +% that $\cps{e}K \reducesto e : K$. -Multicore OCaml is, at the time of writing, an experimental branch of -the OCaml programming language, which aims to extend OCaml with effect -handlers for multicore and concurrent -programming~\cite{DolanWM14,DolanWSYM15}. The current incarnation -features untracked nominal effects and deep handlers with single-use -resumptions. +% \citet{DanvyN03} used this insight to devise a one-pass CPS +% translation that contracts all administrative redexes at translation +% time. -%\dhil{Possibly move to the introduction or background} +% \paragraph{Partial evaluation} -\paragraph{Effect-driven concurrency} -In their tutorial of the Eff programming language \citet{BauerP15} -implement a simple lightweight thread scheduler. It is different from -the schedulers presented in this section as their scheduler only uses -resumptions linearly. This is achieved by making the fork operation -\emph{higher-order} such that the operation is parameterised by a -computation. The computation is run under a fresh instance of the -handler. On one hand this approach has the benefit of making threads -cheap as it is no stack copying is necessary at runtime. On the other -hand it does not guarantee that every operation is handled uniformly -(when in the setting of deep handlers) as every handler in between the -fork operation invocation site and the scheduler handler needs to be -manually reinstalled when the computation argument is -run. Nevertheless, this is the approach to concurrency that -\citet{DolanWSYM15} have adopted for Multicore -OCaml~\cite{DolanWSYM15}. -% -In my MSc(R) dissertation I used a similar approach to implement a -cooperative version of the actor concurrency model of Links as a -user-definable Links library~\cite{Hillerstrom16}. This library was -used by a prototype compiler for Links to make the runtime as lean as -possible~(this compiler hooked directly into the backend of the -Multicore OCaml compiler in order to produce native code for effect -handlers~\cite{HillerstromL16}). -% -This line of work was further explored by \citet{Convent17}, who -implemented various cooperative actor-based concurrency abstractions -using effect handlers in the Frank programming -language. \citet{Poulson20} expanded upon this work by investigating -ways to handle preemptive concurrency. +\paragraph{ANF vs CPS} -\citet{FowlerLMD19} uses effect handlers in the setting of linearly -typed fault-tolerant distributed programming. They use effect handlers -to codify an exception handling mechanism, which automatically -consumes linear resources. Exceptions are implemented as operations, -that are handled by \emph{cancelling} their resumptions. Cancellation -is a runtime primitive that gathers and closes active resources in the -computation represented by some resumption. +\paragraph{Selective CPS transforms} +\dhil{TODO \citet{Nielsen01} \citet{DanvyH92} \citet{DanvyH93} \citet{Leijen17}} -\citet{DolanEHMSW17} and \citet{Leijen17a} gave two widely different -implementations of the async/await idiom using effect -handlers. \citeauthor{DolanEHMSW17}'s implementation is based on -higher-order operations with linearly used resumptions, whereas -\citeauthor{Leijen17a}'s implementation is based on first-order -operations with multi-shot resumptions, and thus, it is close in the -spirit to the schedulers we have considered in this chapter. +\chapter{Abstract machine semantics} +\label{ch:abstract-machine} +%\dhil{The text is this chapter needs to be reworked} -\paragraph{Continuations and operating systems} -% The very first implementation of `lightweight threads' using -% continuations can possibly be credited to -% \citet{Burstall69}. \citeauthor{Burstall69} used -% \citeauthor{Landin65}'s J operator to arrange tree-based search, where -% each branch would be reified as a continuation and put into a -% queue. -The idea of using continuations to implement various facets of -operating systems is not new. However, most work has focused on -implementing some form of multi-tasking mechanism. +Abstract machine semantics are an operational semantics that makes +program control more apparent than context-based reduction +semantics. In a some sense abstract machine semantics are a lower +level semantics than reduction semantics as they provide a model of +computation based on \emph{abstract machines}, which capture some core +aspects of how actual computers might go about executing programs. % -\citet{Wand80} implements a small multi-tasking kernel with support -for mutual exclusion and data protection using undelimited -continuations in the style of the catch operator of Scheme. -% \citet{HaynesFW86} codify coroutines as library using call/cc. -\citet{DybvigH89} implements \emph{engines} using call/cc in Scheme ---- an engine is a kind of process abstraction which support -preemption. An engine runs a computation on some time budget. If -computation exceeds the allotted time budget, then it is -interrupted. They represent engines as reified continuations and use -the macro system of Scheme to insert clock ticks at appropriate places -in the code. % \citet{HiebD90} also design the \emph{spawn-controller} -% operator for programming tree-based concurrency abstractions. -\citet{KiselyovS07a} develop a small fault-tolerant operating system -with multi-tasking support and a file system using delimited -continuations. Their file system is considerably more sophisticated -than the one we implemented in this chapter as it supports -transactional storage, meaning user processes can roll back actions -such as file deletion and file update. +Abstract machines come in different style and flavours, though, a +common trait is that they are defined in terms of +\emph{configurations}. A configuration includes the essentials to +describe the machine state as it were, i.e. some abstract notion of +call stack, memory, program counter, etc. -\paragraph{Resumption monad} -The resumption monad is both a semantic and programmatic abstraction -for interleaving computation. \citet{Papaspyrou01} applies a -resumption monad transformer to construct semantic models of -concurrent computation. A resumption monad transformer, i.e. a monad -$T$ that transforms an arbitrary monad $M$ to a new monad $T~M$ with -commands for interrupting computation. +In this chapter I will demonstrate an application of generalised +continuations (Section~\ref{sec:generalised-continuations}) to +abstract machines that emphasises the usefulness of generalised +continuations to implement various kinds of effect handlers. The key +takeaway from this application is that it is possible to plug the +generalised continuation structure into a standard framework to +achieve a simultaneous implementation of deep, shallow, and +parameterised effect handlers. % -\citet{Harrison06} demonstrates the resumption monad as a practical -programming abstraction by implementing a small multi-tasking -kernel. \citeauthor{Harrison06} implements two variations of the -resumption monad: basic and reactive. The basic resumption monad is a -closed environment for interleaving different strands of -computations. It is closed in the sense that strands of computation -cannot interact with the ambient context of their environment. The -reactive resumption monad makes the environment open by essentially -registering a callback with an interruption action. This provides a -way to model system calls. +Specifically I will change the continuation structure of a standard +\citeauthor{FelleisenF86} style \emph{CEK machine} to fit generalised +continuations. -The origins of the (semantic) resumption monad can be traced back to -at least \citet{Moggi90}, who described a monad for modelling the -interleaving semantics of \citeauthor{Milner75}'s \emph{calculus of - communicating systems}~\cite{Milner75}. +The CEK machine (CEK is an acronym for Control, Environment, +Kontinuation~\cite{FelleisenF86}) is an abstract machine with an +explicit environment, which models the idea that processor registers +name values as an environment associates names with values. Thus by +using the CEK formalism we depart from the substitution-based model of +computation used in the preceding chapters and move towards a more +`realistic' model of computation (realistic in the sense of emulating +how a computer executes a program). Another significant difference is +that in the CEK formalism evaluation contexts are no longer +syntactically intertwined with the source program. Instead evaluation +contexts are separately managed through the continuation of the CEK +machine. -The usage of \emph{resumption} in the name has a slightly different -meaning than the term `resumption' we have been using throughout this -chapter. We have used `resumption' to mean delimited continuation. In -the setting of the resumption monad it has a precise domain-theoretic -meaning. It is derived from \citeauthor{Plotkin76}'s domain of -resumptions, which in turn is derived from \citeauthor{Milner75}'s -domain of processes~\cite{Milner75,Plotkin76}. +% In this chapter we will demonstrate an application of generalised +% continuations (Section~\ref{sec:generalised-continuations}) to +% \emph{abstract machines}. An abstract machine is a model of +% computation that makes program control more apparent than standard +% reduction semantics. Abstract machines come in different styles and +% flavours, though, a common trait is that they closely model how an +% actual computer might go about executing a program, meaning they +% embody some high-level abstract models of main memory and the +% instruction fetch-execute cycle of processors~\cite{BryantO03}. -% \dhil{Briefly mention \citet{AtkeyJ15}} +\paragraph{Relation to prior work} The work in this chapter is based +on work in the following previously published papers. +% +\begin{enumerate}[i] + \item \bibentry{HillerstromL16} \label{en:ch-am-HL16} + \item \bibentry{HillerstromL18} \label{en:ch-am-HL18} + \item \bibentry{HillerstromLA20} \label{en:ch-am-HLA20} +\end{enumerate} +% +The particular presentation in this chapter is adapted from +item~\ref{en:ch-am-HLA20}. -\part{Implementation} -\label{p:implementation} +\section{Configurations with generalised continuations} +\label{sec:machine-configurations} +Syntactically, the CEK machine consists of three components: 1) the +control component, which focuses the term currently being evaluated; +2) the environment component, which maps free variables to machine +values, and 3) the continuation component, which describes what to +evaluate next (some literature uses the term `control string' in lieu +of continuation to disambiguate it from programmatic continuations in +the source language). +% +Intuitively, the continuation component captures the idea of call +stack from actual programming language implementations. -\chapter{Continuation-passing style} -\label{ch:cps} +The abstract machine is formally defined in terms of configurations. A +configuration $\cek{M \mid \env \mid \shk \circ \shk'} \in \MConfCat$ +is a triple consisting of a computation term $M \in \CompCat$, an +environment $\env \in \MEnvCat$, and a pair of generalised +continuations $\kappa,\kappa' \in \MGContCat$. +% +The complete abstract machine syntax is given in +Figure~\ref{fig:abstract-machine-syntax-gencont}. +% +The control and environment components are completely standard as they +are similar to the components in \citeauthor{FelleisenF86}'s original +CEK machine modulo the syntax of the source language. +% +However, the structure of the continuation component is new. This +component comprises two generalised continuations, where the latter +continuation $\kappa'$ is an entirely administrative object that +materialises only during operation invocations as it is used to +construct the reified segment of the continuation up to an appropriate +enclosing handler. For the most part $\kappa'$ is empty, therefore we +will write $\cek{M \mid \env \mid \shk}$ as syntactic sugar for +$\cek{M \mid \env \mid \shk \circ []}$ where $[]$ is the empty +continuation (an alternative is to syntactically differentiate between +regular and administrative configurations by having both three-place +and four-place configurations as for example as \citet{BiernackaBD03} +do). +% -Continuation-passing style (CPS) is a \emph{canonical} program -notation that makes every facet of control flow and data flow -explicit. In CPS every function takes an additional function-argument -called the \emph{continuation}, which represents the next computation -in evaluation position. CPS is canonical in the sense that it is -definable in pure $\lambda$-calculus without any further -primitives. As an informal illustration of CPS consider again the -ever-green factorial function from Section~\ref{sec:tracking-div}. +An environment is either empty, written $\emptyset$, or an extension +of some other environment $\env$, written $\env[x \mapsto v]$, where +$x$ is the name of a variable and $v$ is a machine value. % -\[ - \bl - \dec{fac} : \Int \to \Int\\ - \dec{fac} \defas \lambda n. - \ba[t]{@{}l} - \Let\;isz \revto n = 0\;\In\\ - \If\;isz\;\Then\; \Return\;1\\ - \Else\;\ba[t]{@{~}l} - \Let\; n' \revto n - 1 \;\In\\ - \Let\; m \revto \dec{fac}~n' \;\In\\ - \Let\;res \revto n * m \;\In\\ - \Return\;res - \ea - \ea - \el -\] +The machine values consist of function closures, recursive function +closures, type function closures, records, variants, and reified +continuations. The three abstraction forms are paired with an +environment that binds the free variables in the their bodies. The +records and variants are transliterated from the value forms of the +source calculi. Figure~\ref{fig:abstract-machine-val-interp} defines +the value interpretation function, which turns any source language +value into a corresponding machine value. % -The above implementation of the function $\dec{fac}$ is given in -direct-style fine-grain call-by-value. In CPS notation the -implementation of this function changes as follows. +A continuation $\shk$ is a stack of generalised continuation frames +$[\shf_1, \dots, \shf_n]$. As in +Section~\ref{sec:generalised-continuations} each continuation frame +$\shf = (\slk, \chi)$ consists of a pure continuation $\slk$, +corresponding to a sequence of let bindings, interpreted under some +handler, which in this context is represented by the handler closure +$\chi$. +% +A pure continuation is a stack of pure frames. A pure frame +$(\env, x, N)$ closes a let-binding $\Let \;x=[~] \;\In\;N$ over +environment $\env$. The pure continuation structure is similar to the +continuation structure of \citeauthor{FelleisenF86}'s original CEK +machine. +% +There are three kinds of handler closures, one for each kind of +handler. A deep handler closure is a pair $(\env, H)$ which closes a +deep handler definition $H$ over environment $\env$. Similarly, a +shallow handler closure $(\env, H^\dagger)$ closes a shallow handler +definition over environment $\env$. Finally, a parameterised handler +closure $(\env, (q.\,H))$ closes a parameterised handler definition +over environment $\env$. As a syntactic shorthand we write $H^\depth$ +to range over deep, shallow, and parameterised handler +definitions. Sometimes $H^\depth$ will range over just two kinds of +handler definitions; it will be clear from the context which handler +definition is omitted. +% +We extend the clause projection notation to handler closures and +generalised continuation frames, i.e. % \[ - \bl - \dec{fac}_{\dec{cps}} : \Int \to (\Int \to \alpha) \to \alpha\\ - \dec{fac}_{\dec{cps}} \defas \lambda n.\lambda k. - (=_{\dec{cps}})~n~0~ - (\lambda isz. - \ba[t]{@{~}l} - \If\;isz\;\Then\; k~1\\ - \Else\; - (-_{\dec{cps}})~n~\bl 1\\ - (\lambda n'. - \dec{fac}_{\dec{cps}}~\bl n'\\ - (\lambda m. (*_{\dec{cps}})~n~\bl m\\ - (\lambda res. k~res)))) - \el - \el - \el - \ea + \ba{@{~}l@{~}c@{~}l@{~}c@{~}l@{~}l} + \theta^{\mathrm{ret}} &\defas& (\sigma, \chi^{\mathrm{ret}}) &\defas& \hret, &\quad \text{where } \chi = (\env, H^\depth)\\ + \theta^{\ell} &\defas& (\sigma, \chi^{\ell}) &\defas& \hell, &\quad \text{where } \chi = (\env, H^\depth) \el \] % -There are several worthwhile observations to make about the -differences between the two implementations $\dec{fac}$ and -$\dec{fac}_{\dec{cps}}$. +Values are annotated with types where appropriate to facilitate type +reconstruction in order to make the results of +Section~\ref{subsec:machine-correctness} easier to state. % -Firstly note that their type signatures differ. The CPS version has an -additional formal parameter of type $\Int \to \alpha$ which is the -continuation. By convention the continuation parameter is named $k$ in -the implementation. As usual, the continuation represents the -remainder of computation. In this specific instance $k$ represents the -undelimited current continuation of an application of -$\dec{fac}_{\dec{cps}}$. Given a value of type $\Int$, the -continuation produces a result of type $\alpha$, which is the -\emph{answer type} of the entire program. Thus applying -$\dec{fac}_{\dec{cps}}~3$ to the identity function ($\lambda x.x$) -yields $6 : \Int$, whilst applying it to the predicate -$\lambda x. x > 2$ yields $\True : \Bool$. -% or put differently: it determines what to do with the result -% returned by an invocation of $\dec{fac}_{\dec{cps}}$. % - -Secondly note that every $\Let$-binding in $\dec{fac}$ has become a -function application in $\dec{fac}_{\dec{cps}}$. The binding sequence -in the $\Else$-branch has been turned into a series of nested function -applications. The functions $=_{\dec{cps}}$, $-_{\dec{cps}}$, and -$*_{\dec{cps}}$ denote the CPS versions of equality testing, -subtraction, and multiplication respectively. +\begin{figure}[t] +\flushleft +\begin{syntax} +\slab{Configurations} & \conf \in \MConfCat &::= & \cek{M \mid \env \mid \shk \circ \shk'} \\ +\slab{Value\textrm{ }environments} &\env \in \MEnvCat &::= & \emptyset \mid \env[x \mapsto v] \\ +\slab{Values} &v, w \in \MValCat &::= & (\env, \lambda x^A . M) \mid (\env, \Rec^{A \to C}\,x.M)\\ + & &\mid& (\env, \Lambda \alpha^K . M) \\ + & &\mid& \Record{} \mid \Record{\ell = v; w} \mid (\ell\, v)^R \\ + & &\mid& \shk^A \mid (\shk, \slk)^A \medskip\\ +\slab{Continuations} &\shk \in \MGContCat &::= & \nil \mid \shf \cons \shk \\ +\slab{Continuation\textrm{ }frames} &\shf \in \MGFrameCat &::= & (\slk, \chi) \\ +\slab{Pure\textrm{ }continuations} &\slk \in \MPContCat &::= & \nil \mid \slf \cons \slk \\ +\slab{Pure\textrm{ }continuation\textrm{ }frames} &\slf \in \MPFrameCat &::= & (\env, x, N) \\ +\slab{Handler\textrm{ }closures} &\chi \in \MHCloCat &::= & (\env, H) \mid (\env, H^\dagger) \mid (\env, (q.\,H)) \medskip \\ +\end{syntax} + +\caption{Abstract machine syntax.} +\label{fig:abstract-machine-syntax-gencont} +\end{figure} % -For clarity, I have meticulously written each continuation function on -a newline. For instance, the continuation of the -$-_{\dec{cps}}$-application is another application of -$\dec{fac}_{\dec{cps}}$, whose continuation is an application of -$*_{\dec{cps}}$, and its continuation is an application of the current -continuation, $k$, of $\dec{fac}_{\dec{cps}}$. +\begin{figure} +\[ +\bl +\multicolumn{1}{c}{\val{-} : \ValCat \times \MEnvCat \to \MValCat}\\[1ex] +\ba[t]{@{}r@{~}c@{~}l@{}} +\val{x}{\env} &\defas& \env(x) \\ +\val{\lambda x^A.M}{\env} &\defas& (\env, \lambda x^A.M) \\ +\val{\Rec\,g^{A \to C}\,x.M}{\env} &\defas& (\env, \Rec\,g^{A \to C}\,x.M) \\ +\val{\Lambda \alpha^K.M}{\env} &\defas& (\env, \Lambda \alpha^K.M) \\ +\ea +\qquad +\ba[t]{@{}r@{~}c@{~}l@{}} +\val{\Record{}}{\env} &\defas& \Record{} \\ +\val{\Record{\ell = V; W}}{\env} &\defas& \Record{\ell = \val{V}{\env}; \val{W}{\env}} \\ +\val{(\ell\, V)^R}{\env} &\defas& (\ell\, \val{V}{\env})^R \\ +\ea +\el +\] + \caption{Value interpretation definition.} + \label{fig:abstract-machine-val-interp} +\end{figure} % -Each $\Return$-computation has been turned into an application of the -current continuation $k$. In the $\Then$-branch the continuation -applied to $1$, whilst in the $\Else$-branch the continuation is -applied to the result obtained by multiplying $n$ and $m$. +\section{Generalised continuation-based machine semantics} +\label{sec:machine-transitions} % -Thirdly note that every function application occurs in tail position -(recall Definition~\ref{def:tail-comp}). This is a characteristic -property of CPS transforms that make them feasible as a practical -implementation strategy, since programs in CPS notation require only a -constant amount of stack space to run, namely, a single activation -frame~\cite{Appel92}. Although, the pervasiveness of closures in CPS -means that CPS programs make heavy use of the heap for closure -allocation. -% -Some care must be taken when CPS transforming a program as if done -naïvely the image may be inflated with extraneous -terms~\cite{DanvyN05}. For example in $\dec{fac}_{\dec{cps}}$ the -continuation term $(\lambda res.k~res)$ is redundant as it is simply -an eta expansion of the continuation $k$. A more optimal transform -would simply pass $k$. Extraneous terms can severely impact the -runtime performance of a CPS program. A smart CPS transform recognises -and eliminates extraneous terms at translation -time~\cite{DanvyN03}. Extraneous terms come in various disguises as we -shall see later in this chapter. +\begin{figure}[p] +\rotatebox{90}{ +\begin{minipage}{0.99\textheight}% +\[ +\bl +%\multicolumn{1}{c}{\stepsto \subseteq \MConfCat \times \MConfCat}\\[1ex] +\ba{@{}l@{\quad}r@{~}c@{~}l@{~~}l@{}} +% \mlab{Init} & \multicolumn{3}{@{}c@{}}{M \stepsto \cek{M \mid \emptyset \mid [(\nil, (\emptyset, \{\Return\;x \mapsto \Return\;x\}))]}} \\[1ex] +% App +&&\multicolumn{2}{@{}l}{\stepsto\, \subseteq\! \MConfCat \times \MConfCat}\\ +\mlab{App} & \cek{ V\;W \mid \env \mid \shk} + &\stepsto& \cek{ M \mid \env'[x \mapsto \val{W}{\env}] \mid \shk}, + &\text{if }\val{V}{\env} = (\env', \lambda x^A.M) \\ -The complete exposure of the control flow makes CPS a good fit for -implementing control operators such as effect handlers. It is an -established intermediate representation used by compilers, providing -it with merits as a practical compilation -target~\cite{Appel92,Kennedy07}. +\mlab{AppRec} & \cek{ V\;W \mid \env \mid \shk} + &\stepsto& \cek{ M \mid \env'[g \mapsto (\env', \Rec\,g^{A \to C}\,x.M), x \mapsto \val{W}{\env}] \mid \shk}, + &\text{if }\val{V}{\env} = (\env', \Rec\,g^{A \to C}\,x.M) \\ -The purpose of this chapter is to use the CPS formalism to develop a -universal implementation strategy for deep, shallow, and parameterised -effect handlers. Section~\ref{sec:target-cps} defines a suitable -target calculus $\UCalc$ for CPS transformed -programs. Section~\ref{sec:cps-cbv} demonstrates how to CPS transform -$\BCalc$-programs to $\UCalc$-programs. In Section~\ref{sec:fo-cps} -develop a CPS transform for deep handlers through step-wise refinement -of the initial CPS transform for $\BCalc$. The resulting CPS transform -is adapted in Section~\ref{sec:cps-shallow} to support for shallow -handlers. As a by-product we develop the notion of \emph{generalised - continuation}, which provides a versatile abstraction for -implementing effect handlers. We use generalised continuations to -implement parameterised handlers in Section~\ref{sec:cps-param}. -% +% TyApp +\mlab{AppType} & \cek{ V\,T \mid \env \mid \shk} + &\stepsto& \cek{ M[T/\alpha] \mid \env' \mid \shk}, + &\text{if }\val{V}{\env} = (\env', \Lambda \alpha^K . \, M) \\ -% -%\dhil{The focus of the introduction should arguably not be to explain CPS.} -%\dhil{Justify CPS as an implementation technique} -%\dhil{Give a side-by-side reduction example of $\dec{fac}$ and $\dec{fac}_{\dec{cps}}$.} -% \dhil{Define desirable properties of a CPS translation: properly tail-recursive, no static administrative redexes} -% -% \begin{definition}[Properly tail-recursive~\cite{Danvy06}] -% % -% A CPS translation $\cps{-}$ is properly tail-recursive if the -% continuation of every CPS transformed tail call $\cps{V\,W}$ within -% $\cps{\lambda x.M}$ is $k$, where -% \begin{equations} -% \cps{\lambda x.M} &=& \lambda x.\lambda k.\cps{M}\\ -% \cps{V\,W} &=& \cps{V}\,\cps{W}\,k. -% \end{equations} -% \end{definition} +% Deep resumption application +\mlab{Resume} & \cek{ V\;W \mid \env \mid \shk} + &\stepsto& \cek{ \Return \; W \mid \env \mid \shk' \concat \shk}, + &\text{if }\val{V}{\env} = (\shk')^A \\ -% \[ -% \ba{@{~}l@{~}l} -% \pcps{(\lambda x.(\lambda y.\Return\;y)\,x)\,\Unit} &= (\lambda x.(\lambda y.\lambda k.k\,y)\,x)\,\Unit\,(\lambda x.x)\\ -% &\reducesto ((\lambda y.\lambda k.k\,y)\,\Unit)\,(\lambda x.x)\\ -% &\reducesto (\lambda k.k\,\Unit)\,(\lambda x.x)\\ -% &\reducesto (\lambda x.x)\,\Unit\\ -% &\reducesto \Unit -% \ea -% \] +% Shallow resumption application +\mlab{Resume^\dagger} & \cek{ V\,W \mid \env \mid (\slk, \chi) \cons \shk} + &\stepsto& + \cek{\Return\; W \mid \env \mid \shk' \concat ((\slk' \concat \slk, \chi) \cons \shk)}, + &\text{if } \val{V}{\env} = (\shk', \slk')^A \\ -\paragraph{Relation to prior work} This chapter is based on the -following work. -% -\begin{enumerate}[i] - \item \bibentry{HillerstromLAS17}\label{en:ch-cps-HLAS17} - \item \bibentry{HillerstromL18} \label{en:ch-cps-HL18} - \item \bibentry{HillerstromLA20} \label{en:ch-cps-HLA20} -\end{enumerate} +% Deep resumption application +\mlab{Resume^\param} & \cek{ V\,\Record{W;W'} \mid \env \mid \shk} + &\stepsto& \cek{ \Return \; W \mid \env \mid \shk' \concat [(\sigma,(\env'[q \mapsto \val{W'}\env],q.\,H))] \concat \shk},&\\ + &&&\quad\text{if }\val{V}{\env} = \shk' \concat [(\sigma,(\env',q.\,H))])^A \\ % -Section~\ref{sec:higher-order-uncurried-deep-handlers-cps} is -based on item \ref{en:ch-cps-HLAS17}, however, I have adapted it to -follow the notation and style of item \ref{en:ch-cps-HLA20}. +\mlab{Split} & \cek{ \Let \; \Record{\ell = x;y} = V \; \In \; N \mid \env \mid \shk} + &\stepsto& \cek{ N \mid \env[x \mapsto v, y \mapsto w] \mid \shk}, + &\text{if }\val{V}{\env} = \Record{\ell=v; w} \\ -\section{Initial target calculus} -\label{sec:target-cps} -% -\begin{figure} - \flushleft - \textbf{Syntax} - \begin{syntax} - \slab{Values} &U, V, W \in \UValCat &::= & x \mid \lambda x.M \mid % \Rec\,g\,x.M - \mid \Record{} \mid \Record{V, W} \mid \ell - \smallskip \\ - \slab{Computations} &M,N \in \UCompCat &::= & V \mid M\,W \mid \Let\; \Record{x,y} = V \; \In \; N\\ - & &\mid& \Case\; V\, \{\ell \mapsto M; y \mapsto N\} \mid \Absurd\,V - \smallskip \\ - \slab{Evaluation contexts} &\EC \in \UEvalCat &::= & [~] \mid \EC\;W \\ - \end{syntax} +% Case +\mlab{Case} & \cek{ \Case\; V\, \{ \ell~x \mapsto M; y \mapsto N\} \mid \env \mid \shk} + &\stepsto& \left\{\ba{@{}l@{}} + \cek{ M \mid \env[x \mapsto v] \mid \shk}, \\ + \cek{ N \mid \env[y \mapsto \ell'\, v] \mid \shk}, \\ + \ea \right. + & + \ba{@{}l@{}} + \text{if }\val{V}{\env} = \ell\, v \\ + \text{if }\val{V}{\env} = \ell'\, v \text{ and } \ell \neq \ell' \\ + \ea \\ - \textbf{Reductions} - \begin{reductions} - \usemlab{App} & (\lambda x . \, M) V &\reducesto& M[V/x] \\ - % \usemlab{Rec} & (\Rec\,g\,x.M) V &\reducesto& M[\Rec\,g\,x.M/g,V/x]\\ - \usemlab{Split} & \Let \; \Record{x,y} = \Record{V,W} \; \In \; N &\reducesto& N[V/x,W/y] \\ - \usemlab{Case_1} & - \Case \; \ell \; \{ \ell \mapsto M; y \mapsto N\} &\reducesto& M \\ - \usemlab{Case_2} & - \Case \; \ell \; \{ \ell' \mapsto M; y \mapsto N\} &\reducesto& N[\ell/y], \hfill\quad \text{if } \ell \neq \ell' \\ - \usemlab{Lift} & - \EC[M] &\reducesto& \EC[N], \hfill \text{if } M \reducesto N \\ - \end{reductions} +% Let - eval M +\mlab{Let} & \cek{ \Let \; x \revto M \; \In \; N \mid \env \mid (\slk, \chi) \cons \shk} + &\stepsto& \cek{ M \mid \env \mid ((\env,x,N) \cons \slk, \chi) \cons \shk} \\ - \textbf{Syntactic sugar} -\[ - \begin{eqs} - \Let\;x=V\;\In\;N &\equiv & N[V/x]\\ - \ell \; V & \equiv & \Record{\ell; V}\\ - \Record{} & \equiv & \ell_{\Record{}} \\ - \Record{\ell = V; W} & \equiv & \Record{\ell, \Record{V, W}}\\ - \nil &\equiv & \ell_{\nil} \\ - V \cons W & \equiv & \Record{\ell_{\cons}, \Record{V, W}}\\ - \Case\;V\;\{\ell\;x \mapsto M; y \mapsto N \} &\equiv& - \ba[t]{@{~}l} - \Let\;y = V\;\In\; \Let\;\Record{z,x} = y\;\In \\ - \Case\; z\;\{ \ell \mapsto M; z' \mapsto N \} - \ea\\ - \Let\; \Record{\ell=x;y} = V\;\In\;N &\equiv& - \ba[t]{@{~}l} - \Let\; \Record{z,z'} = V\;\In\;\Let\; \Record{x,y} = z'\;\In \\ - \Case\;z\;\{\ell \mapsto N; z'' \mapsto \ell_\bot \} - \ea - \end{eqs} -\] +% Handle +\mlab{Handle^\depth} & \cek{ \Handle^\depth \, M \; \With \; H^\depth \mid \env \mid \shk} + &\stepsto& \cek{ M \mid \env \mid (\nil, (\env, H^\depth)) \cons \shk} \\ -\caption{Untyped target calculus for the CPS translations.} -\label{fig:cps-cbv-target} -\end{figure} -% -The syntax, semantics, and syntactic sugar for the target calculus -$\UCalc$ is given in Figure~\ref{fig:cps-cbv-target}. The calculus -largely amounts to an untyped variation of $\BCalc$, specifically -we retain the syntactic distinction between values ($V$) and -computations ($M$). -% -The values ($V$) comprise lambda abstractions ($\lambda x.M$), -% recursive functions ($\Rec\,g\,x.M$), -empty tuples ($\Record{}$), pairs ($\Record{V,W}$), and first-class -labels ($\ell$). -% -Computations ($M$) comprise values ($V$), applications ($M~V$), pair -elimination ($\Let\; \Record{x, y} = V \;\In\; N$), label elimination -($\Case\; V \;\{\ell \mapsto M; x \mapsto N\}$), and explicit marking -of unreachable code ($\Absurd$). A key difference from $\BCalc$ is -that the function position of an application is allowed to be a -computation (i.e., the application form is $M~W$ rather than -$V~W$). Later, when we refine the initial CPS translation we will be -able to rule out this relaxation. - -The reduction semantics follows the trend of the previous reduction -semantics in the sense that it is a small-step context-based reduction -semantics. Evaluation contexts comprise the empty context and function -application. +\mlab{Handle^\param} & \cek{ \Handle^\param \, M \; \With \; (q.\,H)(W) \mid \env \mid \shk} + &\stepsto& \cek{ M \mid \env \mid (\nil, (\env[q \mapsto \val{W}\env], H)) \cons \shk} \\ -To make the notation more lightweight, we define syntactic sugar for -variant values, record values, list values, let binding, variant -eliminators, and record eliminators. We use pattern matching syntax -for deconstructing variants, records, and lists. For desugaring -records, we assume a failure constant $\ell_\bot$ (e.g. a divergent -term) to cope with the case of pattern matching failure. -% \dhil{Most of the primitives are Church encodable. Discuss the value -% of treating them as primitive rather than syntactic sugar (target -% languages such as JavaScript has similar primitives).} +% Return - let binding +\mlab{PureCont} &\cek{ \Return \; V \mid \env \mid ((\env',x,N) \cons \slk, \chi) \cons \shk} + &\stepsto& \cek{ N \mid \env'[x \mapsto \val{V}{\env}] \mid (\slk, \chi) \cons \shk} \\ -\section{Transforming fine-grain call-by-value} -\label{sec:cps-cbv} +% Return - handler +\mlab{GenCont} & \cek{ \Return \; V \mid \env \mid (\nil, (\env',H^\delta)) \cons \shk} + &\stepsto& \cek{ M \mid \env'[x \mapsto \val{V}{\env}] \mid \shk}, + &\text{if } \hret = \{\Return\; x \mapsto M\} \\ -We start by giving a CPS translation of $\BCalc$ in -Figure~\ref{fig:cps-cbv}. Fine-grain call-by-value admits a -particularly simple CPS translation due to the separation of values -and computations. All constructs from the source language are -translated homomorphically into the target language $\UCalc$, except -for $\Return$ and $\Let$ (and type abstraction because the translation -performs type erasure). Lifting a value $V$ to a computation -$\Return~V$ is interpreted by passing the value to the current -continuation $k$. Sequencing computations with $\Let$ is translated by -applying the translation of $M$ to the translation of the continuation -$N$, which is ultimately applied to the current continuation $k$. In -addition, we explicitly $\eta$-expand the translation of a type -abstraction in order to ensure that value terms in the source calculus -translate to value terms in the target. +% Deep +\mlab{Do^\depth} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid ((\slk, (\env', H^\depth)) \cons \shk) \circ \shk'} + &\stepsto& \cek{M \mid \env'[p \mapsto \val{V}{\env}, + r \mapsto (\shk' \concat [(\slk, (\env', H^\depth))])^B] \mid \shk},\\ +&&&\quad\text{if } \ell : A \to B \in E \text{ and } \hell = \{\OpCase{\ell}{p}{r} \mapsto M\} \\ -\begin{figure} -\flushleft -\textbf{Values} \\ -\[ -\bl +% Shallow +\mlab{Do^\dagger} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid ((\slk, (\env', H)^\dagger) \cons \shk) \circ \shk'} &\stepsto& \cek{M \mid \env'[p \mapsto \val{V}{\env}, + r \mapsto (\shk', \slk)^B] \mid \shk},\\ + &&&\quad\text{if } \ell : A \to B \in E \text{ and } \hell = \{\OpCase{\ell}{p}{r} \mapsto M\} \\ -\begin{eqs} - \cps{-} &:& \ValCat \to \UValCat\\ -\cps{x} &=& x \\ -\cps{\lambda x.M} &=& \lambda x.\cps{M} \\ -\cps{\Lambda \alpha.M} &=& \lambda k.\cps{M}~k \\ -% \cps{\Rec\,g\,x.M} &=& \Rec\,g\,x.\cps{M}\\ -\cps{\Record{}} &=& \Record{} \\ -\cps{\Record{\ell = V; W}} &=& \Record{\ell = \cps{V}; \cps{W}} \\ -\cps{\ell~V} &=& \ell~\cps{V} \\ -\end{eqs} +% Forward +\mlab{Forward} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid (\theta \cons \shk) \circ \shk'} + &\stepsto& \cek{ (\Do \; \ell \; V)^E \mid \env \mid \shk \circ (\shk' \concat [\theta])}, + &\text{if } \gell = \emptyset +\ea \el \] -\textbf{Computations} +\caption{Abstract machine transitions.} +\label{fig:abstract-machine-semantics-gencont} +\end{minipage} +} +\end{figure} +% +\begin{figure} \[ \bl -\begin{eqs} -\cps{-} &:& \CompCat \to \UCompCat\\ -\cps{V\,W} &=& \cps{V}\,\cps{W} \\ -\cps{V\,T} &=& \cps{V} \\ -\cps{\Let\; \Record{\ell=x;y} = V \; \In \; N} &=& \Let\; \Record{\ell=x;y} = \cps{V} \; \In \; \cps{N} \\ -\cps{\Case~V~\{\ell~x \mapsto M; y \mapsto N\}} &=& - \Case~\cps{V}~\{\ell~x \mapsto \cps{M}; y \mapsto \cps{N}\} \\ -\cps{\Absurd~V} &=& \Absurd~\cps{V} \\ -\cps{\Return~V} &=& \lambda k.k\,\cps{V} \\ -\cps{\Let~x \revto M~\In~N} &=& \lambda k.\cps{M}(\lambda x.\cps{N}\,k) \\ -\end{eqs} +\ba{@{~}l@{\quad}l@{~}l} + \multicolumn{2}{l}{\textbf{Initial continuation}}\\ + \multicolumn{3}{l}{\quad\shk_0 \defas [(\nil, (\emptyset, \{\Return\;x \mapsto x\}))]} +\medskip\\ +% + \textbf{Initialisation} & \stepsto \subseteq \CompCat \times \MConfCat\\ + \quad\mlab{Init} & \multicolumn{2}{l}{\quad M \stepsto \cek{M \mid \emptyset \mid \shk_0}} +\medskip\\ +% + \textbf{Finalisation} & \stepsto \subseteq \MConfCat \times \ValCat\\ + \quad\mlab{Halt} & \multicolumn{2}{l}{\quad\cek{\Return\;V \mid \env \mid \nil} \stepsto \val{V}\env} +\ea \el \] -\caption{First-order CPS translation of $\BCalc$.} -\label{fig:cps-cbv} +\caption{Machine initialisation and finalisation.} +\label{fig:machine-init-final} \end{figure} - -\section{Transforming deep effect handlers} -\label{sec:fo-cps} - -The translation of a computation term by the basic CPS translation in -Section~\ref{sec:cps-cbv} takes a single continuation parameter that -represents the context. -% -In the presence of effect handlers in the source language, it becomes -necessary to keep track of two kinds of contexts in which each -computation executes: a \emph{pure context} that tracks the state of -pure computation in the scope of the current handler, and an -\emph{effect context} that describes how to handle operations in the -scope of the current handler. % -Correspondingly, we have both \emph{pure continuations} ($k$) and -\emph{effect continuations} ($h$). +The semantics of the abstract machine is defined in terms of a +transition relation $\stepsto \subseteq \MConfCat \times \MConfCat$ on +machine configurations. The definition of the transition relation is +given in Figure~\ref{fig:abstract-machine-semantics-gencont}. % -As handlers can be nested, each computation executes in the context of -a \emph{stack} of pairs of pure and effect continuations. +A fair amount of the transition rules involve manipulating the +continuation. We adopt the same stack notation conventions used in the +CPS translation with generalised continuations +(Section~\ref{sec:cps-gen-conts}) and write $\nil$ for an empty stack, +$x \cons s$ for the result of pushing $x$ on top of stack $s$, and +$s \concat s'$ for the concatenation of stack $s$ on top of $s'$. We +use pattern matching to deconstruct stacks. -On entry into a handler, the pure continuation is initialised to a -representation of the return clause and the effect continuation to a -representation of the operation clauses. As pure computation proceeds, -the pure continuation may grow, for example when executing a -$\Let$. If an operation is encountered then the effect continuation is -invoked. -% -The current continuation pair ($k$, $h$) is packaged up as a -\emph{resumption} and passed to the current handler along with the -operation and its argument. The effect continuation then either -handles the operation, invoking the resumption as appropriate, or -forwards the operation to an outer handler. In the latter case, the -resumption is modified to ensure that the context of the original -operation invocation can be reinstated upon invocation of the -resumption. +The first eight rules enact the elimination of values. % +The first three rules concern closures (\mlab{App}, \mlab{AppRec}, +\mlab{AppType}); they all essentially work the same. For example, the +\mlab{App} uses the value interpretation function $\val{-}$ to +interpret the abstractor $V$ in the machine environment $\env$ to +obtain the closure. The body $M$ of closure gets put into the control +component. Before the closure environment $\env'$ gets installed as +the new machine environment, it gets extended with a binding of the +formal parameter of the abstraction to the interpretation of argument +$W$ in the previous environment $\env$. The rule \mlab{AppRec} behaves +the almost the same, the only difference is that it binds the variable +$g$ to the recursive closure in the environment. The rule +\mlab{AppType} does not extend the environment, instead the type is +substituted directly into the body. In either rule continuation +component remains untouched. -\subsection{Curried translation} -\label{sec:first-order-curried-cps} - -We first consider a curried CPS translation that extends the -translation of Figure~\ref{fig:cps-cbv}. The extension to operations -and handlers is localised to the additional features because currying -conveniently lets us get away with a shift in interpretation: rather -than accepting a single continuation, translated computation terms now -accept an arbitrary even number of arguments representing the stack of -pure and effect continuations. Thus, we can conservatively extend the -translation in Figure~\ref{fig:cps-cbv} to cover $\HCalc$, where we -imagine there being some number of extra continuation arguments that -have been $\eta$-reduced. The translation of operations and handlers -is as follows. +The resumption rules (\mlab{Resume}, \mlab{Resume^\dagger}, +\mlab{Resume^\param}), however, manipulate the continuation component +as they implement the context restorative behaviour of deep, shallow, +and parameterised resumption application respectively. The +\mlab{Resume} rule handles deep resumption invocations. A deep +resumption is syntactically a generalised continuation, and therefore +it can be directly composed with the machine continuation. Following a +deep resumption invocation the argument gets placed in the control +component, whilst the reified continuation $\kappa'$ representing the +resumptions gets concatenated with the machine continuation $\kappa$ +in order to restore the captured context. % -\begin{equations} -\cps{-} &:& \CompCat \to \UCompCat\\ -\cps{\Do\;\ell\;V} &\defas& \lambda k.\lambda h.h~\Record{\ell,\Record{\cps{V}, \lambda x.k~x~h}} \\ -\cps{\Handle \; M \; \With \; H} &\defas& \cps{M}~\cps{\hret}~\cps{\hops} \medskip\\ -\cps{-} &:& \HandlerCat \to \UCompCat\\ -\cps{\{ \Return \; x \mapsto N \}} &\defas& \lambda x . \lambda h . \cps{N} \\ -\cps{\{ \ell~p~r \mapsto N_\ell \}_{\ell \in \mathcal{L}}} -&\defas& -\lambda \Record{z,\Record{p,r}}. \Case~z~ - \{ (\ell \mapsto \cps{N_\ell})_{\ell \in \mathcal{L}}; y \mapsto \hforward(y,p,r) \} \\ -\hforward(y,p,r) &\defas& \lambda k. \lambda h. h\,\Record{y,\Record{p, \lambda x.\,r\,x\,k\,h}} -\end{equations} +The rule \mlab{Resume^\dagger} realises shallow resumption +invocations. Syntactically, a shallow resumption consists of a pair +whose first component is a dangling pure continuation $\sigma'$, which +is leftover after removal of its nearest enclosing handler, and the +second component contains a reified generalised continuation +$\kappa'$. The dangling pure continuation gets adopted by the top-most +handler $\chi$ as $\sigma'$ gets appended onto the pure continuation +$\sigma$ running under $\chi$. The resulting continuation gets +composed with the reified continuation $\kappa'$. % -The translation of $\Do\;\ell\;V$ abstracts over the current pure -($k$) and effect ($h$) continuations passing an encoding of the -operation into the latter. The operation is encoded as a triple -consisting of the name $\ell$, parameter $\cps{V}$, and resumption -$\lambda x.k~x~h$, which passes the same effect continuation $h$ to -ensure deep handler semantics. +The rule \mlab{Resume^\param} implements the behaviour of +parameterised resumption invocations. Syntactically, a parameterised +resumption invocation is generalised continuation just like an +ordinary deep resumption. The primary difference between \mlab{Resume} +and \mlab{Resume^\param} is that in the latter rule the top-most frame +of $\kappa'$ contains a parameterised handler definition, whose +parameter $q$ needs to be updated following an invocation. The handler +closure environment $\env'$ gets extended by a mapping of $q$ to the +interpretation of the argument $W'$ such that this value of $q$ is +available during the next activation of the handler. Following the +environment update the reified continuation gets reconstructed and +appended onto the current machine continuation. -The translation of $\Handle~M~\With~H$ invokes the translation of $M$ -with new pure and effect continuations for the return and operation -clauses of $H$. +The rules $\mlab{Split}$ and $\mlab{Case}$ concern record destructing +and variant scrutinising, respectively. Record destructing binds both +the variable $x$ to the value $v$ at label $\ell$ in the record $V$ +and the variable $y$ to the tail of the record in current environment +$\env$. % -The translation of a return clause is a term which garbage collects -the current effect continuation $h$. +Case splitting dispatches to the first branch with the variable $x$ +bound to the variant payload in the environment if the label of the +variant $V$ matches $\ell$, otherwise it dispatches to the second +branch with the variable $y$ bound to the interpretation of $V$ in the +environment. + +The rules \mlab{Let}, \mlab{Handle^\depth}, and \mlab{Handle^\param} +augment the current continuation with let bindings and handlers. The +rule \mlab{Let} puts the computation $M$ of a let expression into the +control component and extends the current pure continuation with the +closure of the (source) continuation of the let expression. % -The translation of a set of operation clauses is a function which -dispatches on encoded operations, and in the default case forwards to -an outer handler. +The \mlab{Handle^\depth} rule covers both ordinary deep and shallow +handler installation. The computation $M$ is placed in the control +component, whilst the continuation is extended by an additional +generalised frame with an empty pure continuation and the closure of +the handler $H$. % -In the forwarding case, the resumption is extended by the parent -continuation pair to ensure that an eventual invocation of the -resumption reinstates the handler stack. +The rule \mlab{Handle^\param} covers installation of parameterised +handlers. The only difference here is that the parameter $q$ is +initialised to the interpretation of $W$ in handler environment +$\env'$. -The translation of complete programs feeds the translated term the -identity pure continuation (which discards its handler argument), and -an effect continuation that is never intended to be called. +The current continuation gets shrunk by rules \mlab{PureCont} and +\mlab{GenCont}. If the current pure continuation is nonempty then the +rule \mlab{PureCont} binds a returned value, otherwise the rule +\mlab{GenCont} invokes the return clause of a handler if the pure +continuation is empty. + +The forwarding continuation is used by rules \mlab{Do^\depth}, +\mlab{Do^\dagger}, and \mlab{Forward}. The rule \mlab{Do^\depth} +covers operation invocations under deep and parameterised handlers. If +the top-most handler handles the operation $\ell$, then corresponding +clause computation $M$ gets placed in the control component, and the +handler environment $\env'$ is installed with bindings of the +operation payload and the resumption. The resumption is the forwarding +continuation $\kappa'$ extended by the current generalised +continuation frame. % -\begin{equations} -\pcps{-} &:& \CompCat \to \UCompCat\\ -\pcps{M} &\defas& \cps{M}~(\lambda x.\lambda h.x)~(\lambda \Record{z,\_}.\Absurd~z) \\ -\end{equations} +The rule \mlab{Do^\dagger} is much like \mlab{Do^\depth}, except it +constructs a shallow resumption, discarding the current handler but +keeping the current pure continuation. % -Conceptually, this translation encloses the translated program in a -top-level handler with an empty collection of operation clauses and an -identity return clause. - -A pleasing property of this particular CPS translation is that it is a -conservative extension to the CPS translation for $\BCalc$. Alas, this -translation also suffers two displeasing properties which makes it -unviable in practice. - -\begin{enumerate} -\item The image of the translation is not \emph{properly - tail-recursive}~\citep{Danvy06,DanvyF92,Steele78}, meaning not - every function application occur in tail position in the image, and - thus the image is not stackless. Consequently, the translation - cannot readily be used as the basis for an implementation. This - deficiency is essentially due to the curried representation of the - continuation stack. +The rule \mlab{Forward} appends the current continuation +frame onto the end of the forwarding continuation. - \item The image of the translation yields static administrative - redexes, i.e. redexes that could be reduced statically. This is a - classic problem with CPS translations. This problem can be dealt - with by introducing a second pass to clean up the - image~\cite{Plotkin75}. By clever means the clean up pass and the - translation pass can be fused together to make an one-pass - translation~\cite{DanvyF92,DanvyN03}. -\end{enumerate} +As a slight abuse of notation, we overload $\stepsto$ to inject +computation terms into an initial machine configuration as well as +projecting values. Figure~\ref{fig:machine-init-final} depicts the +structure of the initial machine continuation and two additional +pseudo transitions. The initial continuation consists of a single +generalised continuation frame with an empty pure continuation running +under an identity handler. The \mlab{Init} rule provides a canonical +way to map a computation term onto a configuration, whilst \mlab{Halt} +provides a way to extract the final value of some computation from a +configuration. -The following minimal example readily illustrates both issues. +\subsection{Putting the machine into action} +\newcommand{\chiid}{\ensuremath{\chi_{\text{id}}}} +\newcommand{\kappaid}{\ensuremath{\kappa_{\text{id}}}} +\newcommand{\incr}{\dec{incr}} +\newcommand{\Incr}{\dec{Incr}} +\newcommand{\prodf}{\dec{prod}} +\newcommand{\consf}{\dec{cons}} % -\begin{align*} -\pcps{\Return\;\Record{}} - = & (\lambda k.k\,\Record{})\,(\lambda x.\lambda h.x)\,(\lambda \Record{z,\_}.\Absurd\,z) \\ - \reducesto& ((\lambda x.\lambda h.x)\,\Record{})\,(\lambda \Record{z,\_}.\Absurd\,z) \numberthis\label{eq:cps-admin-reduct-1}\\ - \reducesto& (\lambda h.\Record{})\,(\lambda \Record{z,\_}.\Absurd\,z) \numberthis\label{eq:cps-admin-reduct-2}\\ - \reducesto& \Record{} -\end{align*} +To gain a better understanding of how the abstract machine concretely +transitions between configurations we will consider a small program +consisting of a deep, parameterised, and shallow handler. % -The second and third reductions simulate handling $\Return\;\Record{}$ -at the top level. The second reduction partially applies the curried -function term $\lambda x.\lambda h.x$ to $\Record{}$, which must -return a value such that the third reduction can be -applied. Consequently, evaluation is not tail-recursive. +For the deep handler we will use the $\nondet$ handler from +Section~\ref{sec:tiny-unix-time} which handles invocations of the +operation $\Fork : \UnitType \opto \Bool$; it is reproduced here in +fine-grain call-by-value syntax. % -The lack of tail-recursion is also apparent in our relaxation of -fine-grain call-by-value in Figure~\ref{fig:cps-cbv-target} as the -function position of an application can be a computation. +\[ + \bl + % H_\nondet : \alpha \eff \{\Choose : \UnitType \opto \Bool\} \Harrow \List~\alpha\\ + % H_\nondet \defas + % \ba[t]{@{~}l@{~}c@{~}l} + % \Return\;x &\mapsto& [x]\\ + % \OpCase{\Choose}{\Unit}{resume} &\mapsto& resume~\True \concat resume~\False + % \ea \smallskip\\ + \nondet : (\UnitType \to \alpha \eff \{\Fork : \UnitType \opto \Bool\}) \to \List~\alpha\\ + \nondet~m \defas \bl + \Handle\;m\,\Unit\;\With\\ + ~\ba{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& [x]\\ + \OpCase{\Fork}{\Unit}{resume} &\mapsto& + \bl + \Let\;xs \revto resume~\True\;\In\\ + \Let\;ys \revto resume~\False\;\In\; + xs \concat ys + \el + \ea + \el + \el +\] % -In Section~\ref{sec:first-order-uncurried-cps} we will refine this -translation to be properly tail-recursive. +As for the parameterised handler we will use a handler, which +implements a simple counter that supports one operation +$\Incr : \UnitType \opto \Int$, which increments the value of the +counter and returns the previous value. It is defined as follows. % -As for administrative redexes, observe that the first reduction is -administrative. It is an artefact introduced by the translation, and -thus it has nothing to do with the dynamic semantics of the original -term. We can eliminate such redexes statically. We will address this -issue in Section~\ref{sec:higher-order-uncurried-deep-handlers-cps}. - -Nevertheless, we can show that the image of this CPS translation -simulates the preimage. Due to the presence of administrative -reductions, the simulation is not on the nose, but instead up to -congruence. +\[ + \bl + % H_\incr : \Record{\Int;\alpha \eff \{\Incr : \UnitType \opto \Int\}} \Harrow^\param \alpha\\ + % H_\incr \defas + % i.\,\ba[t]{@{~}l@{~}c@{~}l} + % \Return\;x &\mapsto& x\\ + % \OpCase{\Incr}{\Unit}{resume} &\mapsto& resume\,\Record{i+1;i} + % \ea \smallskip\\ + \incr : \Record{\Int;\UnitType \to \alpha\eff \{\Incr : \UnitType \opto \Int\}} \to \alpha\\ + \incr\,\Record{i_0;m} \defas + \bl + \ParamHandle\;m\,\Unit\;\With\\ + ~\left(i.\,\ba{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& \Return\;x\\ + \OpCase{\Incr}{\Unit}{resume} &\mapsto& \Let\;i' \revto i+1\;\In\;resume\,\Record{i';i} + \ea\right)~i_0 + \el + \el +\] % -For reduction in the untyped target calculus we write -$\reducesto_{\textrm{cong}}$ for the smallest relation containing -$\reducesto$ that is closed under the term formation constructs. +We will use the $\Pipe$ and $\Copipe$ shallow handlers from +Section~\ref{sec:pipes} to construct a small pipeline. % -\begin{theorem}[Simulation] - \label{thm:fo-simulation} - If $M \reducesto N$ then $\pcps{M} \reducesto_{\textrm{cong}}^+ - \pcps{N}$. -\end{theorem} - -\begin{proof} -The result follows by composing a call-by-value variant of -\citeauthor{ForsterKLP19}'s translation from effect handlers to -delimited continuations~\citeyearpar{ForsterKLP19} with -\citeauthor{MaterzokB12}'s CPS translation for delimited -continuations~\citeyearpar{MaterzokB12}. -\end{proof} - -% \paragraph*{Remark} -% We originally derived this curried CPS translation for effect handlers -% by composing \citeauthor{ForsterKLP17}'s translation from effect -% handlers to delimited continuations~\citeyearpar{ForsterKLP17} with -% \citeauthor{MaterzokB12}'s CPS translation for delimited -% continuations~\citeyearpar{MaterzokB12}. +\[ + \bl + \Pipe : \Record{\UnitType \to \alpha \eff \{ \Yield : \beta \opto \UnitType \}; \UnitType \to \alpha\eff\{ \Await : \UnitType \opto \beta \}} \to \alpha \\ + \Pipe\, \Record{p; c} \defas + \bl + \ShallowHandle\; c\,\Unit \;\With\; \\ + ~\ba[m]{@{}l@{~}c@{~}l@{}} + \Return~x &\mapsto& \Return\;x \\ + \OpCase{\Await}{\Unit}{resume} &\mapsto& \Copipe\,\Record{resume; p} \\ + \ea + \el\medskip\\ -\subsection{Uncurried translation} -\label{sec:first-order-uncurried-cps} + \Copipe : \Record{\beta \to \alpha\eff\{ \Await : \UnitType \opto \beta\}; \UnitType \to \alpha\eff\{ \Yield : \beta \opto \UnitType\}} \to \alpha \\ + \Copipe\, \Record{c; p} \defas + \bl + \ShallowHandle\; p\,\Unit \;\With\; \\ + ~\ba[m]{@{}l@{~}c@{~}l@{}} + \Return~x &\mapsto& \Return\;x \\ + \OpCase{\Yield}{y}{resume} &\mapsto& \Pipe\,\Record{resume; \lambda \Unit. c\, y} \\ + \ea \\ + \el \\ +\el +\] % +We use the following the producer and consumer computations for the +pipes. % -\begin{figure} - \flushleft - \textbf{Syntax} - \begin{syntax} - \slab{Computations} &M,N \in \UCompCat &::= & \cdots \mid \XCancel{M\,W} \mid V\,W \mid U\,V\,W \smallskip \\ - \XCancel{\slab{Evaluation contexts}} &\XCancel{\EC \in \UEvalCat} &::= & \XCancel{[~] \mid \EC\;W} \\ - \end{syntax} - - \textbf{Reductions} - \begin{reductions} - \usemlab{App_1} & (\lambda x . M) V &\reducesto& M[V/x] \\ - \usemlab{App_2} & (\lambda x . \lambda y. \, M) V\, W &\reducesto& M[V/x,W/y] \\ - \XCancel{\usemlab{Lift}} & \XCancel{\EC[M]} &\reducesto& \XCancel{\EC[N], \hfill \text{if } M \reducesto N} - \end{reductions} - \caption{Adjustments to the syntax and semantics of $\UCalc$.} - \label{fig:refined-cps-cbv-target} -\end{figure} +\[ + \bl + \prodf : \UnitType \to \alpha \eff \{\Incr : \UnitType \opto \Int; \Yield : \Int \opto \UnitType\}\\ + \prodf\,\Unit \defas + \bl + \Let\;j \revto \Do\;\Incr\,\Unit\;\In\; + \Let\;x \revto \Do\;\Yield~j\; + \In\;\dec{prod}\,\Unit + \el\smallskip\\ + \consf : \UnitType \to \Int \eff \{\Fork : \UnitType \opto \Bool; \Await : \UnitType \opto \Int\}\\ + \consf\,\Unit \defas + \bl + \Let\;b \revto \Do\;\Fork\,\Unit\;\In\; + \Let\;x \revto \Do\;\Await\,\Unit\;\In\\ + % \Let\;y \revto \Do\;\Await\,\Unit\;\In\\ + \If\;b\;\Then\;x*2\;\Else\;x*x + \el + \el +\] % -In this section we will refine the CPS translation for deep handlers -to make it properly tail-recursive. As remarked in the previous -section, the lack of tail-recursion is apparent in the syntax of the -target calculus $\UCalc$ as it permits an arbitrary computation term -in the function position of an application term. +The producer computation $\prodf$ invokes the operation $\Incr$ to +increment and retrieve the previous value of some counter. This value +is supplied as the payload to an invocation of $\Yield$. % - -As a first step we may restrict the syntax of the target calculus such -that the term in function position must be a value. With this -restriction the syntax of $\UCalc$ implements the property that any -term constructor features at most one computation term, and this -computation term always appears in tail position. This restriction -suffices to ensure that every function application will be in tail -position. +The consumer computation $\consf$ first performs an invocation of +$\Fork$ to duplicate the stream, and then it performs an invocation +$\Await$ to retrieve some value. The return value of $\consf$ depends +on the instance runs in the original stream or forked stream. The +original stream multiplies the retrieved value by $2$, and the +duplicate squares the value. % -Figure~\ref{fig:refined-cps-cbv-target} contains the adjustments to -syntax and semantics of $\UCalc$. The target calculus now supports -both unary and binary application forms. As we shall see shortly, -binary application turns out be convenient when we enrich the notion -of continuation. Both application forms are comprised only of value -terms. As a result the dynamic semantics of $\UCalc$ no longer makes -use of evaluation contexts. The reduction rule $\usemlab{App_1}$ -applies to unary application and it is the same as the -$\usemlab{App}$-rule in Figure~\ref{fig:cps-cbv-target}. The new -$\usemlab{App_2}$-rule applies to binary application: it performs a -simultaneous substitution of the arguments $V$ and $W$ for the -parameters $x$ and $y$, respectively, in the function body $M$. +Finally, the top-level computation plugs all of the above together. % - -These changes to $\UCalc$ immediately invalidate the curried -translation from the previous section as the image of the translation -is no longer well-formed. +\begin{equation} + \nondet\,(\lambda\Unit.\incr\,\Record{1;\lambda\Unit.\Pipe\,\Record{\prodf;\consf}})\label{eq:abs-prog} +\end{equation} % -The crux of the problem is that the curried interpretation of -continuations causes the CPS translation to produce `large' -application terms, e.g. the translation rule for effect forwarding -produces a three-argument application term. % -To rectify this problem we can adapt the technique of -\citet{MaterzokB12} to uncurry our CPS translation. Uncurrying -necessitates a change of representation for continuations: a -continuation is now an alternating list of pure continuation functions -and effect continuation functions. Thus, we move to an explicit -representation of the runtime handler stack. +Function interpretation is somewhat heavy notation-wise as +environments need to be built. To make the notation a bit more +lightweight I will not define the initial environments for closures +explicitly. By convention I will subscript initial environments with +the name of function, e.g. $\env_\consf$ denotes the initial +environment for the closure of $\consf$. Extensions of initial +environments will use superscripts to differentiate themselves, +e.g. $\env_\consf'$ is an extension of $\env_\consf$. As a final +environment simplification, I will take the initial environments to +contain the bindings for parameters of their closures, that is, an +initial environment is really the environment for the body of its +closure. In a similar fashion, I will use superscripts and subscripts +to differentiate handler closures, e.g. $\chi^\dagger_\Pipe$ denotes +the handler closure for the shallow handler definition in $\Pipe$. The +environment of a handler closure is to be understood +implicitly. Furthermore, the definitions above should be understood to +be implicitly $\Let$-sequenced, whose tail computation is +\eqref{eq:abs-prog}. Evaluation of this sequence gives rise to a +`toplevel' environment, which binds the closures for the definition. I +shall use $\env_0$ to denote this environment. + +The machine executes the top-level computation in an initial +configuration with the top-level environment $\env_0$. The first +couple of transitions install the three handlers in order: $\nondet$, +$\incr$, and $\Pipe$. % -The change of continuation representation means the CPS translation -for effect handlers is no longer a conservative extension. The -translation is adjusted as follows to account for the new -representation. +\begin{derivation} + &\nondet\,(\lambda\Unit.\incr\,\Record{1;\lambda\Unit.\Pipe\,\Record{\prodf;\consf}})\\ + \stepsto& \reason{\mlab{Init} with $\env_0$}\\ + &\cek{\nondet\,(\lambda\Unit.\incr\,\Record{0;\lambda\Unit.\Pipe\,\Record{\prodf;\consf}}) \mid \env_0 \mid \sks_0}\\ + \stepsto^+& \reason{$3\times$(\mlab{App}, \mlab{Handle^\delta})}\\ + &% \bl + \cek{c\,\Unit \mid \env_\Pipe \mid (\nil,\chi^\dagger_\Pipe) \cons (\nil, \chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0}\\ + % \text{where } + % \bl + % % \env_\Pipe = \env_0[c \mapsto (\env_0, \consf), p \mapsto (\env_0, \prodf)]\\ + % % \chi^\dagger_\Pipe = (\env_\Pipe, H^\dagger_\Pipe)\\ + % % \env_\incr = \env_0[m \mapsto (\env_0, \lambda\Unit.\Pipe\cdots),i \mapsto 0]\\ + % % \chi^\param_\incr = (\env_\incr, H^\param_\incr)\\ + % % \env_\nondet = \env_0[m \mapsto (\env_0, \lambda\Unit.\incr \cdots)]\\ + % % \chi_\nondet = (\env_\nondet, H_\nondet) + % \el + % \el\\ +\end{derivation} % -\begin{equations} -\cps{-} &:& \CompCat \to \UCompCat\\ -\cps{\Return~V} &\defas& \lambda (k \cons ks).k\,\cps{V}\,ks \\ -\cps{\Let~x \revto M~\In~N} &\defas& \lambda (k \cons ks).\cps{M}((\lambda x.\lambda ks'.\cps{N}(k \cons ks')) \cons ks) -\smallskip \\ -\cps{\Do\;\ell\;V} &\defas& \lambda (k \cons h \cons ks).h\,\Record{\ell,\Record{\cps{V}, \lambda x.\lambda ks'.k\,x\,(h \cons ks')}}\,ks -\smallskip \\ -\cps{\Handle \; M \; \With \; H} &\defas& \lambda ks . \cps{M} (\cps{\hret} \cons \cps{\hops} \cons ks) \medskip\\ -\cps{-} &:& \HandlerCat \to \UCompCat\\ -\cps{\{\Return \; x \mapsto N\}} &\defas& \lambda x.\lambda ks.\Let\; (h \cons ks') = ks \;\In\; \cps{N}\,ks' -\\ -\cps{\{\ell \; p \; r \mapsto N_\ell\}_{\ell \in \mathcal{L}}} -&\defas& -\bl -\lambda \Record{z,\Record{p,r}}. \lambda ks. \Case \; z \; - \{( \bl\ell \mapsto \cps{N_\ell}\,ks)_{\ell \in \mathcal{L}};\,\\ - y \mapsto \hforward((y,p,r),ks) \}\el \\ -\el \\ -\hforward((y,p,r),ks) &\defas& \bl - \Let\; (k' \cons h' \cons ks') = ks \;\In\; \\ - h'\,\Record{y, \Record{p, \lambda x.\lambda ks''.\,r\,x\,(k' \cons h' \cons ks'')}}\,ks'\\ - \el \medskip\\ -\pcps{-} &:& \CompCat \to \UCompCat\\ -\pcps{M} &\defas& \cps{M}~((\lambda x.\lambda ks.x) \cons (\lambda \Record{z,\Record{p,r}}. \lambda ks.\,\Absurd~z) \cons \nil) -\end{equations} +At this stage the continuation consists of four frames. The first +three frames each corresponds to an installed handler, whereas the +last frame is the identity handler. The control component focuses the +application of consumer computation provided as an argument to +$\Pipe$. The next few transitions get us to the first operation +invocation. % -The other cases are as in the original CPS translation in -Figure~\ref{fig:cps-cbv}. +\begin{derivation} + \stepsto^+& \reason{\mlab{App}, \mlab{Let}}\\ + &\bl + \cek{\Do\;\Fork\,\Unit \mid \env_\consf \mid ([(\env_\consf,b,\Let\;x \revto \cdots)],\chi^\dagger_\Pipe) \cons (\nil, \chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0}\\ + \el\\ + \stepsto^+& \reason{\mlab{Forward}, \mlab{Forward}}\\ + &\bl + \cek{\Do\;\Fork\,\Unit \mid \env_\consf \mid [(\nil, \chi_\nondet),(\nil,\chiid)] \circ \kappa'}\\ + \text{where } \kappa' = [([(\env_\consf,b,\Let\;x \revto \cdots)],\chi^\dagger_\Pipe),(\nil, \chi^\param_\incr)] + \el\\ +\end{derivation} % -Since we now use a list representation for the stacks of -continuations, we have had to modify the translations of all the -constructs that manipulate continuations. For $\Return$ and $\Let$, we -extract the top continuation $k$ and manipulate it analogously to the -original translation in Figure~\ref{fig:cps-cbv}. For $\Do$, we -extract the top pure continuation $k$ and effect continuation $h$ and -invoke $h$ in the same way as the curried translation, except that we -explicitly maintain the stack $ks$ of additional continuations. The -translation of $\Handle$, however, pushes a continuation pair onto the -stack instead of supplying them as arguments. Handling of operations -is the same as before, except for explicit passing of the -$ks$. Forwarding now pattern matches on the stack to extract the next -continuation pair, rather than accepting them as arguments. +The pure continuation under $\chi^\dagger_\Pipe$ has been augmented +with the pure frame corresponding to $\Let$-binding of the invocation +of $\Fork$. Operation invocation causes the machine to initiate a +search for a suitable handler, as the top-most handler $\Pipe$ does +not handle $\Fork$. The machine performs two $\mlab{Forward}$ +transitions, which moves the two top-most frames from the program +continuation onto the forwarding continuation. % -% Proper tail recursion coincides with a refinement of the target -% syntax. Now applications are either of the form $V\,W$ or of the form -% $U\,V\,W$. We could also add a rule for applying a two argument lambda -% abstraction to two arguments at once and eliminate the -% $\usemlab{Lift}$ rule, but we defer this until our higher order -% translation in Section~\ref{sec:higher-order-uncurried-cps}. - -Let us revisit the example from -Section~\ref{sec:first-order-curried-cps} to see first hand that our -refined translation makes the example properly tail-recursive. +As a result the, now, top-most frame of the program continuation +contains a suitable handler for $\Fork$. Thus the following +transitions transfer control to $\Fork$-case inside the $\nondet$ +handler. % -\begin{equations} -\pcps{\Return\;\Record{}} - &= & (\lambda (k \cons ks).k\,\Record{}\,ks)\,((\lambda x.\lambda ks.x) \cons (\lambda \Record{z, \_}.\lambda ks.\Absurd\,z) \cons \nil) \\ - &\reducesto& (\lambda x.\lambda ks.x)\,\Record{}\,((\lambda \Record{z,\_}.\lambda ks.\Absurd\,z) \cons \nil)\\ - &\reducesto& \Record{} -\end{equations} +\begin{derivation} + \stepsto^+& \reason{\mlab{Do}, \mlab{Let}}\\ + &\bl + \cek{resume~\True \mid \env_\nondet' \mid \kappa_0'}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\nondet' &=& \env_\nondet[resume \mapsto \kappa' \concat [(\nil, \chi_\nondet)]]\\ + \kappa_0' &=& [([(\env_\nondet',xs,\Let\;ys\revto\cdots)],\chiid)] + \el + \el +\end{derivation} % -The reduction sequence in the image of this uncurried translation has -one fewer steps (disregarding the administrative steps induced by -pattern matching) than in the image of the curried translation. The -`missing' step is precisely the reduction marked -\eqref{eq:cps-admin-reduct-2}, which was a partial application of the -initial pure continuation function that was not in tail -position. Note, however, that the first reduction (corresponding to -\eqref{eq:cps-admin-reduct-1}) remains administrative, the reduction -is entirely static, and as such, it can be dealt with as part of the -translation. +The $\mlab{Do}$ transition is responsible for activating the handler, +and the $\mlab{Let}$ transition focuses the first resumption +invocation. The resumption $resume$ is bound in the environment to the +forwarding continuation $\kappa'$ extended with the frame for the +current handler. The pure continuation running under the identity +handler gets extended with the $\Let$-binding containing the first +resumption invocation. The next transitions reassemble the program +continuation and focuses control on the invocation of $\Await$. % - -\paragraph{Administrative redexes} - -We can determine whether a redex is administrative in the image by -determining whether it corresponds to a redex in the preimage. If -there is no corresponding redex, then the redex is said to be -administrative. We can further classify an administrative redex as to -whether it is \emph{static} or \emph{dynamic}. - -A static administrative redex is a by-product of the translation that -does not contribute to the implementation of the dynamic behaviour of -the preimage. +\begin{derivation} + \stepsto^+& \reason{\mlab{Resume}, \mlab{PureCont}, \mlab{Let}}\\ + &\bl + \cek{\Do\;\Await\,\Unit \mid \env_\consf' \mid ([(\env_\consf',x,\If\;b\cdots)], \chi^\dagger_\Pipe) \cons (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\consf' &=& \env_\consf[b \mapsto \True]\\ + \ea + \el +\end{derivation} % -The separation between value and computation terms in fine-grain -call-by-value makes it evident where static administrative redexes can -arise. They arise from computation terms, which can clearly be seen -from the translation where each computation term induces a -$\lambda$-abstraction. Each induced $\lambda$-abstraction must -necessarily be eliminated by a unary application. These unary -applications are administrative; they do not correspond to reductions -in the preimage. The applications that do correspond to reductions in -the preimage are the binary (continuation) applications. - -A dynamic administrative redex is a genuine implementation detail that -supports some part of the dynamic behaviour of the preimage. An -example of such a detail is the implementation of effect -forwarding. In $\HCalc$ effect forwarding involves no auxiliary -reductions, any operation invocation is instantaneously dispatched to -a suitable handler (if such one exists). +At this stage the context of $\consf$ has been restored with $b$ being +bound to the value $\True$. The pure continuation running under +$\Pipe$ has been extended with pure frame corresponding to the +continuation of the $\Let$-binding of the $\Await$ +invocation. Handling of this invocation requires no use of the +forwarding continuation as the top-most frame contains a suitable +handler. % -The translation presented above realises effect forwarding by -explicitly applying the next effect continuation. This application is -an example of a dynamic administrative reduction. Not every dynamic -administrative reduction is necessary, though. For instance, the -implementation of resumptions as a composition of -$\lambda$-abstractions gives rise to administrative reductions upon -invocation. As we shall see in -Section~\ref{sec:first-order-explicit-resump} administrative -reductions due to resumption invocation can be dealt with by choosing -a more clever implementation of resumptions. - -\subsection{Resumptions as explicit reversed stacks} -\label{sec:first-order-explicit-resump} -% -% \dhil{Show an example involving administrative redexes produced by resumptions} +\begin{derivation} + \stepsto& \reason{\mlab{Do^\dagger}}\\ + &\bl + \cek{\Copipe\,\Record{resume;p} \mid \env_\Pipe' \mid (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\Pipe' &=& \env_\Pipe[resume \mapsto (\nil, [(\env_\consf',x,\If\;b\cdots)])]\\ + \ea + \el +\end{derivation} % -Thus far resumptions have been represented as functions, and -forwarding has been implemented using function composition. The -composition of resumption gives rise to unnecessary dynamic -administrative redexes as function composition necessitates the -introduction of an additional lambda abstraction. +Now the $\Await$-case of the $\Pipe$ handler has been activated. The +resumption $resume$ is bound to the shallow resumption in the +environment. The generalised continuation component of the shallow +resumption is empty, because no forwarding was involved in locating +the handler. The next transitions install the $\Copipe$ handler and +runs the producer computation. % -As an illustration of how and where these administrative redexes arise -let us consider an example with an operation $\Ask : \Unit \opto \Int$ -and two handlers $H_\Reader$ and $H_\Other$ such that -$H_\Reader^\Ask = \{\OpCase{\Ask}{\Unit}{r} \mapsto r~42\}$ whilst -$\Ask \not\in \dom(H_\Other)$. We denote the top-level continuation by -$ks_\top$. +\begin{derivation} + \stepsto^+& \reason{\mlab{App}, \mlab{Handle^\dagger}}\\ + &\bl + \cek{p\,\Unit \mid \env_\Copipe' \mid (\nil, \chi^\dagger_\Copipe) \cons \kappa'}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\Copipe' &=& \env_\Copipe[c \mapsto (\nil, [(\env_\consf',x,\If\;b\cdots)])]\\ + % \chi_\Copipe &=& (\env_\Copipe,H^\dagger_\Copipe)\\ + \ea + \el\\ + \stepsto^+& \reason{\mlab{AppRec}, \mlab{Let}}\\ + &\cek{\Do\;\Incr\,\Unit \mid \env_\prodf \mid ([(\env_\prodf,j,\Let\;x\revto\cdots)],\chi^\dagger_\Copipe) \cons \kappa'}\\ + \stepsto^+& \reason{\mlab{Forward}, \mlab{Do^\param}, \mlab{Let}, \mlab{App}, \mlab{PureCont}}\\ + &\bl + \cek{resume\,\Record{i';i} \mid \env_\incr' \mid (\nil, \chi_\nondet) \cons \kappa_0'}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\incr' &=& \env_\incr[\bl + i \mapsto 1, i' \mapsto 2,\\ + resume \mapsto [([(\env_\prodf,j,\Let\;x\revto\cdots)],\chi^\dagger_\Copipe),(\nil,\chi^\param_\incr)]] + \el + \ea + \el +\end{derivation} % -% \[ -% \bl -% \Reader \defas \{\Ask : \Unit \opto \Int\} \smallskip\\ -% H_{\Reader} : \alpha \eff \Reader \Harrow \alpha, \{ \OpCase{\Ask}{\Unit}{r} \mapsto r~42 \} \in H_{\Reader}\\ -% H_{\Other} : \alpha \eff \varepsilon \Harrow \alpha, \Ask \not\in \dom(H_{\Reader}) -% \el -% \] +The producer computation performs the $\Incr$ operation, which +requires one $\mlab{Forward}$ transition in order to locate a suitable +handler for it. The $\Incr$-case of the $\incr$ handler increments the +counter $i$ by one. The environment binds the current value of the +counter. The following $\mlab{Resume^\param}$ transition updates the +counter value to be that of $i'$ and continues the producer +computation. % \begin{derivation} - &\pcps{\Handle\; (\Handle\; \Do\;\Ask\,\Unit\;\With\;H_{\Other})\;\With\;H_{\Reader}}\\ - % =& \reason{definition of $\cps{-}$}\\ - % % &\lambda ks.\cps{\Handle\; \Do\;\Ask\,\Unit\;\With\;H_{\Other}}(\cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks)\\ - % % =& \reason{}\\ - % &(\lambda ks.(\lambda ks'.\cps{\Do\;\Ask\,\Unit}(\cps{H_{\Other}^\mret} \cons \cps{H_{\Other}^\mops} \cons ks'))(\cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks))\,ks_\top\\ - =& \reason{definition of $\pcps{-}$}\\ - &(\lambda ks. - \bl - (\lambda ks'. - \bl - (\lambda (k \cons h \cons ks'').h\,\Record{\Ask,\Record{\Unit,\lambda x.\lambda ks'''.k~x~(h \cons ks''')}}\,ks'')\\ - (\cps{H_{\Other}^\mret} \cons \cps{H_{\Other}^\mops} \cons ks')) - \el\\ - (\cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks))\,ks_\top - \el\\ - % \reducesto^\ast& \reason{apply continuation}\\ - % & (\lambda (k \cons h \cons ks'').h\,\Record{\Ask,\Record{\Unit,\lambda x.\lambda ks'''.k~x~(h \cons ks''')}})(\cps{H_{\Other}^\mret} \cons \cps{H_{\Other}^\mops} \cons \cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks_\top)\\ - \reducesto^\ast & \reason{multiple applications of \usemlab{App}, activation of $H_\Other$}\\ - & \cps{H_{\Other}^\mops}\,\Record{\Ask,\Record{\Unit,\lambda x.\lambda ks'''.\cps{H_{\Other}^\mret}~x~(\cps{H_{\Other}^\mops} \cons ks''')}}\,(\cps{H_{\Reader}^\mret} \cons H_{\Reader}^\mops \cons ks_\top)\\ - \reducesto^\ast & \reason{effect forwarding to $H_\Reader$}\\ - & \bl - H_{\Reader}^\mops\,\Record{\Ask,\Record{\Unit,\lambda x.\lambda ks''. r_\dec{admin}~x~(H_{\Reader}^\mret \cons H_{\Reader}^\mops \cons ks'')}}\,ks_\top\\ - \where~r_\dec{admin} \defas \lambda x.\lambda - ks'''.\cps{H_{\Other}^\mret}~x~(\cps{H_{\Other}^\mops} \cons ks''') + \stepsto^+&\reason{\mlab{Resume^\param}, \mlab{PureCont}, \mlab{Let}}\\ + &\bl + \cek{\Do\;\Yield~j \mid \env_{\prodf}'' \mid ([(\env_{\prodf}'',x,\prodf\,\Unit)],\chi^\dagger_\Copipe) \cons (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\prodf'' &=& \env_\prodf'[j \mapsto 1]\\ + \ea + \el\\ + \stepsto& \reason{\mlab{Do^\dagger}}\\ + &\bl + \cek{\Pipe\,\Record{resume;\lambda\Unit.c\,y} \mid \env_\Pipe' \mid (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\Pipe' &=& \env_\Pipe[y \mapsto 1, resume \mapsto (\nil,[(\env_{\prodf}'',x,\prodf\,\Unit)])] + \ea \el\\ - \reducesto^\ast & \reason{invocation of the administrative resumption} \\ - & r_\dec{admin}~42~(H_{\Reader}^\mret \cons H_{\Reader}^\mops \cons ks_\top)\\ - \reducesto^\ast & \reason{invocation of the resumption of the operation invocation site}\\ - & \cps{H_{\Other}^\mret}~42~(\cps{H_{\Other}^\mops} \cons - H_{\Reader}^\mret \cons H_{\Reader}^\mops \cons ks_\top) + \stepsto^+& \reason{\mlab{App}, \mlab{Handle^\dagger}, \mlab{Resume^\dagger}, \mlab{PureCont}}\\ + &\bl + \cek{\If\;b\;\Then\;x * 2\;\Else\;x*x \mid \env_\consf'' \mid (\nil, \chi^\dagger_\Pipe) \cons (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\consf'' &=& \env_\consf'[x \mapsto 1] + \ea + \el \end{derivation} % -Effect forwarding introduces the administrative abstraction -$r_{\dec{admin}}$, whose sole purpose is to forward the interpretation -of the operation to the operation invocation site. In a certain sense -$r_{\dec{admin}}$ is a sort of identity frame. The insertion of -identities ought to always trigger the alarm bells as an identity -computation is typically extraneous. -% -The amount of identity frames being generated scales linearly with the -number of handlers the operation needs to pass through before reaching -a suitable handler. - -We can avoid generating these administrative resumption redexes by -applying a variation of the technique that we used in the previous -section to uncurry the curried CPS translation. +The $\Yield$ operation causes another instance of the $\Pipe$ to be +installed in place of the $\Copipe$. The $\mlab{Resume^\dagger}$ +transition occurs because the consumer argument provided to $\Pipe$ is +the resumption of captured by the original instance of $\Pipe$, thus +invoking it causes the context of the original consumer computation to +be restored. Since $b$ is $\True$ the $\If$-expression will dispatch +to the $\Then$-branch, meaning the computation will ultimately return +$2$. This return value gets propagated through the handler stack. % -Rather than representing resumptions as functions, we move to an -explicit representation of resumptions as \emph{reversed} stacks of -pure and effect continuations. By choosing to reverse the order of -pure and effect continuations, we can construct resumptions -efficiently using regular cons-lists. We augment the syntax and -semantics of $\UCalc$ with a computation term -$\Let\;r=\Res\,V\;\In\;N$ which allow us to convert these reversed -stacks to actual functions on demand. +\begin{derivation} + \stepsto^+& \reason{\mlab{Case}, \mlab{App}, \mlab{GenCont}, \mlab{GenCont}, \mlab{GenCont}}\\ + &\cek{\Return\;[x] \mid \env_\nondet[x \mapsto 2] \mid \kappa_0'} +\end{derivation} % -\begin{reductions} - \usemlab{Res} - & \Let\;r=\Res\,(V_n \cons \dots \cons V_1 \cons \nil)\;\In\;N - & \reducesto - & N[\lambda x\,k.V_1\,x\,(V_2 \cons \dots \cons V_n \cons k)/r] -\end{reductions} +The $\Return$-clauses of the $\Pipe$ and $\incr$ handlers are +identities, and thus, the return value $x$ passes through +unmodified. The $\Return$-case of $\nondet$ lifts the value into a +singleton list. Next the pure continuation is invoked, which restores +the handling context of the first operation invocation $\Fork$. % -This reduction rule reverses the stack, extracts the top continuation -$V_1$, and prepends the remainder onto the current stack $W$. The -stack representing a resumption and the remaining stack $W$ are -reminiscent of the zipper data structure for representing cursors in -lists~\cite{Huet97}. Thus we may think of resumptions as representing -pointers into the stack of handlers. +\begin{derivation} + \stepsto& \reason{\mlab{PureCont}, \mlab{Let}}\\ + &\bl + \cek{resume~\False \mid \env_\nondet' \mid \kappa_0''}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\nondet'' &=& \env_\nondet'[xs \mapsto [3]]\\ + \kappa_0'' &=& [([(\env_\nondet'',ys,xs \concat ys)],\chiid)] + \ea + \el\\ + \stepsto^+& \reason{\mlab{Resume}, \mlab{PureCont}, \mlab{Let}}\\ + &\bl + \cek{\Do\;\Await\,\Unit \mid \env_\consf''' \mid ([(\env_\consf''',x,\If\;b\cdots)], \chi^\dagger_\Pipe) \cons (\nil, \chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0''}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\consf''' &=& \env_\consf''[b \mapsto \False]\\ + \ea + \el\\ +\end{derivation} % -The translations of $\Do$, handling, and forwarding need to be -modified to account for the change in representation of -resumptions. +The second invocation of the resumption $resume$ interprets $\Fork$ as +$\False$. The consumer computation is effectively restarted with $b$ +bound to $\False$. The previous transitions will be repeated. % -\begin{equations} -\cps{-} &:& \CompCat \to \UCompCat\\ - \cps{\Do\;\ell\;V} - &\defas& \lambda k \cons h \cons ks.\,h\, \Record{\ell,\Record{\cps{V}, h \cons k \cons \nil}}\, ks - \medskip\\ +\begin{derivation} + \stepsto^+ & \reason{same reasoning as above}\\ + &\bl + \cek{resume\,\Record{i';i} \mid \env_\incr'' \mid (\nil, \chi_\nondet) \cons \kappa_0''}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\incr' &=& \env_\incr[\bl + i \mapsto 2, i' \mapsto 3,\\ + resume \mapsto [([(\env_\prodf'',i,\Let\;x\revto\cdots)],\chi^\dagger_\Copipe),(\nil,\chi^\param_\incr)]]\el + \ea + \el +\end{derivation} % -\cps{-} &:& \HandlerCat \to \UCompCat\\ - \cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}} - &\defas& \bl - \lambda \Record{z,\Record{p,rs}}.\lambda ks.\Case \;z\; \{ - \bl - (\ell \mapsto \Let\;r=\Res\;rs \;\In\; \cps{N_{\ell}}\, ks)_{\ell \in \mathcal{L}};\,\\ - y \mapsto \hforward((y,p,rs),ks) \} \\ - \el \\ - \el \\ - \hforward((y,p,rs),ks) - &\defas&\Let\; (k' \cons h' \cons ks') = ks \;\In\; h'\,\Record{y,\Record{p,h' \cons k' \cons rs}} \,ks' -\end{equations} +After some amount transitions the parameterised handler $\incr$ will +be activated again. The counter variable $i$ is bound to the value +computed during the previous activation of the handler. The machine +proceeds as before and eventually reaches concatenation application +inside the $\Fork$-case. % -The translation of $\Do$ constructs an initial resumption stack, -operation clauses extract and convert the current resumption stack -into a function using the $\Res$ construct, and $\hforward$ augments -the current resumption stack with the current continuation pair. +\begin{derivation} + \stepsto^+& \reason{same reasoning as above}\\ + &\bl + \cek{xs \concat ys \mid \env_\nondet'' \mid \kappa_0}\\ + \text{where } + \ba[t]{@{~}r@{~}c@{~}l} + \env_\nondet'' &=& \env_\nondet'[ys \mapsto [4]] + \ea + \el\\ + \stepsto& \reason{\mlab{App}, \mlab{GenCont}, \mlab{Halt}}\\ + & [3,4] +\end{derivation} % +\section{Realisability and efficiency implications} +\label{subsec:machine-realisability} -\subsection{Higher-order translation for deep effect handlers} -\label{sec:higher-order-uncurried-deep-handlers-cps} -% -\begin{figure} -% -\textbf{Values} -% -\begin{displaymath} -\begin{eqs} -\cps{-} &:& \ValCat \to \UValCat\\ -\cps{x} &\defas& x \\ -\cps{\lambda x.M} &\defas& \dlam x\,ks.\Let\;(k \dcons h \dcons ks') = ks \;\In\;\cps{M} \sapp (\reflect k \scons \reflect h \scons \reflect ks') \\ -% \cps{\Rec\,g\,x.M} &\defas& \Rec\;f\,x\,ks.\cps{M} \sapp \reflect ks\\ -\cps{\Lambda \alpha.M} &\defas& \dlam \Unit\,ks.\Let\;(k \dcons h \dcons ks') = ks \;\In\;\cps{M} \sapp (\reflect k \scons \reflect h \scons \reflect ks') \\ -\cps{\Record{}} &\defas& \Record{} \\ -\cps{\Record{\ell = V; W}} &\defas& \Record{\ell = \cps{V}; \cps{W}} \\ -\cps{\ell~V} &\defas& \ell~\cps{V} \\ -\end{eqs} -\end{displaymath} -% -\textbf{Computations} -% -\begin{equations} -\cps{-} &:& \CompCat \to \SValCat^\ast \to \UCompCat\\ -\cps{V\,W} &\defas& \slam \sks.\cps{V} \dapp \cps{W} \dapp \reify \sks \\ -\cps{V\,T} &\defas& \slam \sks.\cps{V} \dapp \Record{} \dapp \reify \sks \\ -\cps{\Let\; \Record{\ell=x;y} = V \; \In \; N} &\defas& \slam \sks.\Let\; \Record{\ell=x;y} = \cps{V} \; \In \; \cps{N} \sapp \sks \\ -\cps{\Case~V~\{\ell~x \mapsto M; y \mapsto N\}} &\defas& - \slam \sks.\Case~\cps{V}~\{\ell~x \mapsto \cps{M} \sapp \sks; y \mapsto \cps{N} \sapp \sks\} \\ -\cps{\Absurd~V} &\defas& \slam \sks.\Absurd~\cps{V} \\ -\cps{\Return~V} &\defas& \slam \sk \scons \sks.\reify \sk \dapp \cps{V} \dapp \reify \sks \\ -\cps{\Let~x \revto M~\In~N} &\defas& \slam \sk \scons \sks.\cps{M} \sapp - (\reflect (\dlam x\,\dhk. - \ba[t]{@{}l} - \Let\;(h \dcons \dhk') = \dhk\;\In\\ - \cps{N} \sapp (\sk \scons \reflect h \scons \reflect \dhk')) \scons \sks) - \ea\\ -\cps{\Do\;\ell\;V} - &\defas& \slam \sk \scons \sh \scons \sks.\reify \sh \dapp \Record{\ell,\Record{\cps{V}, \reify \sh \dcons \reify \sk \dcons \dnil}} \dapp \reify \sks\\ -\cps{\Handle \; M \; \With \; H} &\defas& \slam \sks . \cps{M} \sapp (\reflect \cps{\hret} \scons \reflect \cps{\hops} \scons \sks) -% -\end{equations} -% -\textbf{Handler definitions} -% -\begin{equations} -\cps{-} &:& \HandlerCat \to \UValCat\\ -\cps{\{\Return \; x \mapsto N\}} &\defas& \dlam x\, \dhk. - \ba[t]{@{~}l} - \Let\; (h \dcons \dk \dcons h' \dcons \dhk') = \dhk \;\In\\ - \cps{N} \sapp (\reflect \dk \scons \reflect h' \scons \reflect \dhk') - \ea -\\ -\cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}} - &\defas& \bl - \dlam \Record{z,\Record{p,\dhkr}}\,\dhk.\Case \;z\; \{ - \ba[t]{@{}l@{}c@{~}l} - &(\ell \mapsto& - \ba[t]{@{}l} - \Let\;r=\Res\;\dhkr \;\In\\ - \Let\;(\dk \dcons h \dcons \dhk') = \dhk \;\In\\ - \cps{N_{\ell}} \sapp (\reflect \dk \scons \reflect h \scons \reflect \dhk'))_{\ell \in \mathcal{L}}; - \ea\\ - &y \mapsto& \hforward((y,p,\dhkr),\dhk) \} \\ - \ea \\ - \el \\ -\hforward((y,p,\dhkr),\dhk) - &\defas&\Let\; (\dk' \dcons h' \dcons \dhk') = \dhk \;\In\; h' \dapp \Record{y,\Record{p,h' \dcons \dk' \dcons \dhkr}} \dapp \dhk' -\end{equations} -% -\textbf{Top level program} -% -\begin{equations} -\pcps{-} &:& \CompCat \to \UCompCat\\ -\pcps{M} &=& \cps{M} \sapp (\reflect (\dlam x\,\dhk.x) \scons \reflect (\dlam z\,\dhk.\Absurd~z) \scons \snil) \\ -\end{equations} +A practical benefit of the abstract machine semantics over the +context-based small-step reduction semantics with explicit +substitutions is that it provides either a blueprint for a high-level +interpreter-based implementation or an outline for how stacks should +be manipulated in a low-level implementation along with a more +practical and precise cost model. The cost model is more practical in +the sense of modelling how actual hardware might go about executing +instructions, and it is more precise as it eliminates the declarative +aspect of the contextual semantics induced by the \semlab{Lift} +rule. For example, the asymptotic cost of handler lookup is unclear in +the contextual semantics, whereas the abstract machine clearly tells +us that handler lookup involves a linear search through the machine +continuation. -\caption{Higher-order uncurried CPS translation of $\HCalc$.} -\label{fig:cps-higher-order-uncurried} -\end{figure} -% -In the previous sections, we have seen step-wise refinements of the -initial curried CPS translation for deep effect handlers -(Section~\ref{sec:first-order-curried-cps}) to be properly -tail-recursive (Section~\ref{sec:first-order-uncurried-cps}) and to -avoid yielding unnecessary dynamic administrative redexes for -resumptions (Section~\ref{sec:first-order-explicit-resump}). +The abstract machine is readily realisable using standard persistent +functional data structures such as lists and +maps~\cite{Okasaki99}. The concrete choice of data structures required +to realise the abstract machine is not set in stone, although, its +definition is suggestive about the choice of data structures it leaves +space for interpretation. % -There is still one outstanding issue, namely, that the translation -yields static administrative redexes. In this section we will further -refine the CPS translation to eliminate all static administrative -redexes at translation time. +For example, generalised continuations can be implemented using lists, +arrays, or even heaps. However, the concrete choice of data structure +is going to impact the asymptotic time and space complexity of the +primitive operations on continuations: continuation augmentation +($\cons$) and concatenation ($\concat$). % -Specifically, the translation will be adapted to a higher-order -one-pass CPS translation~\citep{DanvyF90} that partially evaluates -administrative redexes at translation time. +For instance, a linked list provides a fast constant time +implementations of either operation, whereas a fixed-size array can +only provide implementations of either operation that run in linear +time due to the need to resize and copy contents in the extreme case. % -Following \citet{DanvyN03}, I will use a two-level lambda calculus -notation to distinguish between \emph{static} lambda abstraction and -application in the meta language and \emph{dynamic} lambda abstraction -and application in the target language. To disambiguate syntax -constructors in the respective calculi I will mark static constructors -with a {\color{blue}$\overline{\text{blue overline}}$}, whilst dynamic -constructors are marked with a -{\color{red}$\underline{\text{red underline}}$}. The principal idea is -that redexes marked as static are reduced as part of the translation, -whereas those marked as dynamic are reduced at runtime. To facilitate -this notation I will write application explicitly using an infix -``at'' symbol ($@$) in both calculi. +An implementation based on a singly-linked list admits constant time +for both continuation augmentation as this operation corresponds +directly to list cons. However, it admits only a linear time +implementation for continuation concatenation. Alternatively, an +implementation based on a \citeauthor{Hughes86} list~\cite{Hughes86} +reverses the cost as a \citeauthor{Hughes86} list uses functions to +represent cons cells, thus meaning concatenation is simply function +composition, but accessing any element, including the head, always +takes linear time in the size of the list. In practice, this +difference in efficiency means we can either trade-off fast +interpretation of $\Let$-bindings and $\Handle$-computations for +`slow' handling and context restoration or vice versa depending on +what we expect to occur more frequently. -\paragraph{Static terms} -% -As in the dynamic target language, continuations are represented as -alternating lists of pure continuation functions and effect -continuation functions. To ease notation I will make use of pattern -matching notation. The static meta language is generated by the -following productions. +The pervasiveness of $\Let$-bindings in fine-grain call-by-value means +that the top-most pure continuation is likely to be augmented and +shrunk repeatedly, thus it is a sensible choice to simply represent +generalisation continuations as singly linked list in order to provide +constant time pure continuation augmentation (handler installation +would be constant time too). However, the continuation component +contains two generalisation continuations. In the rule \mlab{Forward} +the forwarding continuation is extended using concatenation, thus we +may choose to represent the forwarding continuation as a +\citeauthor{Hughes86} list for greater efficiency. A consequence of +this choice is that upon resumption invocation we must convert the +forwarding continuation into singly linked list such that it can be +concatenated with the program continuation. Both the conversion and +the concatenation require a full linear traversal of the forwarding +continuation. % -\begin{syntax} - \slab{Static\text{ }patterns} &\sP \in \SPatCat &::=& \sks \mid \sk \scons \sP\\ - \slab{Static\text{ }values} & \sV, \sW \in \SValCat &::=& \reflect V \mid \sV \scons \sW \mid \slam \sP. \sM\\ - \slab{Static\text{ }computations} & \sM \in \SCompCat &::=& \sV \mid \sV \sapp \sW \mid \sV \dapp V \dapp W -\end{syntax} +A slightly clever choice is to represent both continuations using +\citeauthor{Huet97}'s Zipper data structure~\cite{Huet97}, which +essentially boils down to using a pair of singly linked lists, where +the first component contains the program continuation, and the second +component contains the forwarding continuation. We can make a +non-asymptotic improvement by representing the forwarding continuation +as a reversed continuation such that we may interpret the +concatenation operation ($\concat$) in \mlab{Forward} as regular cons +($\cons$). In the \mlab{Resume^\delta} rules we must then interpret +concatenation as reverse append, which needs to traverse the +forwarding continuation only once. + +\paragraph{Continuation copying} +A convenient consequence of using persistent functional data structure +to realise the abstract machine is that multi-shot resumptions become +efficiency as continuation copying becomes a constant time +operation. However, if we were only interested one-shot or linearly +used resumptions, then we may wish to use in-place mutations to +achieve greater efficiency. In-place mutations do not exclude support +for multi-shot resumptions, however, with mutable data structures the +resumptions needs to be copied before use. One possible way to copy +resumptions is to expose an explicit copy instruction in the source +language. Alternatively, if the source language is equipped with a +linear type system, then the linear type information can be leveraged +to provide an automatic insertion of copy instructions prior to +resumption invocations. + +% The structure of a generalised continuation lends itself to a +% straightforward implementation using a persistent singly-linked +% list. A particularly well-suited data structure for the machine +% continuation is \citeauthor{Huet97}'s Zipper data +% structure~\cite{Huet97}, which is essentially a pair of lists. + +% copy-on-write environments + +% The definition of abstract machine in this chapter is highly +% suggestive of the choice of data structures required for a realisation +% of the machine. The machine presented in this chapter can readily be +% realised using standard functional data structures such as lists and +% maps~\cite{Okasaki99}. + +\section{Simulation of the context-based reduction semantics} +\label{subsec:machine-correctness} +\begin{figure}[t] +\flushleft +\newcommand{\contapp}[2]{#1 #2} +\newcommand{\contappp}[2]{#1(#2)} +%% \newcommand{\contapp}[2]{#1[#2]} +%% \newcommand{\contapp}[2]{#1\mathbin{@}#2} +%% \newcommand{\contappp}[2]{#1\mathbin{@}(#2)} % -The patterns comprise only static list deconstructing. We let $\sP$ -range over static patterns. +\textbf{Configurations} +\begin{displaymath} +\inv{\cek{M \mid \env \mid \shk \circ \shk'}} \defas \contappp{\inv{\shk' \concat \shk}}{\inv{M}\env} + \defas \contappp{\inv{\shk'}}{\contapp{\inv{\shk}}{\inv{M}\env}} +\end{displaymath} % -The static values comprise reflected dynamic values, static lists, and -static lambda abstractions. We let $\sV, \sW$ range over meta language -values; by convention we shall use variables $\sk$ to denote -statically known pure continuations, $\sh$ to denote statically known -effect continuations, and $\sks$ to denote statically known -continuations. +\textbf{Pure continuations} +\begin{displaymath} +\contapp{\inv{[]}}{M} \defas M \qquad \contapp{\inv{((\env, x, N) \cons \slk)}}{M} + \defas \contappp{\inv{\slk}}{\Let\; x \revto M \;\In\; \inv{N}(\env \res \{x\})} +\end{displaymath} % -I shall use $\sM$ to range over static computations, which comprise -static values, static application and binary dynamic application of a -static value to two dynamic values. +\textbf{Continuations} +\begin{displaymath} +\contapp{\inv{[]}}{M} + \defas M \qquad +\contapp{\inv{(\slk, \chi) \cons \shk}}{M} + \defas \contapp{\inv{\shk}}{(\contappp{\inv{\chi}}{\contappp{\inv{\slk}}{M}})} +\end{displaymath} % -Static computations are subject to the following equational axioms. +\textbf{Handler closures} +\begin{displaymath} +\contapp{\inv{(\env, H^\depth)}}{M} + \defas \Handle^\depth\;M\;\With\;\inv{H^\depth}\env +\end{displaymath} % +\textbf{Computation terms} \begin{equations} - (\slam \sks. \sM) \sapp \sV &\defas& \sM[\sV/\sks]\\ - (\slam \sk \scons \sks. \sM) \sapp (\sV \scons \sW) &\defas& (\slam \sks. \sM[\sV/\sk]) \sapp \sW\\ -\end{equations} -% -The first equation is static $\beta$-equivalence, it states that -applying a static lambda abstraction with binder $\sks$ and body $\sM$ -to a static value $\sV$ is equal to substituting $\sV$ for $\sks$ in -$\sM$. The second equation provides a means for applying a static -lambda abstraction to a static list component-wise. +\inv{V\,W}\env &\defas& \inv{V}\env\,\inv{W}{\env} \\ +\inv{V\,T}\env &\defas& \inv{V}\env\,T \\ +\inv{\Let\;\Record{\ell = x; y} = V \;\In\;N}\env + &\defas& \Let\;\Record{\ell = x; y} =\inv{V}\env \;\In\; \inv{N}(\env \res \{x, y\}) \\ +\inv{\Case\;V\,\{\ell\;x \mapsto M; y \mapsto N\}}\env + &\defas& \Case\;\inv{V}\env \,\{\ell\;x \mapsto \inv{M}(\env \res \{x\}); y \mapsto \inv{N}(\env \res \{y\})\} \\ +\inv{\Return\;V}\env &\defas& \Return\;\inv{V}\env \\ +\inv{\Let\;x \revto M \;\In\;N}\env + &\defas& \Let\;x \revto\inv{M}\env \;\In\; \inv{N}(\env \res \{x\}) \\ +\inv{\Do\;\ell\;V}\env + &\defas& \Do\;\ell\;\inv{V}\env \\ +\inv{\Handle^\depth\;M\;\With\;H}\env + &\defas& \Handle^\depth\;\inv{M}\env\;\With\;\inv{H}\env \\ +\end{equations} + +\textbf{Handler definitions} +\begin{equations} +\inv{\{\Return\;x \mapsto M\}}\env + &\defas& \{\Return\;x \mapsto \inv{M}(\env \res \{x\})\} \\ +\inv{\{\OpCase{\ell}{p}{r} \mapsto M\} \uplus H^\depth}\env + &\defas& \{\OpCase{\ell}{p}{r} \mapsto \inv{M}(\env \res \{p, r\}\} \uplus \inv{H^\depth}\env \\ +\inv{(q.\,H)}\env &\defas& \inv{H}(\env \res \{q\}) +\end{equations} + +\textbf{Value terms and values} +\begin{displaymath} +\ba{@{}c@{}} +\begin{eqs} +\inv{x}\env &\defas& \inv{v}, \quad \text{ if }\env(x) = v \\ +\inv{x}\env &\defas& x, \quad \text{ if }x \notin \dom(\env) \\ +\inv{\lambda x^A.M}\env &\defas& \lambda x^A.\inv{M}(\env \res \{x\}) \\ +\inv{\Lambda \alpha^K.M}\env &\defas& \Lambda \alpha^K.\inv{M}\env \\ +\inv{\Record{}}\env &\defas& \Record{} \\ +\inv{\Record{\ell=V; W}}\env &\defas& \Record{\ell=\inv{V}\env; \inv{W}\env} \\ +\inv{(\ell\;V)^R}\env &\defas& (\ell\;\inv{V}\env)^R \\ +\end{eqs} +\quad +\begin{eqs} +\inv{\shk^A} &\defas& \lambda x^A.\inv{\shk}(\Return\;x) \\ +\inv{(\shk, \slk)^A} &\defas& \lambda x^A.\inv{\slk}(\inv{\shk}(\Return\;x)) \\ +\inv{(\env, \lambda x^A.M)} &\defas& \lambda x^A.\inv{M}(\env \res \{x\}) \\ +\inv{(\env, \Lambda \alpha^K.M)} &\defas& \Lambda \alpha^K.\inv{M}\env \\ +\inv{\Record{}} &\defas& \Record{} \\ +\inv{\Record{\ell=v; w}} &\defas& \Record{\ell=\inv{v}; \inv{w}} \\ +\inv{(\ell\;v)^R} &\defas& (\ell\;\inv{v})^R \\ +\end{eqs} \smallskip\\ +\inv{\Rec\,g^{A \to C}\,x.M}\env \defas \Rec\,g^{A \to C}\,x.\inv{M}(\env \res \{g, x\}) + \defas \inv{(\env, \Rec\,g^{A \to C}\,x.M)} \\ +\ea +\end{displaymath} + +\caption{Mapping from abstract machine configurations to terms.} +\label{fig:config-to-term} +\end{figure} % +We now show that the base abstract machine is correct with respect to +the combined context-based small-step semantics of $\HCalc$, $\SCalc$, +and $\HPCalc$ via a simulation result. -Reflected static values are reified as dynamic language values -$\reify \sV$ by induction on their structure. +Initial states provide a canonical way to map a computation term onto +the abstract machine. % -\[ - \ba{@{}l@{\qquad}c} - \reify \reflect V \defas V - &\reify (\sV \scons \sW) \defas \reify \sV \dcons \reify \sW - \ea -\] +A more interesting question is how to map an arbitrary configuration +to a computation term. % - -\paragraph{Higher-order translation} +Figure~\ref{fig:config-to-term} describes such a mapping $\inv{-}$ +from configurations to terms via a collection of mutually recursive +functions defined on configurations, continuations, handler closures, +computation terms, handler definitions, value terms, and machine +values. The mapping makes use of a domain operation and a restriction +operation on environments. % -As we shall see this translation manipulates the continuation -intricate ways; and since we maintain the interpretation of the -continuation as an alternating list of pure continuation functions and -effect continuation functions it is useful to define the `parity' of a -continuation as follows: +\begin{definition} + The domain of an environment is defined recursively as follows. + % + \[ + \bl + \dom : \MEnvCat \to \VarCat\\ + \ba{@{}l@{~}c@{~}l} + \dom(\emptyset) &\defas& \emptyset\\ + \dom(\env[x \mapsto v]) &\defas& \{x\} \cup \dom(\env) + \ea + \el + \] + % + We write $\env \res \{x_1, \dots, x_n\}$ for the restriction of + environment $\env$ to $\dom(\env) \res \{x_1, \dots, x_n\}$. +\end{definition} % -a continuation is said to be \emph{odd} if the top element is an -effect continuation function, otherwise it is said to \emph{even}. - +The $\inv{-}$ function enables us to classify the abstract machine +reduction rules according to how they relate to the context-based +small-step semantics. % - -The complete CPS translation is given in -Figure~\ref{fig:cps-higher-order-uncurried}. In essence, it is the -same as the refined first-order uncurried CPS translation, although -the notation is slightly more involved due to the separation of static -and dynamic parts. - -As before, the translation comprises three translation functions, one -for each syntactic category: values, computations, and handler -definitions. Amongst the three functions, the translation function for -computations stands out, because it is the only one that operates on -static continuations. Its type signature, -$\cps{-} : \CompCat \to \SValCat^\ast \to \UCompCat$, signifies that -it is a binary function, taking a $\HCalc$-computation term as its -first argument and a static continuation (a list of static values) as -its second argument, and ultimately produces a $\UCalc$-computation -term. Thus the computation translation function is able to manipulate -the continuation. In fact, the translation is said to be higher-order -because the continuation parameter is a higher-order: it is a list of -functions. - -To ensure that static continuation manipulation is well-defined the -translation maintains the invariant that the statically known -continuation stack ($\sk$) always contains at least two continuation -functions, i.e. a complete continuation pair consisting of a pure -continuation function and an effect continuation function. +Both the rules \mlab{Let} and \mlab{Forward} are administrative in the +sense that $\inv{-}$ is invariant under either rule. % -This invariant guarantees that all translations are uniform in whether -they appear statically within the scope of a handler or not, and this -also simplifies the correctness proof -(Theorem~\ref{thm:ho-simulation}). +This leaves the $\beta$-rules \mlab{App}, \mlab{AppRec}, \mlab{TyApp}, +\mlab{Resume^\delta}, \mlab{Split}, \mlab{Case}, \mlab{PureCont}, and +\mlab{GenCont}. Each of these corresponds directly with performing a +reduction in the small-step semantics. We extend the notion of +transition to account for administrative steps. % -Maintaining this invariant has a cosmetic effect on the presentation -of the translation. This effect manifests in any place where a -dynamically known continuation stack is passed in (as a continuation -parameter $\dhk$), as it must be deconstructed using a dynamic -language $\Let$ to expose the continuation structure and subsequently -reconstructed as a static value with reflected variable names. - -The translation of $\lambda$-abstractions provides an example of this -deconstruction and reconstruction in action. The dynamic continuation -$\dhk$ is deconstructed to expose to the next pure continuation -function $\dk$ and effect continuation $h$, and the remainder of the -continuation $\dhk'$; these names are immediately reflected and put -back together to form a static continuation that is provided to the -translation of the body computation $M$. - -The only translation rule that consumes a complete reflected -continuation pair is the translation of $\Do$. The effect continuation -function, $\sh$, is dynamically applied to an operation package and -the reified remainder of the continuation $\sks$. As usual, the -operation package contains the payload and the resumption, which is -represented as a reversed continuation slice. +\begin{definition}[Auxiliary reduction relations] + We write $\stepsto_{\textrm{a}}$ for administrative steps and + $\simeq_{\textrm{a}}$ for the symmetric closure of + $\stepsto_{\textrm{a}}^*$. We write $\stepsto_\beta$ for + $\beta$-steps and $\Stepsto$ for a sequence of steps of the form + $\stepsto_{\textrm{a}}^\ast \stepsto_\beta$. +\end{definition} % -The only other translation rules that manipulate the continuation are -$\Return$ and $\Let$, which only consume the pure continuation -function $\sk$. For example, the translation of $\Return$ is a dynamic -application of $\sk$ to the translation of the value $V$ and the -remainder of the continuation $\sks$. +The following lemma describes how we can simulate each reduction in +the small-step reduction semantics by a sequence of administrative +steps followed by one $\beta$-step in the abstract machine. % -The shape of $\sks$ is odd, meaning that the top element is an effect -continuation function. Thus the pure continuation $\sk$ has to account -for this odd shape. Fortunately, the possible instantiations of the -pure continuation are few. We can derive the all possible -instantiations systematically by using the operational semantics of -$\HCalc$. According to the operational semantics the continuation of a -$\Return$-computation is either the continuation of a -$\Let$-expression or a $\Return$-clause (a bare top-level -$\Return$-computation is handled by the $\pcps{-}$ translation). +\begin{lemma} +\label{lem:machine-simulation} +Suppose $M$ is a computation and $\conf$ is configuration such that +$\inv{\conf} = M$, then if $M \reducesto N$ there exists $\conf'$ such +that $\conf \Stepsto \conf'$ and $\inv{\conf'} = N$, or if +$M \not\reducesto$ then $\conf \not\Stepsto$. +\end{lemma} % -The translations of $\Let$-expressions and $\Return$-clauses each -account for odd continuations. For example, the translation of $\Let$ -consumes the current pure continuation function and generates a -replacement: a pure continuation function which expects an odd dynamic -continuation $\dhk$, which it deconstructs to expose the effect -continuation $h$ along with the current pure continuation function in -the translation of $N$. The modified continuation is passed to the -translation of $M$. +\begin{proof} +By induction on the derivation of $M \reducesto N$. +\end{proof} % -To provide a flavour of how this continuation manipulation functions -in practice, consider the following example term. +The correspondence here is rather strong: there is a one-to-one +mapping between $\reducesto$ and the quotient relation of $\Stepsto$ +and $\simeq_{\textrm{a}}$. % The inverse of the lemma is straightforward +% as the semantics is deterministic. % -\begin{derivation} - &\pcps{\Let\;x \revto \Return\;V\;\In\;N}\\ - =& \reason{definition of $\pcps{-}$}\\ - &\ba[t]{@{}l}(\slam \sk \scons \sks.\cps{\Return\;V} \sapp - (\reflect(\dlam x\,ks. - \ba[t]{@{}l} - \Let\;(h \dcons ks') = ks \;\In\\ - \cps{N} \sapp (\sk \scons \reflect h \scons \reflect ks')) \scons \sks) - \ea\\ - \sapp (\reflect (\dlam x\,ks.x) \scons \reflect (\dlam z\,ks.\Absurd~z) \scons \snil)) - \ea\\ - =& \reason{definition of $\cps{-}$}\\ - &\ba[t]{@{}l}(\slam \sk \scons \sks.(\slam \sk \scons \sks. \reify \sk \dapp \cps{V} \dapp \reify \sks) \sapp - (\reflect(\dlam x\,ks. - \ba[t]{@{}l} - \Let\;(h \dcons ks') = ks \;\In\\ - \cps{N} \sapp (\sk \scons \reflect h \scons \reflect ks')) \scons \sks) - \ea\\ - \sapp (\reflect (\dlam x\,ks.x) \scons \reflect (\dlam z\,ks.\Absurd~z) \scons \snil)) - \ea\\ - =& \reason{static $\beta$-reduction}\\ - &(\slam \sk \scons \sks. \reify \sk \dapp \cps{V} \dapp \reify \sks) - \sapp - (\reflect(\dlam x\,\dhk. - \ba[t]{@{}l} - \Let\;(h \dcons \dhk') = \dhk \;\In\\ - \cps{N} \sapp - \ba[t]{@{}l} - (\reflect (\dlam x\,\dhk.x) \scons \reflect h \scons \reflect \dhk'))\\ - ~~\scons \reflect (\dlam z\,\dhk.\Absurd~z) \scons \snil)) - \ea - \ea\\ - =& \reason{static $\beta$-reduction}\\ - &\ba[t]{@{}l@{~}l} - &(\dlam x\,\dhk. - \Let\;(h \dcons \dhk') = \dhk \;\In\; - \cps{N} \sapp - (\reflect (\dlam x\,\dhk.x) \scons \reflect h \scons \reflect \dhk'))\\ - \dapp& \cps{V} \dapp ((\dlam z\,\dhk.\Absurd~z) \dcons \dnil)\\ - \ea\\ -\reducesto& \reason{\usemlab{App_2}}\\ - &\Let\;(h \dcons \dhk') = (\dlam z\,\dhk.\Absurd~z) \dcons \dnil \;\In\; - \cps{N[V/x]} \sapp - (\reflect (\dlam x\,\dhk.x) \scons \reflect h \scons \reflect \dhk'))\\ -\reducesto^+& \reason{dynamic pattern matching and substitution}\\ - &\cps{N[V/x]} \sapp - (\reflect (\dlam x\,\dhk.x) \scons \reflect (\dlam z\,\dhk.\Absurd~z) \scons \reflect \dnil) -\end{derivation} -% -The translation of $\Return$ provides the generated dynamic pure -continuation function with the odd continuation -$((\dlam z\,ks.\Absurd~z) \dcons \dnil)$. After the \usemlab{App_2} -reduction, the pure continuation function deconstructs the odd -continuation in order to bind the current effect continuation function -to the name $h$, which would have been used during the translation of -$N$. - -The translation of $\Handle$ applies the translation of $M$ to the -current continuation extended with the translation of the -$\Return$-clause, acting as a pure continuation function, and the -translation of operation-clauses, acting as an effect continuation -function. +Notice that Lemma~\ref{lem:machine-simulation} does not require that +$M$ be well-typed. This is mostly a convenience to simplify the +lemma. The lemma is used in the following theorem where it is being +applied only on well-typed terms. % -The translation of a $\Return$-clause discards the effect continuation -$h$ and in addition exposes the next pure continuation $\dk$ and -effect continuation $h'$ which are reflected to form a static -continuation for the translation of $N$. +\begin{theorem}[Simulation]\label{thm:handler-simulation} + If $\typc{}{M : A}{E}$ and $M \reducesto^+ N$ such that $N$ is + normal with respect to $E$, then + $\cek{M \mid \emptyset \mid \kappa_0} \stepsto^+ \conf$ such that + $\inv{\conf} = N$, or $M \not\reducesto$ then + $\cek{M \mid \emptyset \mid \kappa_0} \not\stepsto$. +\end{theorem} % -The translation of operation clauses unpacks the provided operation -package to perform a case-split on the operation label $z$. The branch -for $\ell$ deconstructs the continuation $\dhk$ in order to expose the -continuation structure. The forwarding branch also deconstructs the -continuation, but for a different purpose; it augments the resumption -$\dhkr$ with the next pure and effect continuation functions. +\begin{proof} +By repeated application of Lemma~\ref{lem:machine-simulation}. +\end{proof} -Let us revisit the example from -Section~\ref{sec:first-order-curried-cps} to see that the higher-order -translation eliminates the static redex at translation time. -% -\begin{equations} -\pcps{\Return\;\Record{}} - &=& (\slam \sk \scons \sks. \sk \dapp \Record{} \dapp \reify \sks) \sapp (\reflect (\dlam x\,\dhk.x) \scons \reflect (\dlam z\,\dhk.\Absurd\;z) \scons \snil)\\ - &=& (\dlam x\,\dhk.x) \dapp \Record{} \dapp (\reflect (\dlam z\,\dhk.\Absurd\;z) \dcons \dnil)\\ - &\reducesto& \Record{} -\end{equations} -% -In contrast with the previous translations, the reduction sequence in -the image of this translation contains only a single dynamic reduction -(disregarding the dynamic administrative reductions arising from -continuation construction and deconstruction); both -\eqref{eq:cps-admin-reduct-1} and \eqref{eq:cps-admin-reduct-2} -reductions have been eliminated as part of the translation. +\section{Related work} +The literature on abstract machines is vast and rich. I describe here +the basic structure of some selected abstract machines from the +literature. -The elimination of static redexes coincides with a refinements of the -target calculus. Unary application is no longer a necessary -primitive. Every unary application dealt with by the metalanguage, -i.e. all unary applications are static. +\paragraph{Handler machines} Chronologically, the machine presented in +this chapter was the first abstract machine specifically designed for +effect handlers to appear in the literature. Subsequently, this +machine has been extended and used to explain the execution model for +the Multicore OCaml +implementation~\cite{SivaramakrishnanDWKJM21}. Their primary extension +captures the finer details of the OCaml runtime as it models the +machine continuation as a heterogeneous sequence consisting of +interleaved OCaml and C frames. -\paragraph{Implicit lazy continuation deconstruction} +An alternative machine has been developed by \citet{BiernackiPPS19} +for the Helium language. Although, their machine is based on +\citeauthor{BiernackaBD05}'s definitional abstract machine for the +control operators shift and reset~\cite{BiernackaBD05}, the +continuation structure of the resulting machine is essentially the +same as that of a generalised continuation. The primary difference is +that in their presentation a generalised frame is either pair +consisting of a handler closure and a pure continuation (as in the +presentation in this chapter) or a coercion paired with a pure +continuation. + +\paragraph{SECD machine} \citeauthor{Landin64}'s SECD machine was the +first abstract machine for $\lambda$-calculus viewed as a programming +language~\cite{Landin64,Danvy04}. The machine is named after its +structure as it consists of a \emph{stack} component, +\emph{environment} component, \emph{control} component, and a +\emph{dump} component. The stack component maintains a list of +intermediate value. The environment maps free variables to values. The +control component holds a list of directives that manipulate the stack +component. The dump acts as a caller-saved register as it maintains a +list of partial machine state snapshots. Prior to a closure +application, the machine snapshots the state of the stack, +environment, and control components such that this state can be +restored once the stack has been reduced to a single value and the +control component is empty. The structure of the SECD machine lends +itself to a simple realisation of the semantics of +\citeauthor{Landin98}'s the J operator as its behaviour can realised +by reifying the dump the as value. % -An alternative to the explicit deconstruction of continuations is to -implicitly deconstruct continuations on demand when static pattern -matching fails. I took this approach in \citet{HillerstromLAS17}. On -one hand this approach leads to a slightly slicker presentation. On -the other hand it complicates the proof of correctness as one must -account for static pattern matching failure. +\citet{Plotkin75} proved the correctness of the machine in style of a +simulation result with respect to a reduction +semantics~\cite{AgerBDM03}. + +The SECD machine is a precursor to the CEK machine as the latter can +be viewed as a streamlined variation of the SECD machine, where the +continuation component unifies stack and dump components of the SECD +machine. % -A practical argument in favour of the explicit eager continuation -deconstruction is that it is more accessible from an implementation -point of view. No implementation details are hidden away in side -conditions. +For a deep dive into the operational details of +\citeauthor{Landin64}'s SECD machine, the reader may consult +\citet{Danvy04}, who dissects the SECD machine, and as a follow up on +that work \citet{DanvyM08} perform several rational deconstructions +and reconstructions of the SECD machine with the J operator. + +\paragraph{Krivine machine} The \citeauthor{Krivine07} machine takes +its name after its designer \citet{Krivine07}. It is designed for +call-by-name $\lambda$-calculus computation as it performs reduction +to weak head normal form~\cite{Krivine07,Leroy90}. % -Also, it is not clear that lazy deconstruction has any advantage over -eager deconstruction, as the translation must reify the continuation -when it transitions from computations to values and reflect the -continuation when it transitions from values to computations, in which -case static pattern matching would fail. +The structure of the \citeauthor{Krivine07} machine is similar to that +of the CEK machine as it features a control component, which focuses +the current term under evaluation; an environment component, which +binds variables to closures; and a stack component, which contains a +list of closures. +% +Evaluation of an application term pushes the argument along with the +current environment onto the stack and continues to evaluate the +abstractor term. Dually evaluation of a $\lambda$-abstraction places +the body in the control component, subsequently it pops the top most +closure from the stack and extends the current environment with this +closure~\cite{DouenceF07}. -\subsubsection{Correctness} -\label{sec:higher-order-cps-deep-handlers-correctness} +\citet{Krivine07} has also designed a variation of the machine which +supports a call-by-name variation of the callcc control operator. In +this machine continuations have the same representation as the stack +component, and they can be stored on the stack. Then the continuation +capture mechanism of callcc can be realised by popping and installing +the top-most closure from the stack, and then saving the tail of the +stack as the continuation object, which is to be placed on top of the +stack. An application of a continuation can be realised by replacing +the current stack with the stack embedded inside the continuation +object~\cite{Krivine07}. -We establish the correctness of the higher-order uncurried CPS -translation via a simulation result in the style of -Plotkin~\cite{Plotkin75} (Theorem~\ref{thm:ho-simulation}). However, -before we can state and prove this result, we first several auxiliary -lemmas describing how translated terms behave. First, the higher-order -CPS translation commutes with substitution. + +\paragraph{ZINC machine} The ZINC machine is a strict variation of +\citeauthor{Krivine07}'s machine, though it was designed independently +by \citet{Leroy90}. The machine is used as the basis for the OCaml +byte code interpreter~\cite{Leroy90,LeroyDFGRV20}. % -\begin{lemma}[Substitution]\label{lem:ho-cps-subst} - % - The higher-order uncurried CPS translation commutes with - substitution in value terms - % - \[ - \cps{W}[\cps{V}/x] = \cps{W[V/x]}, - \] - % - and with substitution in computation terms - \[ - (\cps{M} \sapp (\sk \scons \sh \scons \sks))[\cps{V}/x] - = \cps{M[V/x]} \sapp (\sk \scons \sh \scons \sks)[\cps{V}/x], - \] - % - and with substitution in handler definitions - % - \begin{equations} - \cps{\hret}[\cps{V}/x] - &=& \cps{\hret[V/x]},\\ - \cps{\hops}[\cps{V}/x] - &=& \cps{\hops[V/x]}. - \end{equations} -\end{lemma} +There are some cosmetic difference between \citeauthor{Krivine07}'s +machine and the ZINC machine. For example, the latter decomposes the +stack component into an argument stack, holding arguments to function +calls, and a return stack, which holds closures. % -\begin{proof} - By mutual induction on the structure of $W$, $M$, $\hret$, and - $\hops$. -\end{proof} +A peculiar implementation detail of the ZINC machine that affects the +semantics of the OCaml language is that for $n$-ary function +application to be efficient, function arguments are evaluated +right-to-left rather than left-to-right as customary in call-by-value +language~\cite{Leroy90}. The OCaml manual leaves the evaluation order +for function arguments unspecified~\cite{LeroyDFGRV20}. However, for a +long time the native code compiler for OCaml would emit code utilised +left-to-right evaluation order for function arguments, consequently +the compilation method could affect the semantics of a program, as the +evaluation order could be observed using effects, e.g. by raising an +exception~\cite{CartwrightF92}. Anecdotally, Damien Doligez told me in +person at ICFP 2017 that unofficially the compiler has been aligned +with the byte code interpreter such that code running on either +implementation exhibits the same semantics. Even though the evaluation +order remains unspecified in the manual any other observable order +than right-to-left evaluation order is now considered a bug (subject +to some exceptions, notably short-circuiting logical and/or +functions). + +\paragraph{Mechanical machine derivations} % -It follows as a corollary that top-level substitution is well-behaved. +There are deep mathematical connections between environment-based +abstract machine semantics and standard reduction semantics with +explicit substitutions. % -\begin{corollary}[Top-level substitution] - \[ - \pcps{M}[\cps{V}/x] = \pcps{M[V/x]}. - \] -\end{corollary} +For example, \citet{AgerBDM03,AgerDM04,AgerBDM03a} relate abstract +machines and functional evaluators by way of a two-way derivation that +consists of closure conversion, transformation into CPS, and +defunctionalisation of continuations. % -\begin{proof} - Follows immediately by the definitions of $\pcps{-}$ and - Lemma~\ref{lem:ho-cps-subst}. -\end{proof} +\citet{BiernackaD07} demonstrate how to formally derive an abstract +machine from a small-step reduction strategy. Their presentation has +been formalised by \citet{Swierstra12} in the dependently-typed +programming language Agda. % -In order to reason about the behaviour of the \semlab{Op} rule, which -is defined in terms of an evaluation context, we need to extend the -CPS translation to evaluation contexts. +\citet{HuttonW04} demonstrate how to calculate a +correct-by-construction abstract machine from a given specification +using structural induction. Notably, their example machine supports +basic computational effects in the form of exceptions. % -\begin{equations} -\cps{-} &:& \EvalCat \to \SValCat\\ -\cps{[~]} &\defas& \slam \sks.\sks \\ -\cps{\Let\; x \revto \EC \;\In\; N} &\defas& \slam \sk \scons \sks.\cps{\EC} \sapp - (\reflect(\dlam x\,ks. - \ba[t]{@{}l} - \Let\;(h \dcons ks') = ks\;\In\;\\ - \cps{N} \sapp (\sk \scons \reflect h \scons \reflect ks')) \scons \sks) - \ea\\ -\cps{\Handle\; \EC \;\With\; H} &\defas& \slam \sks. \cps{\EC} \sapp (\cps{\hret} \scons \cps{\hops} \scons \sks) -\end{equations} -% -The following lemma is the characteristic property of the CPS -translation on evaluation contexts. -% -It provides a means for decomposing an evaluation context, such that -we can focus on the computation contained within the evaluation -context. -% -\begin{lemma}[Decomposition] -\label{lem:decomposition} -% -\begin{equations} -\cps{\EC[M]} \sapp (\sV \scons \sW) &=& \cps{M} \sapp (\cps{\EC} \sapp (\sV \scons \sW)) \\ -\end{equations} -% -\end{lemma} +\citet{AgerDM05} also extended their technique to derive abstract +machines from monadic-style effectful evaluators. + + +\part{Expressiveness} +\label{p:expressiveness} +\chapter{Interdefinability of effect handlers} +\label{ch:deep-vs-shallow} + +On the surface, shallow handlers seem to offer more flexibility than +deep handlers as they do not enforce a particular recursion scheme +over effectful computations. An interesting hypothesis worth +investigating is whether this flexibility is a mere programming +convenience or whether it enables shallow handlers to implement +programs that would otherwise be impossible to implement with deep +handlers. Put slightly different, the hypothesis to test is whether +handlers can implement one another. To test this sort of hypothesis we +first need to pin down what it means for `something to be able to +implement something else'. + +For example in Section~\ref{sec:pipes} I asserted that shallow +handlers provide the natural basis for implementing pipes, suggesting +that an implementation based on deep handlers would be fiddly. If we +were to consider the wider design space of programming language +features, then it turns out that deep handlers offer a direct +implementation of pipes by shifting recursion from terms to the level +of types (the interested reader may consult either \citet{KammarLO13} +or \citet{HillerstromL18} for the precise details). Thus in some sense +pipes are implementable with deep handlers, however, this particular +implementation strategy is not realisable in the $\HCalc$-calculus +since it has no notion of recursive types, meaning we cannot use this +strategy to argue that deep handlers can implement pipes in our +setting. % -\begin{proof} - By structural induction on the evaluation context $\EC$. -\end{proof} + +We will restrict our attention to the calculi $\HCalc$, $\SCalc$, and +$\HPCalc$ and use the notion of \emph{typeability-preserving + macro-expressiveness} to determine whether handlers are +interdefinable~\cite{ForsterKLP19}. In our particular setting, +typeability-preserving macro-expressiveness asks whether there exists +a \emph{local} transformation that can transform one kind of handler +into another kind of handler, whilst preserving typeability in the +image of the transformation. By mandating that the transform is local +we rule out the possibility of rewriting the entire program in, say, +CPS notation to implement deep and shallow handlers as in +Chapter~\ref{ch:cps}. % -Even though we have eliminated the static administrative redexes, we -still need to account for the dynamic administrative redexes that -arise from pattern matching against a reified continuation. To -properly account for these administrative redexes it is convenient to -treat list pattern matching as a primitive in $\UCalc$, therefore we -introduce a new reduction rule $\usemlab{SplitList}$ in $\UCalc$. +In this chapter we use the notion of typeability-preserving +macro-expressiveness to show that shallow handlers and general +recursion can simulate deep handlers up to congruence, and that deep +handlers can simulate shallow handlers up to administrative +reductions. % The +% latter construction generalises the example of pipes implemented +% using deep handlers that we gave in Section~\ref{sec:pipes}. % -\begin{reductions} -\usemlab{SplitList} & \Let\; (k \dcons ks) = V \dcons W \;\In\; M &\reducesto& M[V/k, W/ks] \\ -\end{reductions} +\paragraph{Relation to prior work} The results in this chapter has +been published previously in the following papers. % -Note this rule is isomorphic to the \usemlab{Split} rule with lists -encoded as right nested pairs using unit to denote nil. +\begin{enumerate}[i] + \item \bibentry{HillerstromL18} \label{en:ch-def-HL18} + \item \bibentry{HillerstromLA20} \label{en:ch-def-HLA20} +\end{enumerate} % -We write $\areducesto$ for the compatible closure of -\usemlab{SplitList}. +The results of Sections~\ref{sec:deep-as-shallow} and +\ref{sec:shallow-as-deep} appear in item \ref{en:ch-def-HL18}, whilst +the result of Section~\ref{sec:param-desugaring} appear in item +\ref{en:ch-def-HLA20}. -We also need to be able to reason about the computational content of -reflection after reification. By definition we have that -$\reify \reflect V = V$, the following lemma lets us reason about the -inverse composition. -% -\begin{lemma}[Reflect after reify] -\label{lem:reflect-after-reify} +\section{Deep as shallow} +\label{sec:deep-as-shallow} + +\newcommand{\dstrans}[1]{\mathcal{S}\llbracket #1 \rrbracket} + +The implementation of deep handlers using shallow handlers (and +recursive functions) is by a direct local translation, similar to how +one would implement a fold (catamorphism) in terms of general +recursion. Each handler is wrapped in a recursive function and each +resumption has its body wrapped in a call to this recursive function. % -Reflection after reification may give rise to dynamic administrative -reductions, i.e. +Formally, the translation $\dstrans{-}$ is defined as the homomorphic +extension of the following equations to all terms and substitutions. % \[ -\cps{M} \sapp (\sV_1 \scons \dots \sV_n \scons \reflect \reify \sW) - \areducesto^\ast \cps{M} \sapp (\sV_1 \scons \dots \sV_n \scons \sW) +\bl + \dstrans{-} : \CompCat \to \CompCat\\ + \dstrans{\Handle \; M \; \With \; H} \defas + (\Rec~h~f.\ShallowHandle\; f\,\Unit \; \With \; \dstrans{H}_h)\,(\lambda \Unit{}.\dstrans{M}) \medskip\\ + % \dstrans{H}h &=& \dstrans{\hret}h \uplus \dstrans{\hops}h \\ + \dstrans{-} : \HandlerCat \times \ValCat \to \HandlerCat\\ + \ba{@{}l@{~}c@{~}l} + \dstrans{\{ \Return \; x \mapsto N\}}_h &\defas& + \{ \Return \; x \mapsto \dstrans{N} \}\\ + \dstrans{\{ \OpCase{\ell}{p}{r} \mapsto N_\ell \}_{\ell \in \mathcal{L}}}_h &\defas& + \{ \OpCase{\ell}{p}{r} \mapsto + \bl + \Let \; r \revto \Return \; \lambda x.h\,(\lambda \Unit{}.r\,x)\\ + \In\;\dstrans{N_\ell} \}_{\ell \in \mathcal{L}} + \el + \ea +\el \] -\end{lemma} % -\begin{proof} - By induction on the structure of $M$. -\end{proof} -% -We next observe that the CPS translation simulates forwarding. -% -\begin{lemma}[Forwarding] -\label{lem:forwarding} -If $\ell \notin dom(H_1)$ then +The translation of $\Handle$ uses a $\Rec$-abstraction to introduce a +fresh name $h$ for the handler $H$. This name is used by the +translation of the handler definitions. The translation of +$\Return$-clauses is the identity, and thus ignores the handler +name. However, the translation of operation clauses uses the name to +simulate a deep resumption by guarding invocations of the shallow +resumption $r$ with $h$. + +In order to exemplify the translation, let us consider a variation of +the $\environment$ handler from Section~\ref{sec:tiny-unix-env}, which +handles an operation $\Ask : \UnitType \opto \Int$. % \[ - \cps{\hops_1} \dapp \Record{\ell,\Record{U, V}} \dapp (V_2 \dcons \cps{\hops_2} \dcons W) -\reducesto^+ - \cps{\hops_2} \dapp \Record{\ell,\Record{U, \cps{\hops_2} \dcons V_2 \dcons V}} \dapp W + \ba{@{~}l@{~}l} + &\mathcal{D}\left\llbracket + \ba[m]{@{}l} + \Handle\;\Do\;\Ask\,\Unit + \Do\;\Ask\,\Unit\;\With\\ + \quad\ba[m]{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& \Return\;x\\ + \OpCase{\Ask}{\Unit}{r} &\mapsto& r~42 + \ea + \ea + \right\rrbracket \medskip\\ + =& \bl + (\Rec\;env~f. + \bl + \ShallowHandle\;f\,\Unit\;\With\\ + \quad\ba[t]{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& \Return\;x\\ + \OpCase{\Ask}{\Unit}{r} &\mapsto& + \bl + \Let\;r \revto \Return\;\lambda x.env~(\lambda\Unit.r~x)\;\In\\ + r~42)~(\lambda\Unit.\Do\;\Ask\,\Unit + \Do\;\Ask\,\Unit) + \el + \ea + \el + \el + \ea \] % -\end{lemma} -% -\begin{proof} - By direct calculation. -\end{proof} -% -Now we show that the translation simulates the \semlab{Op} -rule. -% -\begin{lemma}[Handling] -\label{lem:handle-op} -If $\ell \notin BL(\EC)$ and $\hell = \{\ell\,p\,r \mapsto N_\ell\}$ then -% -\[ -\bl -\cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\reflect\cps{\hret} \scons \reflect\cps{\hops} \scons \sV)) \reducesto^+\areducesto^\ast \\ -\quad - (\cps{N_\ell} \sapp \sV)[\cps{V}/p, (\lambda y\,ks.\cps{\Return\;y} \sapp (\cps{\EC} \sapp (\reflect\cps{\hret} \scons \reflect\cps{\hops} \scons \reflect ks)))/] -\el -\] +The deep semantics are simulated by generating the name $env$ for the +shallow handlers and recursively apply the handler under the modified +resumption. + +The translation commutes with substitution and preserves typeability. % +\begin{lemma}\label{lem:dstrans-subst} + Let $\sigma$ denote a substitution. The translation $\dstrans{-}$ + commutes with substitution, i.e. + % + \[ + \dstrans{V}\dstrans{\sigma} = \dstrans{V\sigma},\quad + \dstrans{M}\dstrans{\sigma} = \dstrans{M\sigma},\quad + \dstrans{H}\dstrans{\sigma} = \dstrans{H\sigma}. + \] + % \end{lemma} % \begin{proof} -Follows from Lemmas~\ref{lem:decomposition}, -\ref{lem:reflect-after-reify}, and \ref{lem:forwarding}. + By induction on the structures of $V$, $M$, and $H$. \end{proof} -% -Finally, we have the ingredients to state and prove the simulation -result. The following theorem shows that the only extra behaviour -exhibited by a translated term is the bureaucracy of deconstructing -the continuation stack. -% -\begin{theorem}[Simulation] -\label{thm:ho-simulation} -If $M \reducesto N$ then $\pcps{M} \reducesto^+ \areducesto^* \pcps{N}$. + +\begin{theorem} +If $\Delta; \Gamma \vdash M : C$ then $\Delta; \Gamma \vdash +\dstrans{M} : \dstrans{C}$. \end{theorem} % \begin{proof} - By case analysis on the reduction relation using Lemmas - \ref{lem:decomposition}--\ref{lem:handle-op}. The \semlab{Op} case - follows from Lemma~\ref{lem:handle-op}. + By induction on the typing derivations. \end{proof} + +In order to obtain a simulation result, we allow reduction in the +simulated term to be performed under lambda abstractions (and indeed +anywhere in a term), which is necessary because of the redefinition of +the resumption to wrap the handler around its body. % -% In common with most CPS translations, full abstraction does not -% hold. However, as our semantics is deterministic it is straightforward -% to show a backward simulation result. -% % -% \begin{corollary}[Backwards simulation] -% If $\pcps{M} \reducesto^+ \areducesto^* V$ then there exists $W$ such that -% $M \reducesto^* W$ and $\pcps{W} = V$. -% \end{corollary} -% % -% \begin{proof} -% TODO\dots -% \end{proof} +Nevertheless, the simulation proof makes minimal use of this power, +merely using it to rename a single variable. % +% We write $R_{\Cong}$ for the compatible closure of relation +% $R$, that is the smallest relation including $R$ and closed under term +% constructors for $\SCalc$. +%% , otherwise known as \emph{reduction up to +%% congruence}. -\section{Transforming shallow effect handlers} -\label{sec:cps-shallow} - -In this section we will continue to build upon the higher-order -uncurried CPS translation -(Section~\ref{sec:higher-order-uncurried-deep-handlers-cps}) in order -to add support for shallow handlers. The dynamic nature of shallow -handlers pose an interesting challenge, because unlike deep resumption -capture, a shallow resumption capture discards the handler leaving -behind a dangling pure continuation. The dangling pure continuation -must be `adopted' by whichever handler the resumption invocation occur -under. This handler is determined dynamically by the context, meaning -the CPS translation must be able to modify continuation pairs. +\begin{theorem}[Simulation up to congruence] +If $M \reducesto N$ then $\dstrans{M} \reducesto_{\Cong}^+ +\dstrans{N}$. +\end{theorem} -In Section~\ref{sec:cps-shallow-flawed} I will discuss an attempt at a -`natural' extension of the higher-order uncurried CPS translation for -deep handlers, but for various reasons this extension is -flawed. However, the insights gained by attempting this extension -leads to yet another change of the continuation representation -(Section~\ref{sec:generalised-continuations}) resulting in the notion -of a \emph{generalised continuation}. -% -In Section~\ref{sec:cps-gen-conts} we will see how generalised -continuations provide a basis for implementing deep and shallow effect -handlers simultaneously, solving all of the problems encountered thus -far uniformly. +\begin{proof} + By case analysis on $\reducesto$ using + Lemma~\ref{lem:dstrans-subst}. The interesting case is + $\semlab{Op}$, which is where we apply a single $\beta$-reduction, + renaming a variable, under the $\lambda$-abstraction representing + the resumption. The proof of this case is as follows. + % + \begin{derivation} + & \dstrans{\Handle\;\EC[\Do\;\ell~V]\;\With\;H}\\ + =& \reason{definition of $\dstrans{-}$}\\ + & (\Rec\;h\;f.\ShallowHandle\;f\,\Unit\;\With\;\dstrans{H}_h)\,(\lambda\Unit.\dstrans{\EC}[\Do\;\ell~\dstrans{V}])\\ + \reducesto^+& \reason{\semlab{Rec}, \semlab{App}, \semlab{Op^\dagger} with $\hell = \{\OpCase{\ell}{p}{r} \mapsto N\}$}\\ + & (\Let\;r \revto \Return\;\lambda x.h\,(\lambda\Unit.r~x)\;\In\;\dstrans{N})[\lambda y.\dstrans{\EC}[\Return\;y]/r,\dstrans{V}/p]\\ + =& \reason{definition of [-]}\\ + &(\Let\;r \revto \Return\;\lambda x.h\,(\lambda\Unit.(\lambda y.\dstrans{\EC}[\Return\;y])~x)\;\In\;\dstrans{N}[\dstrans{V}/p]\\ + \reducesto_\Cong & \reason{\semlab{App} reduction under $\lambda x.\cdots$}\\ + &(\Let\;r \revto \Return\;\lambda x.h\,(\lambda\Unit.\dstrans{\EC}[\Return\;x])\;\In\;\dstrans{N}[\dstrans{V}/p]\\ + \reducesto& \reason{\semlab{Let} and Lemma~\ref{lem:dstrans-subst}}\\ + % & \dstrans{N}[\dstrans{V}/p,\lambda x.h\,(\lambda\Unit.\dstrans{\EC}[\Return\;x])/r]\\ + % =& \reason{}\\ + &\dstrans{N[V/p,\lambda x.h\,(\lambda\Unit.\EC[\Return\;x])/r]} + \end{derivation} +\end{proof} -\subsection{A specious attempt} -\label{sec:cps-shallow-flawed} -% -Initially it is tempting to try to extend the interpretation of the -continuation representation in the higher-order uncurried CPS -translation for deep handlers to squeeze in shallow handlers. The main -obstacle one encounters is how to decouple a pure continuation from -its handler such that a it can later be picked up by another handler. +\section{Shallow as deep} +\label{sec:shallow-as-deep} +\newcommand{\sdtrans}[1]{\mathcal{D}\llbracket #1 \rrbracket} -To fully uninstall a handler, we must uninstall both the pure -continuation function corresponding to its return clause and the -effect continuation function corresponding to its operation clauses. +Implementing shallow handlers in terms of deep handlers is slightly +more involved than the other way round. % -In the current setup it is impossible to reliably uninstall the former -as due to the translation of $\Let$-expressions it may be embedded -arbitrarily deep within the current pure continuation and the -extensional representation of pure continuations means that we cannot -decompose them. - -A quick fix to this problem is to treat pure continuation functions -arising from return clauses separately from pure continuation -functions arising from $\Let$-expressions. +It amounts to the encoding of a case split by a fold and involves a +translation on handler types as well as handler terms. % -Thus we can interpret the continuation as a sequence of triples -consisting of two pure continuation functions followed by an effect -continuation function, where the first pure continuation function -corresponds the continuation induced by some $\Let$-expression and the -second corresponds to the return clause of the current handler. +Formally, the translation $\sdtrans{-}$ is defined as the homomorphic +extension of the following equations to all types, terms, type +environments, and substitutions. % -To distinguish between the two kinds of pure continuations, we shall -write $\svhret$ for statically known pure continuations arising from -return clauses, and $\vhret$ for dynamically known ones. Similarly, we -write $\svhops$ and $\vhops$, respectively, for statically and -dynamically, known effect continuations. With this notation in mind, -we may translate operation invocation and handler installation using -the new interpretation of the continuation representation as follows. +\[ + \bl + \sdtrans{-} : \HandlerTypeCat \to \HandlerTypeCat\\ + \sdtrans{A\eff E_1 \Rightarrow B\eff E_2} \defas + \sdtrans{A\eff E_1} \Rightarrow \Record{\UnitType \to \sdtrans{C \eff E_1}; \UnitType \to \sdtrans{B \eff E_2}} \eff \sdtrans{E_2} \medskip \medskip\\ + % \sdtrans{C \Rightarrow D} \defas + % \sdtrans{C} \Rightarrow \Record{\UnitType \to \sdtrans{C}; \UnitType \to \sdtrans{D}} \medskip \medskip\\ + \sdtrans{-} : \CompCat \to \CompCat\\ + \sdtrans{\ShallowHandle \; M \; \With \; H} \defas + \ba[t]{@{}l} + \Let\;z \revto \Handle \; \sdtrans{M} \; \With \; \sdtrans{H} \; \In\\ + \Let\;\Record{f; g} = z \;\In\; + g\,\Unit + \ea \medskip\\ + \sdtrans{-} : \HandlerCat \to \HandlerCat\\ + % \sdtrans{H} &=& \sdtrans{\hret} \uplus \sdtrans{\hops} \\ + \ba{@{}l@{~}c@{~}l} + \sdtrans{\{\Return \; x \mapsto N\}} &\defas& + \{\Return \; x \mapsto \Return\; \Record{\lambda \Unit.\Return\; x; \lambda \Unit.\sdtrans{N}}\} \\ + \sdtrans{\{\OpCase{\ell}{p}{r} \mapsto N\}_{\ell \in \mathcal{L}}} &\defas& \{ + \bl + \OpCase{\ell}{p}{r} \mapsto\\ + \qquad\bl\Let \;r \revto + \lambda x. \Let\; z \revto r~x\;\In\; + \Let\; \Record{f; g} = z\; \In\; f\,\Unit\;\In\\ + \Return\;\Record{\lambda \Unit.\Let\; x \revto \Do\;\ell~p\; \In\; r~x; \lambda \Unit.\sdtrans{N}}\}_{\ell \in \mathcal{L}} + \el + \el + \ea + \el +\] % -\begin{equations} -\cps{-} &:& \CompCat \to \SValCat^\ast \to \UCompCat \smallskip\\ -\cps{\Do\;\ell\;V} &\defas& \slam \sk \scons \svhret \scons \svhops \scons \sks. - \reify\svhops \ba[t]{@{}l} - \dapp \Record{\ell, \Record{\cps{V}, \reify\svhops \dcons \reify\svhret \dcons \reify\sk \dcons \dnil}}\\ - \dapp \reify \sks - \ea\smallskip\\ -\cps{\ShallowHandle \; M \; \With \; H} &\defas& -\slam \sks . \cps{M} \sapp (\reflect\kid \scons \reflect\cps{\hret} \scons \reflect\cps{\hops}^\dagger \scons \sks) \medskip\\ -\kid &\defas& \dlam x\, \dhk.\Let\; (\vhret \dcons \dhk') = \dhk \;\In\; \vhret \dapp x \dapp \dhk' -\end{equations} +As evident from the translation of handler types, each shallow handler +is encoded as a deep handler that returns a pair of thunks. It is +worth noting that the handler construction is actually pure, yet we +need to annotate the pair with the translated effect signature +$\sdtrans{E_2}$, because the calculus has no notion of effect +subtyping. Technically we could insert an administrative identity +handler to coerce the effect signature. There are practical reasons +for avoiding administrative handlers, though, as we shall discuss +momentarily the inordinate administrative overhead of this +transformation might conceal the additional overhead incurred by the +introduction of administrative identity handlers. The first component +of the pair forwards all operations, acting as the identity on +computations. The second component interprets a single operation +before reverting to forwarding. % -The only change to the translation of operation invocation is the -extra bureaucracy induced by the additional pure continuation. +The following example illustrates the translation on an instance of +the $\Pipe$ operator from Section~\ref{sec:pipes} using the consumer +computation $\Do\;\Await\,\Unit + \Do\;\Await\,\Unit$ and the +suspended producer computation +$\Rec\;ones\,\Unit.\Do\;\Yield~1;ones\,\Unit$. % -The translation of handler installation is a little more interesting -as it must make up an initial pure continuation in order to maintain -the sequence of triples interpretation of the continuation -structure. As the initial pure continuation we use the administrative -function $\kid$, which amounts to a dynamic variation of the -translation rule for the trivial computation term $\Return$: it -invokes the next pure continuation with whatever value it was -provided. +\[ + \ba{@{~}l@{~}l} + &\mathcal{D}\left\llbracket + \ba[m]{@{}l} + \ShallowHandle\;\Do\;\Await\,\Unit + \Do\;\Await\,\Unit\;\With\\ + \quad\ba[m]{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& \Return\;x\\ + \OpCase{\Await}{\Unit}{r} &\mapsto& \Copipe\,\Record{r;\Rec\;ones\,\Unit.\Do\;\Yield~1;ones\,\Unit} + \ea + \ea + \right\rrbracket \medskip\\ + =& \bl + \Let\;z \revto \bl + \Handle\;(\lambda\Unit.\Do\;\Await\,\Unit + \Do\;\Await\,\Unit)\,\Unit\;\With\\ + \quad\ba[t]{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& \Return\;\Record{\lambda\Unit.\Return\;x;\lambda\Unit.\Return\;x}\\ + \OpCase{\Await}{\Unit}{r} &\mapsto&\\ + \multicolumn{3}{l}{ + \quad\bl + \Let\;r \revto \lambda x.\Let\;z \revto r~x\;\In\;\Let\;\Record{f;g} = z\;\In\;f\,\Unit\;\In\\ + \Return\;\Record{\bl + \lambda\Unit.\Let\;x \revto \Do\;\ell~p\;\In\;r~x;\\ + \lambda\Unit. \sdtrans{\Copipe}\,\Record{r;\Rec\;ones\,\Unit.\Do\;\Yield~1;ones\,\Unit}}\el + \el} + \ea + \el\\ + \In\;\Let\;\Record{f;g} = z\;\In\;g\,\Unit + \el + \ea +\] % +Evaluation of both the left hand side and right hand side of the +equals sign yields the value $2 : \Int$. The $\Return$-case in the +image contains a redundant pair, because the $\Return$-case of $\Pipe$ +is the identity. The translation of the $\Await$-case sets up the +forwarding component and handling component of the pair of thunks. -Although, I will not demonstrate it here, the translation rules for -$\lambda$-abstractions, $\Lambda$-abstractions, and $\Let$-expressions -must also be adjusted accordingly to account for the extra -bureaucracy. The same is true for the translation of $\Return$-clause, -thus it is rather straightforward to adapt it to the new continuation -interpretation. +The distinction between deep and shallow handlers is that the latter +is discharged after handling a single operation, whereas the former is +persistent and apt for continual operation interpretations. The +persistence of deep handlers means that any handler in the image of +the translation remains in place for the duration of the handled +computation after handling a single operation, which has noticeable +asymptotic performance implications. Each activation of a handler in +the image introduces another layer of indirection that any subsequent +operation invocation have to follow. Supposing some source program +contains $n$ handlers and performs $k$ operation invocations, then the +image introduces $k$ additional handlers, meaning the total amount of +handlers in the image is $n+k$. Viewed through the practical lens of +the CPS translation (Chapter~\ref{ch:cps}) or abstract machine +(Chapter~\ref{ch:abstract-machine}) it means that in the worst case +handler lookup takes $\BigO(n+k)$ time. For example, consider the +extreme case where $n = 1$, that is, the handler lookup takes +$\BigO(1)$ time in the source, but in the image it takes $\BigO(k)$ +time. % -\begin{equations} - \cps{-} &:& \HandlerCat \to \UValCat\\ - \cps{\{\Return \; x \mapsto N\}} &\defas& \dlam x\, \dhk. - \ba[t]{@{}l} - \Let\; (\_ \dcons \dk \dcons \vhret \dcons \vhops \dcons \dhk') = \dhk \;\In\\ - \cps{N} \sapp (\reflect \dk \scons \reflect \vhret \scons \reflect \vhops \scons \reflect \dhk') - \ea -\end{equations} +Thus this translation is more of theoretical significance than +practical interest. It also demonstrates that typeability-preserving +macro-expressiveness is rather coarse-grained notion of +expressiveness, as it blindly considers whether some construct is +computable using another construct without considering the +computational cost. + +The translation commutes with substitution and preserves typeability. % -As before, the translation ensures that the associated effect -continuation is discarded (the first element of the dynamic -continuation $ks$). In addition the next continuation triple is -extracted and reified as a static continuation triple. -% -The interesting rule is the translation of operation clauses. -% -\begin{equations} -\cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}}^\dagger -&\defas& -\bl -\dlam \Record{z,\Record{p,\dhkr}}\,\dhk.\\ - \qquad\Case \;z\; \{ - \ba[t]{@{}l@{}c@{~}l} - (&\ell &\mapsto - \ba[t]{@{}l} - \Let\;(\dk \dcons \vhret \dcons \vhops \dcons \dhk') = \dhk\;\In\\ - \Let\;(\_ \dcons \_ \dcons \dhkr') = \dhkr \;\In\\ - \Let\;r = \Res\,(\hid \dcons \rid \dcons \dhkr') \;\In \\ - \cps{N_{\ell}} \sapp (\reflect\dk \scons \reflect\vhret \scons \reflect\vhops \scons \reflect \dhk'))_{\ell \in \mathcal{L}} \\ - \ea \\ - &y &\mapsto \hforward((y,p,\dhkr),\dhk) \} \\ - \ea -\el \medskip\\ -\hforward((y, p, \dhkr), \dhk) &\defas& \bl - \Let\; (\dk \dcons \vhret \dcons \vhops \dcons \dhk') = \dhk \;\In \\ - \vhops \dapp \Record{y, \Record{p, \vhops \dcons \vhret \dcons \dk \dcons \dhkr}} \dapp \dhk' \\ - \el \smallskip\\ -\hid &\defas& \dlam\,\Record{z,\Record{p,\dhkr}}\,\dhk.\hforward((z,p,\dhkr),\dhk) \smallskip\\ -\rid &\defas& \dlam x\, \dhk.\Let\; (\vhops \dcons \dk \dcons \dhk') = \dhk \;\In\; \dk \dapp x \dapp \dhk' -% \pcps{-} &:& \CompCat \to \UCompCat\\ -% \pcps{M} &\defas& \cps{M} \sapp (\reflect \kid \scons \reflect (\dlam x\,\dhk.x) \scons \reflect (\dlam z\,ks.\Absurd~z) \scons \snil) \\ -\end{equations} +\begin{lemma}\label{lem:sdtrans-subst} + Let $\sigma$ denote a substitution. The translation $\sdtrans{-}$ + commutes with substitution, i.e. + % + \[ + \sdtrans{V}\sdtrans{\sigma} = \sdtrans{V\sigma},\quad + \sdtrans{M}\sdtrans{\sigma} = \sdtrans{M\sigma},\quad + \sdtrans{H}\sdtrans{\sigma} = \sdtrans{H\sigma}. + \] + % +\end{lemma} % -The main difference between this translation rule and the translation -rule for deep handler operation clauses is the realisation of -resumptions. +\begin{proof} + By induction on the structures of $V$, $M$, and $H$. +\end{proof} % -Recall that a resumption is represented as a reversed slice of a -continuation. Thus the deconstruction of the resumption $\dhkr$ -effectively ensures that the current handler gets properly -uninstalled. However, it presents a new problem as the remainder -$\dhkr'$ is not a well-formed continuation slice, because the top -element is a pure continuation without a handler. +\begin{theorem} +If $\Delta; \Gamma \vdash M : C$ then $\sdtrans{\Delta}; +\sdtrans{\Gamma} \vdash \sdtrans{M} : \sdtrans{C}$. +\end{theorem} % +\begin{proof} + By induction on the typing derivations. +\end{proof} -To rectify this problem, we can insert a dummy identity handler -composed from $\hid$ and $\rid$. The effect continuation $\hid$ -forwards every operation, and the pure continuation $\rid$ is an -identity clause. Thus every operation and the return value will -effectively be handled by the next handler. -% -Unfortunately, insertion of such identity handlers lead to memory -leaks~\cite{Kiselyov12,HillerstromL18}. -% -% \dhil{Give the counting example} -% +\newcommand{\admin}{admin} +\newcommand{\approxa}{\gtrsim} -The use of identity handlers is symptomatic for the need of a more -general notion of resumptions. During resumption invocation the -dangling pure continuation should be composed with the current pure -continuation which suggests the need for a shallow variation of the -resumption construction primitive $\Res$. -% -\[ - \bl - \Let\; r = \Res^\dagger (\_ \dcons \_ \dcons \dk \dcons h_n^{\mathrm{ops}} \dcons h_n^{\mathrm{ret}} \dcons \dk_n \dcons \cdots \dcons h_1^{\mathrm{ops}} \dcons h_1^{\mathrm{ret}} \dcons \dk_1 \dcons \dnil)\;\In\;N \reducesto\\ - \quad N[(\dlam x\,\dhk. - \ba[t]{@{}l} - \Let\; (\dk' \dcons \dhk') = \dhk\;\In\\ - \dk_1 \dapp x \dapp (h_1^{\mathrm{ret}} \dcons h_1^{\mathrm{ops}} \cdots \dcons \dk_n \dcons h_n^{\mathrm{ret}} \dcons h_n^{\mathrm{ops}} \dcons (\dk' \circ \dk) \dcons \dhk'))/r] - \ea - \el -\] -% -where $\circ$ is defined to be function composition in continuation -passing style. +As with the implementation of deep handlers as shallow handlers, the +implementation is again given by a typeability-preserving local +translation. However, this time the administrative overhead is more +significant. Reduction up to congruence is insufficient and we require +a more semantic notion of administrative reduction. + +\begin{definition}[Administrative evaluation contexts]\label{def:admin-eval} + An evaluation context $\EC \in \EvalCat$ is administrative, + $\admin(\EC)$, when the following two criteria hold. +\begin{enumerate} +\item For all values $V \in \ValCat$, we have: $\EC[\Return\;V] \reducesto^\ast + \Return\;V$ +\item For all evaluation contexts $\EC' \in \EvalCat$, operations + $\ell \in \BL(\EC) \backslash \BL(\EC')$, and values + $V \in \ValCat$: % \[ - g \circ f \defas \lambda x\,\dhk. - \ba[t]{@{}l} - \Let\;(\dk \dcons \dhk') = \dhk\; \In\\ - f \dapp x \dapp ((\lambda x\,\dhk. g \dapp x \dapp (\dk \dcons \dhk)) \dcons \dhk') - \ea + \EC[\EC'[\Do\;\ell\;V]] \reducesto_\Cong^\ast \Let\; x \revto \Do\;\ell\;V \;\In\; \EC[\EC'[\Return\;x]]. \] +\end{enumerate} +\end{definition} % -The idea is that $\Res^\dagger$ uninstalls the appropriate handler and -composes the dangling pure continuation $\dk$ with the next -\emph{dynamically determined} pure continuation $\dk'$, and reverses -the remainder of the resumption and composes it with the modified -dynamic continuation ($(\dk' \circ \dk) \dcons ks'$). -% - -While the underlying idea is correct, this particular realisation of -the idea is problematic as the use of function composition -reintroduces a variation of the dynamic administrative redexes that we -dealt with in Section~\ref{sec:first-order-explicit-resump}. -% -In order to avoid generating these administrative redexes we need a -more intensional continuation representation. -% -Another telltale sign that we require a more intensional continuation -representation is the necessary use of the administrative function -$\kid$ in the translation of $\Handle$ as a placeholder for the empty -pure continuation. -% -In terms of aesthetics, the non-uniform continuation deconstructions -also suggest that we could benefit from a more structured -interpretation of continuations. +The intuition is that an administrative evaluation context behaves +like the empty evaluation context up to some amount of administrative +reduction, which can only proceed once the term in the context becomes +sufficiently evaluated. % -Although it is seductive to program with lists, it quickly gets -unwieldy. - -\subsection{Generalised continuations} -\label{sec:generalised-continuations} - -One problem is that the continuation representation used by the -higher-order uncurried translation for deep handlers is too -extensional to support shallow handlers efficiently. Specifically, the -representation of pure continuations needs to be more intensional to -enable composition of pure continuations without having to materialise -administrative continuation functions. +Values annihilate the evaluation context and handled operations are +forwarded. % +%% The forwarding handler is a technical device which allows us to state +%% the second property in a uniform way by ensuring that the operation is +%% handled at least once. -Another problem is that the continuation representation integrates the -return clause into the pure continuations, but the semantics of -shallow handlers demands that this return clause is discarded when any -of the operations is invoked. - -The solution to the first problem is to reuse the key idea of -Section~\ref{sec:first-order-explicit-resump} to avoid administrative -continuation functions by representing a pure continuation as an -explicit list consisting of pure continuation functions. As a result -the composition of pure continuation functions can be realised as a -simple cons-operation. +\begin{definition}[Approximation up to administrative reduction]\label{def:approx-admin} +Define $\approxa$ as the compatible closure of the following inference +rules. % +\begin{mathpar} + \inferrule* + { } + {M \approxa M} -The solution to the second problem is to pair the continuation -functions corresponding to the $\Return$-clause and operation clauses -in order to distinguish the pure continuation function induced by a -$\Return$-clause from those induced by $\Let$-expressions. -% + \inferrule* + {M \reducesto M' \\ M' \approxa N} + {M \approxa N} -Plugging these two solutions yields the notion of \emph{generalised - continuations}. A generalised continuation is a list of -\emph{continuation frames}. A continuation frame is a triple -$\Record{fs, \Record{\vhret, \vhops}}$, where $fs$ is list of stack -frames representing the pure continuation for the computation -occurring between the current execution and the handler, $\vhret$ is -the (translation of the) return clause of the enclosing handler, and -$\vhops$ is the (translation of the) operation clauses. + \inferrule* + {\admin(\EC) \\ M \approxa N} + {\EC[M] \approxa N} +\end{mathpar} % - -The change of representation of pure continuations does mean that we -can no longer invoke them by simple function application. Instead, we -must inspect the structure of the pure continuation $fs$ and act -appropriately. To ease notation it is convenient introduce a new -computation form for pure continuation application $\kapp\;V\;W$ that -feeds a value $W$ into the continuation represented by $V$. There are -two reduction rules. +We say that $M$ approximates $N$ up to administrative reduction if $M +\approxa N$. +\end{definition} % -\begin{reductions} - \usemlab{KAppNil} - & \kapp\;(\dRecord{\dnil, \dRecord{\vhret, \vhops}} \dcons \dhk)\,W - & \reducesto - & \vhret\,W\,\dhk - \\ - \usemlab{KAppCons} - & \kapp\;(\dRecord{f \cons fs, h} \dcons \dhk)\,W - & \reducesto - & f\,W\,(\dRecord{fs, h} \dcons \dhk) -\end{reductions} +Approximation up to administrative reduction captures the property +that administrative reduction may occur anywhere within a term. % -%\dhil{Say something about skip frames?} +The following lemma states that the forwarding component of the +translation is administrative. % -The first rule describes what happens when the pure continuation is -exhausted and the return clause of the enclosing handler is -invoked. The second rule describes the case when the pure continuation -has at least one element: this pure continuation function is invoked -and the remainder of the continuation is passed in as the new -continuation. - -We must also change how resumptions (i.e. reversed continuations) are -converted into functions that can be applied. Resumptions for deep -handlers ($\Res\,V$) are similar to -Section~\ref{sec:first-order-explicit-resump}, except that we now use -$\kapp$ to invoke the continuation. Resumptions for shallow handlers -($\Res^\dagger\,V$) are more complex. Instead of taking all the frames -and reverse appending them to the current stack, we remove the current -handler $h$ and move the pure continuation -($f_1 \dcons \dots \dcons f_m \dcons \dnil$) into the next frame. This -captures the intended behaviour of shallow handlers: they are removed -from the stack once they have been invoked. The following two -reduction rules describe their behaviour. +\begin{lemma}\label{lem:sdtrans-admin} +For all shallow handlers $H$, the following context is administrative % \[ - \ba{@{}l@{\quad}l} - \usemlab{Res} - & \Let\;r=\Res\,(V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N - \reducesto N[\dlam x\, \dhk.\kapp\;(V_1 \dcons \dots V_n \dcons \dhk)\,x/r] \\ - \usemlab{Res^\dagger} - & \Let\;r=\Res^\dagger\,(\dRecord{f_1 \dcons \dots \dcons f_m \dcons \nil, h} \dcons V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N \reducesto \\ - & \qquad N[\dlam x\,\dhk.\bl - \Let\,\dRecord{fs',h'} \dcons \dhk' = \dhk\;\In\;\\ - \kapp\,(V_1 \dcons \dots \dcons V_n \dcons \dRecord{f_1 \dcons \dots \dcons f_m \dcons fs', h'} \dcons \dhk')\,x/r] - \el - \ea + \Let\; z \revto + \Handle\; [~] \;\With\; \sdtrans{H}\; + \In\; + \Let\; \Record{f;\_} = z\; \In\; f\,\Unit. \] + % +\end{lemma} % -These constructs along with their reduction rules are -macro-expressible in terms of the existing constructs. -% -I choose here to treat them as primitives in order to keep the -presentation relatively concise. - -Essentially, a generalised continuation amounts to a sort of -\emph{defunctionalised} continuation, where $\kapp$ acts as an -interpreter for the continuation -structure~\cite{Reynolds98a,DanvyN01}. - -\subsection{Dynamic terms: the target calculus revisited} -\label{sec:target-calculus-revisited} - -\begin{figure}[t] -\textbf{Syntax} -\begin{syntax} -\slab{Values} &V, W \in \UValCat &::= & x \mid \dlam x\,\dhk.M \mid \Rec\,g\,x\,\dhk.M \mid \ell \mid \dRecord{V, W} -\smallskip \\ -\slab{Computations} &M,N \in \UCompCat &::= & V \mid U \dapp V \dapp W \mid \Let\; \dRecord{x, y} = V \; \In \; N \\ -& &\mid& \Case\; V\, \{\ell \mapsto M; x \mapsto N\} \mid \Absurd\;V\\ -& &\mid& \kapp\,V\,W \mid \Let\;r=\Res^\depth\;V\;\In\;M -\end{syntax} -\textbf{Syntactic sugar} -\begin{displaymath} -\bl -\begin{eqs} -\Let\; x = V \;\In\; N &\equiv& N[V/x] \\ -\ell\;V &\equiv& \dRecord{\ell, V} \\ -\end{eqs} -\qquad -\begin{eqs} -\Record{} &\equiv& \ell_{\Record{}} \\ -\Record{\ell=V; W} &\equiv& \ell\;\dRecord{V, W} \\ -\end{eqs} -\qquad -\begin{eqs} -\dnil &\equiv& \ell_{\dnil} \\ -V \dcons W &\equiv& \ell_{\dcons}\;\dRecord{V, W} \\ -\end{eqs} -\smallskip \\ -\ba{@{}c@{\quad}c@{}} -\Case\;V\;\{\ell\,x \mapsto M; y \mapsto N\} \equiv \\ - \qquad \bl - \Let\; y=V \;\In\; - \Let\; \dRecord{z, x} = y \;\In \\ - \Case\;z\;\{\ell \mapsto M; z \mapsto N\} \\ - \el \\ -\ea -\qquad -\ba{@{}l@{\quad}l@{}} -\Let\;\Record{\ell=x; y} = V \;\In\; N \equiv \\ - \qquad \bl - \Let\; \dRecord{z, z'} = V \;\In\; - \Let\; \dRecord{x, y} = z' \;\In \\ - \Case\; z \;\{\ell \mapsto N; z \mapsto \ell_{\bot}\} \\ - \el \\ -\ea \\ -\el -\end{displaymath} -% -\textbf{Standard reductions} -% -\begin{reductions} -%% Standard reductions -\usemlab{App} & (\dlam x\,\dhk.M) \dapp V \dapp W &\reducesto& M[V/x, W/\dhk] \\ -\usemlab{Rec} & (\Rec\,g\,x\,\dhk.M) \dapp V \dapp W &\reducesto& M[\Rec\,g\,x\,\dhk.M/g, V/x, W/\dhk] \smallskip\\ -\usemlab{Split} & \Let \; \dRecord{x, y} = \dRecord{V, W} \; \In \; N &\reducesto& N[V/x, W/y] \\ -\usemlab{Case_1} & - \Case \; \ell \,\{ \ell \; \mapsto M; x \mapsto N\} &\reducesto& M \\ -\usemlab{Case_2} & - \Case \; \ell \,\{ \ell' \; \mapsto M; x \mapsto N\} &\reducesto& N[\ell/x], \hfill\quad \text{if } \ell \neq \ell' \smallskip\\ -\end{reductions} -% -\textbf{Continuation reductions} -% -\begin{reductions} -\usemlab{KAppNil} & - \kapp \; (\dRecord{\dnil, \dRecord{v, e}} \dcons \dhk) \, V &\reducesto& v \dapp V \dapp \dhk \\ -\usemlab{KAppCons} & - \kapp \; (\dRecord{\dlf \dcons \dlk, h} \dcons \dhk) \, V &\reducesto& \dlf \dapp V \dapp (\dRecord{\dlk, h} \dcons \dhk) \\ -\end{reductions} -% -\textbf{Resumption reductions} -% -\[ -\ba{@{}l@{\quad}l@{}} -\usemlab{Res} & -\Let\;r=\Res(V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N \reducesto \\ -&\quad N[\dlam x\,\dhk. \bl\Let\;\dRecord{fs, \dRecord{\vhret, \vhops}}\dcons \dhk' = \dhk\;\In\\ - \kapp\;(V_1 \dcons \dots \dcons V_n \dcons \dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhk')\;x/r]\el -\\ -\usemlab{Res^\dagger} & -\Let\;r=\Res^\dagger(\dRecord{\dlf_1 \dcons \dots \dcons \dlf_m \dcons \dnil, h} \dcons V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N \reducesto\\ - & \quad N[\dlam x\,k. \bl - \Let\;\dRecord{\dlk', h'} \dcons \dhk' = \dhk \;\In \\ - \kapp\;(V_1 \dcons \dots \dcons V_n \dcons \dRecord{\dlf_1 \dcons \dots \dcons \dlf_m \dcons \dlk', h'} \dcons \dhk')\;x/r] \\ - \el -\ea -\] +\begin{proof} + We have to check both conditions of Definition~\ref{def:admin-eval}. + \begin{enumerate} + \item Follows by direct calculation. + % \item We need to show that for all $V \in \ValCat$ + % % + % \[ + % \Let\; z \revto + % \Handle\; \Return\;V \;\With\; \sdtrans{H}\; + % \In\; + % \Let\; \Record{f;\_} = z\; \In\; f\,\Unit \reducesto^\ast \Return\;V. + % \] + % % + % We show this by direct calculation using the assumption $\hret = \{\Return\;x \mapsto N\}$. + % \begin{derivation} + % &\Let\; z \revto + % \Handle\; \Return\;V \;\With\; \sdtrans{H}\; + % \In\; + % \Let\; \Record{f;\_} = z\; \In\; f\,\Unit\\ + % \reducesto^+& \reason{\semlab{Lift}, \semlab{Ret}, \semlab{Let}, \semlab{Split}}\\ + % % &\Let\;\Record{f;\_} = \Record{\lambda\Unit.\Return\;x;\lambda\Unit.\sdtrans{N}}\;\In\;f\,\Unit + % & (\lambda\Unit.\Return\;V)\,\Unit \reducesto \Return\;V + % \end{derivation} + \item % We need to show that for all evaluation contexts + % $\EC' \in \EvalCat$, operations + % $\ell \in \BL(\EC) \backslash \BL(\EC')$, and values + % $V \in \ValCat$ + % % + % \[ + % \ba{@{~}l@{~}l} + % &\Let\; z \revto + % \Handle\; \EC'[\Do\;\ell\;V]\;\With\;\sdtrans{H} \;\In\;\Record{f;\_} = z\;\In\;f\,\Unit\\ + % \reducesto_\Cong^\ast& \Let\; x \revto \Do\;\ell\;V \;\In\; \Let\; z \revto \Handle\; \EC'[\Return\;x]\;\With\;\sdtrans{H}\;\In\;\Let\;\Record{f;\_} = z\;\In\;f\,\Unit + % \ea + % \] + % + Follows by direct calculation using the assumption that + $\ell \notin \BL(\EC')$. + \begin{derivation} + &\Let\; z \revto + \Handle\; \EC'[\Do\;\ell\;V]\;\With\;\sdtrans{H}\;\In\; + \Let\;\Record{f;\_} = z\;\In\;f\,\Unit\\ + \reducesto& \reason{\semlab{Op} using assumption $\ell \notin \BL(\EC')$}\\ + &\bl \Let\; z \revto + \bl + \Let\;r\revto + \lambda x.\bl\Let\;z \revto (\lambda x.\Handle\;\EC'[\Return\;x]\;\With\;\sdtrans{H})~x\\ + \In\;\Let\;\Record{f;g} = z\;\In\;f\,\Unit + \el\\ + \In\;\Return\;\Record{\lambda\Unit.\Let\;x\revto \Do\;\ell~V\;\In\;r~x;\lambda\Unit.\sdtrans{N}} + \el\\ + \In\;\Record{f;\_} = z\;\In\;f\,\Unit + \el\\ + \reducesto^+& \reason{\semlab{Let}, \semlab{Split}, \semlab{App}}\\ + &(\Let\;x\revto \Do\;\ell~V\;\In\;r~x)[(\lambda x.\bl + \Let\;z \revto (\lambda x.\Handle\;\EC'[\Return\;x]\;\With\;\sdtrans{H})\,x\\ + \In\;\Let\;\Record{f;g} = z\;\In\;f\,\Unit)/r]\el\\ + \reducesto_\Cong &\reason{\semlab{App} tail position reduction}\\ + &\bl + \Let\;x \revto \Do\;\ell~V\;\In\;\Let\;z \revto (\lambda x.\Handle\;\EC'[\Return\;x]\;\With\;\sdtrans{H})\,x\; \In\\\Let\;\Record{f;g} = z\;\In\;f\,\Unit + \el\\ + \reducesto_\Cong& \reason{\semlab{App} reduction under binder}\\ + &\bl \Let\;x \revto \Do\;\ell~V\;\In\;\Let\;z \revto \Handle\;\EC'[\Return\;x]\;\With\;\sdtrans{H}\; \In\\\Let\;\Record{f;g} = z\;\In\;f\,\Unit\el + \end{derivation} + \end{enumerate} +\end{proof} % -\caption{Untyped target calculus supporting generalised continuations.} -\label{fig:cps-target-gen-conts} -\end{figure} - -Let us revisit the target -calculus. Figure~\ref{fig:cps-target-gen-conts} depicts the untyped -target calculus with support for generalised continuations. +\begin{theorem}[Simulation up to administrative reduction] +If $M' \approxa \sdtrans{M}$ and $M \reducesto N$ then there exists +$N'$ such that $N' \approxa \sdtrans{N}$ and $M' \reducesto^+ N'$. +\end{theorem} % -This is essentially the same as the target calculus used for the -higher-order uncurried CPS translation for deep effect handlers in -Section~\ref{sec:higher-order-uncurried-deep-handlers-cps}, except for -the addition of recursive functions. The calculus also includes the -$\kapp$ and $\Let\;r=\Res^\depth\;V\;\In\;N$ constructs described in -Section~\ref{sec:generalised-continuations}. There is a small -difference in the reduction rules for the resumption constructs: for -deep resumptions we do an additional pattern match on the current -continuation ($\dhk$). This is required to make the simulation proof -for the CPS translation with generalised continuations -(Section~\ref{sec:cps-gen-conts}) go through, because it makes the -functions that resumptions get converted to have the same shape as the -translation of source level functions -- this is required because the -operational semantics does not treat resumptions as distinct -first-class objects, but rather as a special kinds of functions. +\begin{proof} + % By case analysis on $\reducesto$ and induction on $\approxa$ using + % Lemma~\ref{lem:sdtrans-subst} and Lemma~\ref{lem:sdtrans-admin}. + % + By induction on $M' \approxa \sdtrans{M}$ and case analysis on + $M \reducesto N$ using Lemma~\ref{lem:sdtrans-subst} and + Lemma~\ref{lem:sdtrans-admin}. + % + The interesting case is reflexivity of $\approxa$ where + $M \reducesto N$ is an application of $\semlab{Op^\dagger}$, which + we will show. -\subsection{Translation with generalised continuations} -\label{sec:cps-gen-conts} -% -\begin{figure} -% -\textbf{Values} -% -\begin{equations} - \cps{-} &:& \ValCat \to \UValCat\\ - \cps{x} &\defas& x\\ - \cps{\lambda x.M} &\defas& \dlam x\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{M} \sapp (\reflect\dk \scons \reflect \dhk') \\ - \cps{\Lambda \alpha.M} &\defas& \dlam \Unit\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{M} \sapp (\reflect\dk \scons \reflect \dhk') \\ - \cps{\Rec\,g\,x.M} &\defas& \Rec\,g\,x\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{M} \sapp (\reflect\dk \scons \reflect \dhk') \\ - \multicolumn{3}{c}{ - \cps{\Record{}} \defas \Record{} - \qquad - \cps{\Record{\ell = \!\!V; W}} \defas \Record{\ell = \!\cps{V}; \cps{W}} - \qquad - \cps{\ell\,V} \defas \ell\,\cps{V} - } -\end{equations} -% -\textbf{Computations} -% -\begin{equations} -\cps{-} &:& \CompCat \to \SValCat^\ast \to \UCompCat\\ -\cps{V\,W} &\defas& \slam \shk.\cps{V} \dapp \cps{W} \dapp \reify \shk \\ -\cps{V\,T} &\defas& \slam \shk.\cps{V} \dapp \Record{} \dapp \reify \shk \\ -\cps{\Let\; \Record{\ell=x;y} = V \; \In \; N} &\defas& \slam \shk.\Let\; \Record{\ell=x;y} = \cps{V} \; \In \; \cps{N} \sapp \shk \\ -\cps{\Case~V~\{\ell~x \mapsto M; y \mapsto N\}} &\defas& - \slam \shk.\Case~\cps{V}~\{\ell~x \mapsto \cps{M} \sapp \shk; y \mapsto \cps{N} \sapp \shk\} \\ -\cps{\Absurd~V} &\defas& \slam \shk.\Absurd~\cps{V} \\ -\end{equations} -\begin{equations} -\cps{\Return\,V} &\defas& \slam \shk.\kapp\;(\reify \shk)\;\cps{V} \\ -\cps{\Let~x \revto M~\In~N} &\defas& - \bl\slam \sRecord{\shf, \sv} \scons \shk. - \ba[t]{@{}l} - \cps{M} \sapp (\sRecord{\bl\reflect((\dlam x\,\dhk.\bl\Let\;(\dk \dcons \dhk') = \dhk\;\In\\ - \cps{N} \sapp (\reflect\dk \scons \reflect \dhk')) \el\\ - \dcons \reify\shf), \sv} \scons \shk)\el - \ea - \el\\ -\cps{\Do\;\ell\;V} &\defas& - \slam \sRecord{\shf, \sRecord{\svhret, \svhops}} \scons \shk.\, - \reify\svhops \bl\dapp \dRecord{\ell,\dRecord{\cps{V}, \dRecord{\reify \shf, \dRecord{\reify\svhret, \reify\svhops}} \dcons \dnil}}\\ - \dapp \reify \shk\el \\ -\cps{\Handle^\depth \, M \; \With \; H} &\defas& -\slam \shk . \cps{M} \sapp (\sRecord{\snil, \sRecord{\reflect \cps{\hret}, \reflect \cps{\hops}^\depth}} \scons \shk) \\ -\end{equations} -% -\textbf{Handler definitions} -% -\begin{equations} -\cps{-} &:& \HandlerCat \to \UValCat\\ -% \cps{H}^\depth &=& \sRecord{\reflect \cps{\hret}, \reflect \cps{\hops}^\depth}\\ -\cps{\{\Return \; x \mapsto N\}} &\defas& \dlam x\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{N} \sapp (\reflect\dk \scons \reflect \dhk') \\ -\cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}}^\depth -&\defas& -\dlam \dRecord{z,\dRecord{p,\dhkr}}\,\dhk. - \Case \;z\; \{ - \ba[t]{@{}l@{}c@{~}l} - (&\ell &\mapsto - \ba[t]{@{}l} - \Let\;r=\Res^\depth\,\dhkr\;\In\; \\ - \Let\;(\dk \dcons \dhk') = \dhk\;\In\\ - \cps{N_{\ell}} \sapp (\reflect\dk \scons \reflect \dhk'))_{\ell \in \mathcal{L}} - \ea\\ - &y &\mapsto \hforward((y, p, \dhkr), \dhk) \} \\ - \ea \\ -\hforward((y, p, \dhkr), \dhk) &\defas& \bl - \Let\; \dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk \;\In \\ - \vhops \dapp \dRecord{y,\dRecord{p, \dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhkr}} \dapp \dhk' \\ - \el -\end{equations} - -\textbf{Top-level program} -% -\begin{equations} -\pcps{-} &:& \CompCat \to \UCompCat\\ -\pcps{M} &\defas& \cps{M} \sapp (\sRecord{\snil, \sRecord{\reflect \dlam x\,\dhk. x, \reflect \dlam \dRecord{z,\dRecord{p,\dhkr}}\,\dhk.\Absurd~z}} \scons \snil) \\ -\end{equations} -% -\caption{Higher-order uncurried CPS translation for effect handlers.} -\label{fig:cps-higher-order-uncurried-simul} -\end{figure} -% - -The CPS translation is given in -Figure~\ref{fig:cps-higher-order-uncurried-simul}. In essence, it is -the same as the CPS translation for deep effect handlers as described -in Section~\ref{sec:higher-order-uncurried-deep-handlers-cps}, though -it is adjusted to account for generalised continuation -representation. For notational convenience, we write $\chi$ to denote -a statically known effect continuation frame -$\sRecord{\svhret,\svhops}$. -% -The translation of $\Return$ invokes the continuation $\shk$ using the -continuation application primitive $\kapp$. -% -The translations of deep and shallow handlers differ only in their use -of the resumption construction primitive. - -A major aesthetic improvement due to the generalised continuation -representation is that continuation construction and deconstruction -are now uniform: only a single continuation frame is inspected at a -time. - -\subsubsection{Correctness} -\label{sec:cps-gen-cont-correctness} -% -The correctness of this CPS translation -(Theorem~\ref{thm:ho-simulation-gen-cont}) follows closely the -correctness result for the higher-order uncurried CPS translation for -deep handlers (Theorem~\ref{thm:ho-simulation}). Save for the -syntactic difference, the most notable difference is the extension of -the operation handling lemma (Lemma~\ref{lem:handle-op-gen-cont}) to -cover shallow handling in addition to deep handling. Each lemma is -stated in terms of static continuations, where the shape of the top -element is always known statically, i.e., it is of the form -$\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons -\sW$. Moreover, the static values $\sV_{fs}$, $\sV_{\mret}$, and -$\sV_{\mops}$ are all reflected dynamic terms (i.e., of the form -$\reflect V$). This fact is used implicitly in the proofs. For brevity -we write $\sV_f$ to denote a statically known continuation frame -$\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}}$. The full -proof details are given in Appendix~\ref{sec:proofs-cps-gen-cont}. - -% -\begin{lemma}[Substitution]\label{lem:subst-gen-cont} + In the reflexivity case we have $M' \approxa \sdtrans{M}$, where + $M = \ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H$ and + $N = N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]$ such that + $M \reducesto N$ where $\ell \notin \BL(\EC)$ and + $H^\ell = \{\OpCase{\ell}{p}{r} \mapsto N_\ell\}$. % - The CPS translation commutes with substitution in value terms + Hence by reflexivity of $\approxa$ we have + $M' = \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}$. Now we + can compute $N'$ by direct calculation starting from $M'$ yielding + \begin{derivation} + & \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}\\ + =& \reason{definition of $\sdtrans{-}$}\\ + &\bl + \Let\;z \revto \Handle\;\sdtrans{\EC}[\Do\;\ell~\sdtrans{V}]\;\With\;\sdtrans{H}\;\In\\ + \Let\;\Record{f;g} = z\;\In\;g\,\Unit + \el\\ + \reducesto^+& \reason{\semlab{Op} using assumption $\ell \notin \BL(\sdtrans{\EC})$, \semlab{Let}, \semlab{Let}}\\ + &\bl + \Let\;\Record{f;g} = \Record{ + \bl + \lambda\Unit.\Let\;x \revto \Do\;\ell~\sdtrans{V}\;\In\;r~x;\\ + \lambda\Unit.\sdtrans{N_\ell}}[\lambda x. + \bl + \Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ + \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,\sdtrans{V}/p]\;\In\; g\,\Unit + \el + \el\\ + \el\\ + \reducesto^+ &\reason{\semlab{Split}, \semlab{App}}\\ + &\sdtrans{N_\ell}[\lambda x. + \bl + \Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ + \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,\sdtrans{V}/p] + \el\\ + =& \reason{by Lemma~\ref{lem:sdtrans-subst}}\\ + &\sdtrans{N_\ell[\lambda x. + \bl + \Let\;z \revto (\lambda y.\Handle\;\EC[\Return\;y]\;\With\;H)~x\;\In\\ + \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,V/p]} + \el + \end{derivation} % - \[ - \cps{W}[\cps{V}/x] = \cps{W[V/x]}, - \] + Take the final term to be $N'$. If the resumption + $r \notin \FV(N_\ell)$ then the two terms $N'$ and + $\sdtrans{N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]}$ are the + identical, and thus the result follows immediate by reflexivity of + the $\approxa$-relation. Otherwise the proof reduces to showing that + the larger resumption term simulates the smaller resumption term, + i.e (note we lift the $\approxa$-relation to value terms). % - and with substitution in computation terms \[ - \ba{@{}l@{~}l} - % &(\cps{M} \sapp (\sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW))[\cps{V}/x]\\ - % = &\cps{M[V/x]} \sapp (\sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons\sW)[\cps{V}/x], - &(\cps{M} \sapp (\sV_f \scons \sW))[\cps{V}/x]\\ - = &\cps{M[V/x]} \sapp (\sV_f \scons \sW)[\cps{V}/x], - \ea + (\bl + \lambda x.\Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ + \Let\;\Record{f;g} = z \;\In\;f\,\Unit) \approxa (\lambda y.\sdtrans{\EC}[\Return\;y]). + \el \] % - and with substitution in handler definitions + We use the congruence rules to apply a single $\semlab{App}$ on the + left hand side to obtain % - \begin{equations} - \cps{\hret}[\cps{V}/x] - &=& \cps{\hret[V/x]},\\ - \cps{\hops}[\cps{V}/x] - &=& \cps{\hops[V/x]}. - \end{equations} -\end{lemma} -% -In order to reason about the behaviour of the \semlab{Op} and -\semlab{Op^\dagger} rules, which are defined in terms of evaluation -contexts, we extend the CPS translation to evaluation contexts, using -the same translations as for the corresponding constructs in $\SCalc$. -% -\begin{equations} - \cps{[~]} - &=& \slam \shk. \shk \\ - \cps{\Let\; x \revto \EC \;\In\; N} - &=& - \begin{array}[t]{@{}l} - \slam \sRecord{\shf, \sv} \scons \shk.\\ - \quad \cps{\EC} \sapp (\bl\sRecord{\reflect((\dlam x\,\dhk.\bl\Let\;\dRecord{fs,\dRecord{\vhret,\vhops}}\dcons \dhk'=\dhk\;\In\\ - \cps{N} \sapp (\sRecord{\reflect fs, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect \dhk')) \dcons \reify\shf),\el\\ - \sv} \scons \shk)\el - \end{array} - \\ - \cps{\Handle^\depth\; \EC \;\With\; H} - &=& \slam \shk.\cps{\EC} \sapp (\sRecord{[], \cps{H}^\depth} \scons \shk) -\end{equations} -% -The following lemma is the characteristic property of the CPS -translation on evaluation contexts. -% -This allows us to focus on the computation within an evaluation -context. -% -\begin{lemma}[Evaluation context decomposition] - \label{lem:decomposition-gen-cont} - \[ - % \cps{\EC[M]} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW) - % = - % \cps{M} \sapp (\cps{\EC} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)) - \cps{\EC[M]} \sapp (\sV_f \scons \sW) - = - \cps{M} \sapp (\cps{\EC} \sapp (\sV_f \scons \sW)) - \] -\end{lemma} -% -By definition, reifying a reflected dynamic value is the identity -($\reify \reflect V = V$), but we also need to reason about the -inverse composition. As a result of the invariant that the translation -always has static access to the top of the handler stack, the -translated terms are insensitive to whether the remainder of the stack -is statically known or is a reflected version of a reified stack. This -is captured by the following lemma. The proof is by induction on the -structure of $M$ (after generalising the statement to stacks of -arbitrary depth), and relies on the observation that translated terms -either access the top of the handler stack, or reify the stack to use -dynamically, whereupon the distinction between reflected and reified -becomes void. Again, this lemma holds when the top of the static -continuation is known. -% -\begin{lemma}[Reflect after reify] - \label{lem:reflect-after-reify-gen-cont} -% \[ - % \cps{M} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \reflect \reify \sW) - % = - % \cps{M} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW). - \cps{M} \sapp (\sV_f \scons \reflect \reify \sW) - = - \cps{M} \sapp (\sV_f \scons \sW). + (\bl + \lambda x.\Let\;z \revto \Handle\;\sdtrans{\EC}[\Return\;x]\;\With\;\sdtrans{H}\;\In\\ + \Let\;\Record{f;g} = z \;\In\;f\,\Unit) \approxa (\lambda y.\sdtrans{\EC}[\Return\;y]). + \el \] -\end{lemma} - -The next lemma states that the CPS translation correctly simulates -forwarding. The proof is by inspection of how the translation of -operation clauses treats non-handled operations. -% -\begin{lemma}[Forwarding]\label{lem:forwarding-gen-cont} - If $\ell \notin dom(H_1)$ then: + % + Now the trick is to define the following context % \[ - \bl - \cps{\hops_1}^\delta \dapp \dRecord{\ell, \dRecord{V_p, V_{\dhkr}}} \dapp (\dRecord{V_{fs}, \cps{H_2}^\delta} \dcons W) - \reducesto^+ \qquad \\ - \hfill - \cps{\hops_2}^\delta \dapp \dRecord{\ell, \dRecord{V_p, \dRecord{V_{fs}, \cps{H_2}^\delta} \dcons V_{\dhkr}}} \dapp W. \\ - \el + \EC' \defas \Let\;z \revto \Handle\; [\,]\;\With\;\sdtrans{H}\;\In\;\Let\;\Record{f;g} = z \;\In\;f\,\Unit. \] % -\end{lemma} - -The following lemma is central to our simulation theorem. It -characterises the sense in which the translation respects the handling -of operations. Note how the values substituted for the resumption -variable $r$ in both cases are in the image of the translation of -$\lambda$-terms in the CPS translation. This is thanks to the precise -way that the reductions rules for resumption construction works in our -dynamic language, as described above. -% -\begin{lemma}[Handling]\label{lem:handle-op-gen-cont} -Suppose $\ell \notin BL(\EC)$ and $\hell = \{\ell\,p\,r \mapsto N_\ell\}$. If $H$ is deep then - % - % \[ - % \bl - % \cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}} \scons \sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)) \reducesto^+ \\ - % \quad (\cps{N_\ell} \sapp \sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)\\ - % \qquad \quad [\cps{V}/p, - % \dlam x\,\dhk.\bl - % \Let\;\dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk\;\In\;\\ - % \cps{\Return\;x} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}} \scons \sRecord{\reflect \dlk, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect\dhk'))/r]. \\ - % \el\\ - % \el - % \] - \[ - \bl - \cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}} \scons \sV_f \scons \sW)) \reducesto^+ \\ - \quad (\cps{N_\ell} \sapp (\sV_f \scons \sW)) - [\cps{V}/p, - \dlam x\,\dhk.\bl - \Let\;\dRecord{fs, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk\;\In\; - \cps{\Return\;x}\\ - \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}} \scons \sRecord{\reflect \dlk, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect\dhk'))/r]. \\ - \el\\ - \el - \] - % - Otherwise if $H$ is shallow then + The context $\EC'$ is an administrative evaluation context by + Lemma~\ref{lem:sdtrans-admin}. Now it follows by + Defintion~\ref{def:approx-admin} that + $(\lambda x.\EC'[\sdtrans{\EC}[\Return\;x]]) \approxa + (\lambda y.\sdtrans{\EC}[\Return\;y])$. % + % \noindent\textbf{Case} $\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H \reducesto + % N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]$ where + % $\ell \notin \BL(\EC)$ and + % $H^\ell = \{\OpCase{\ell}{p}{r} \mapsto N_\ell\}$. \smallskip\\ + % There are three subcases to consider. + % \begin{enumerate} + % \item Base step: + % $M' = \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}$. We + % can compute $N'$ by direct calculation starting from $M'$ yielding + % % + % % \begin{derivation} + % % & \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}\\ + % % =\reducesto^+& \reason{\semlab{Op} ($\ell \notin \BL(\sdtrans{\EC})$), $2\times$\semlab{Let},\semlab{Split},\semlab{App}, Lemma~\ref{lem:sdtrans-subst}}\\ + % % &\sdtrans{N_\ell[\lambda x. + % % \bl + % % \Let\;z \revto (\lambda y.\Handle\;\EC[\Return\;y]\;\With\;H)~x\;\In\\ + % % \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,V/p]} + % % \el\\ + % % \end{derivation} + % \begin{derivation} + % & \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}\\ + % =& \reason{definition of $\sdtrans{-}$}\\ + % &\bl + % \Let\;z \revto \Handle\;\sdtrans{\EC}[\Do\;\ell~\sdtrans{V}]\;\With\;\sdtrans{H}\;\In\\ + % \Let\;\Record{f;g} = z\;\In\;g\,\Unit + % \el\\ + % \reducesto^+& \reason{\semlab{Op} using assumption $\ell \notin \BL(\sdtrans{\EC})$, \semlab{Let}, \semlab{Let}}\\ + % % &\bl + % % \Let\;z \revto + % % (\bl + % % \Let\;r \revto \lambda x.\Let\;z \revto r~x\;\In\;\Let\;\Record{f;g} = z\;\In\;f\,\Unit\;\In\\ + % % \Return\;\Record{ + % % \bl + % % \lambda\Unit.\Let\;x \revto \Do\;\ell~p\;\In\;r~x;\\ + % % \lambda\Unit.\sdtrans{N_\ell}})[ + % % \bl + % % \sdtrans{V}/p,\\ + % % \lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H}/r] + % % \el + % % \el + % % \el\\ + % % \In\;\Let\;\Record{f;g} = z\;\In\;g\,\Unit + % % \el\\ + % % \reducesto^+& \reason{\semlab{Let}, \semlab{Let}}\\ + % &\bl + % \Let\;\Record{f;g} = \Record{ + % \bl + % \lambda\Unit.\Let\;x \revto \Do\;\ell~\sdtrans{V}\;\In\;r~x;\\ + % \lambda\Unit.\sdtrans{N_\ell}}[\lambda x. + % \bl + % \Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ + % \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,\sdtrans{V}/p]\;\In\; g\,\Unit + % \el + % \el\\ + % \el\\ + % \reducesto^+ &\reason{\semlab{Split}, \semlab{App}}\\ + % &\sdtrans{N_\ell}[\lambda x. + % \bl + % \Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ + % \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,\sdtrans{V}/p] + % \el\\ + % =& \reason{by Lemma~\ref{lem:sdtrans-subst}}\\ + % &\sdtrans{N_\ell[\lambda x. + % \bl + % \Let\;z \revto (\lambda y.\Handle\;\EC[\Return\;y]\;\With\;H)~x\;\In\\ + % \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,V/p]} + % \el + % \end{derivation} + % % + % Take the final term to be $N'$. If the resumption + % $r \notin \FV(N_\ell)$ then the two terms $N'$ and + % $\sdtrans{N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]}$ are the + % identical, and thus the result follows immediate by reflexivity of + % the $\approxa$-relation. Otherwise $N'$ approximates + % $N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]$ at least up to a use of + % $r$. We need to show that the approximation remains faithful during + % any application of $r$. Specifically, we proceed to show that for + % any value $W \in \ValCat$ + % % % \[ - % \bl - % \cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}^\dagger} \scons \sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)) \reducesto^+ \\ - % \quad (\cps{N_\ell} \sapp \sRecord{\sV_{fs},\sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW)\\ - % \qquad [\cps{V}/p, \dlam x\,\dhk. \bl - % \Let\;\dRecord{\dlk, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk \;\In \\ - % \cps{\Return\;x} \sapp (\cps{\EC} \sapp (\sRecord{\reflect \dlk, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect\dhk'))/r]. \\ - % \el \\ + % (\bl + % \lambda x.\Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ + % \Let\;\Record{f;g} = z \;\In\;f\,\Unit)~W \approxa (\lambda y.\sdtrans{\EC}[\Return\;y])~W. % \el % \] - \[ - \bl - \cps{\Do\;\ell\;V} \sapp (\cps{\EC} \sapp (\sRecord{\snil, \cps{H}^\dagger} \scons \sV_f \scons \sW)) \reducesto^+ \\ - \quad (\cps{N_\ell} \sapp (\sV_f \scons \sW)) - [\cps{V}/p, \dlam x\,\dhk. \bl - \Let\;\dRecord{\dlk, \dRecord{\vhret, \vhops}} \dcons \dhk' = \dhk \;\In\;\cps{\Return\;x}\\ - \sapp (\cps{\EC} \sapp (\sRecord{\reflect \dlk, \sRecord{\reflect \vhret, \reflect \vhops}} \scons \reflect\dhk'))/r]. \\ - \el \\ - \el - \] - % -\end{lemma} - -\medskip - -With the aid of the above lemmas we can state and prove the main -result for the translation: a simulation result in the style of -\citet{Plotkin75}. -% -\begin{theorem}[Simulation] - \label{thm:ho-simulation-gen-cont} - If $M \reducesto N$ then - \[ - \cps{M} \sapp (\sRecord{\sV_{fs}, \sRecord{\sV_{\mret},\sV_{\mops}}} - \scons \sW) \reducesto^+ \cps{N} \sapp (\sRecord{\sV_{fs}, - \sRecord{\sV_{\mret},\sV_{\mops}}} \scons \sW). - \] -\end{theorem} + % % + % The right hand side reduces to $\sdtrans{\EC}[\Return\;W]$. Two + % applications of \semlab{App} on the left hand side yield the term + % % + % \[ + % \Let\;z \revto \Handle\;\sdtrans{\EC}[\Return\;W]\;\With\;\sdtrans{H}\;\In\;\Let\;\Record{f;g} = z \;\In\;f\,\Unit. + % \] + % % + % Define + % % + % $\EC' \defas \Let\;z \revto \Handle\; [\,]\;\With\;\sdtrans{H}\;\In\;\Let\;\Record{f;g} = z \;\In\;f\,\Unit$ + % % + % such that $\EC'$ is an administrative evaluation context by + % Lemma~\ref{lem:sdtrans-admin}. Then it follows by + % Defintion~\ref{def:approx-admin} that + % $\EC'[\sdtrans{\EC}[\Return\;W]] \approxa + % \sdtrans{\EC}[\Return\;W]$. -\begin{proof} - The proof is by case analysis on the reduction relation using Lemmas - \ref{lem:decomposition-gen-cont}--\ref{lem:handle-op-gen-cont}. In - particular, the \semlab{Op} and \semlab{Op^\dagger} cases follow - from Lemma~\ref{lem:handle-op-gen-cont}. + % \item Inductive step: Assume $M' \reducesto M''$ and + % $M'' \approxa \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}$. Using a similar argument to above we get that + % \[ + % \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H} + % \reducesto^+ \sdtrans{N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]}. + % \] + % Take $N' = M''$ then by the first induction hypothesis + % $M' \reducesto N'$ and by the second induction hypothesis + % $N' \approxa \sdtrans{N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]}$ as + % desired. + % \item Inductive step: Assume $admin(\EC')$ and + % $M' \approxa \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}$. + % % + % By the induction the hypothesis $M' \reducesto N''$. Take + % $N' = \EC'[N'']$. The result follows by an application of the + % admin rule. + % \item Compatibility step: We check every syntax constructor, + % however, since the relation is compositional\dots + % \end{enumerate} \end{proof} +% \begin{proof} +% By case analysis on $\reducesto$ using Lemma~\ref{lem:sdtrans-subst} +% and Lemma~\ref{lem:sdtrans-admin}. We show only the interesting case +% $\semlab{Op^\dagger}$, which uses Lemma~\ref{lem:sdtrans-admin} to +% approximate the body of the resumption up to administrative +% reduction.\smallskip -In common with most CPS translations, full abstraction does not hold -(a function could count the number of handlers it is invoked within by -examining the continuation, for example). However, as the semantics is -deterministic it is straightforward to show a backward simulation -result. -% -\begin{lemma}[Backwards simulation] - If $\pcps{M} \reducesto^+ V$ then there exists $W$ - such that $M \reducesto^\ast W$ and $\pcps{W} = V$. -\end{lemma} -% -\begin{corollary} -$M \reducesto^\ast V$ if and only if $\pcps{M} \reducesto^\ast \pcps{V}$. -\end{corollary} - -\section{Transforming parameterised handlers} -\label{sec:cps-param} -% -\begin{figure} -% \textbf{Continuation reductions} -% % -% \begin{reductions} -% \usemlab{KAppNil} & -% \kapp \; (\dRecord{\dnil, \dRecord{q, v, e}} \dcons \dhk) \, V &\reducesto& v \dapp \dRecord{q,V} \dapp \dhk \\ -% \usemlab{KAppCons} & -% \kapp \; (\dRecord{\dlf \dcons \dlk, h} \dcons \dhk) \, V &\reducesto& \dlf \dapp V \dapp (\dRecord{\dlk, h} \dcons \dhk) \\ -% \end{reductions} -% % -% \textbf{Resumption reductions} -% % -% \[ -% \ba{@{}l@{\quad}l@{}} -% \usemlab{Res}^\ddag & -% \Let\;r=\Res(V_n \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N \reducesto \\ -% &\quad N[\dlam x\,\dhk. \bl\Let\;\dRecord{fs, \dRecord{q,\vhret, \vhops}}\dcons \dhk' = \dhk\;\In\\ -% \kapp\;(V_1 \dcons \dots \dcons V_n \dcons \dRecord{fs, \dRecord{q,\vhret, \vhops}} \dcons \dhk')\;x/r]\el -% \\ -% \ea -% \] -% -\textbf{Computations} -% -\begin{equations} -\cps{-} &:& \CompCat \to \SValCat^\ast \to \UCompCat\\ -% \cps{\Let~x \revto M~\In~N} &\defas& -% \bl\slam \sRecord{\shf, \sRecord{\xi, \svhret, \svhops}} \scons \shk. -% \ba[t]{@{}l} -% \cps{M} \sapp (\sRecord{\bl\reflect((\dlam x\,\dhk.\bl\Let\;(\dk \dcons \dhk') = \dhk\;\In\\ -% \cps{N} \sapp (\reflect\dk \scons \reflect \dhk')) \el\\ -% \dcons \reify\shf), \sRecord{\xi, \svhret, \svhops}} \scons \shk)\el -% \ea -% \el\\ -\cps{\Do\;\ell\;V} &\defas& - \slam \sRecord{\shf, \sRecord{\xi, \svhret, \svhops}} \scons \shk.\, - \reify\svhops \bl\dapp \dRecord{\reify\xi, \ell, - \dRecord{\bl - \cps{V}, \dRecord{\reify \shf, \dRecord{\reify\xi,\reify\svhret, \reify\svhops}}\\ - \dcons \dnil}} - \dapp \reify \shk\el\el \\ -\end{equations} -\begin{equations} -\cps{\ParamHandle \, M \; \With \; (q.H)(W)} &\defas& -\slam \shk . \cps{M} \sapp (\sRecord{\snil, \sRecord{\reflect\cps{W},\reflect \cps{\hret}^{\ddag}_q, \reflect \cps{\hops}^{\ddag}_q}} \scons \shk) \\ -\end{equations} -\textbf{Handler definitions} -% -\begin{equations} -\cps{-} &:& \HandlerCat \times \UValCat \to \UValCat\\ -% \cps{H}^\depth &=& \sRecord{\reflect \cps{\hret}, \reflect \cps{\hops}^\depth}\\ -\cps{\{\Return \; x \mapsto N\}}^{\ddag}_q &\defas& \dlam \dRecord{q,x}\,\dhk.\Let\;(\dk \dcons \dhk') = \dhk\;\In\;\cps{N} \sapp (\reflect\dk \scons \reflect \dhk') \\ -\cps{\{(\ell \; p \; r \mapsto N_\ell)_{\ell \in \mathcal{L}}\}}^{\ddag}_q -&\defas& -\dlam \dRecord{q,z,\dRecord{p,\dhkr}}\,\dhk. - \Case \;z\; \{ - \ba[t]{@{}l@{}c@{~}l} - (&\ell &\mapsto - \ba[t]{@{}l} - \Let\;r=\Res^\ddag\,\dhkr\;\In\; \\ - \Let\;(\dk \dcons \dhk') = \dhk\;\In\\ - \cps{N_{\ell}} \sapp (\reflect\dk \scons \reflect \dhk'))_{\ell \in \mathcal{L}} - \ea\\ - &y &\mapsto \hforward((y, p, \dhkr), \dhk) \} \\ - \ea \\ -\hforward((y, p, \dhkr), \dhk) &\defas& \bl - \Let\; \dRecord{fs, \dRecord{q, \vhret, \vhops}} \dcons \dhk' = \dhk \;\In \\ - \vhops \dapp \dRecord{q, y,\dRecord{p, \dRecord{fs, \dRecord{q,\vhret, \vhops}} \dcons \dhkr}} \dapp \dhk' \\ - \el -\end{equations} - -\textbf{Top-level program} -\begin{equations} -\pcps{M} &=& \cps{M} \sapp (\sRecord{\dnil, \sRecord{\reflect\dRecord{},\reflect \dlam \dRecord{q,x}\,\dhk. x, \reflect \dlam \dRecord{q,z}\,\dhk.\Absurd~z}} \scons \snil) \\ -\end{equations} - -\caption{CPS translation for parameterised handlers.} -\label{fig:param-cps} -\end{figure} +% \noindent\textbf{Case} $\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H \reducesto +% N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]$ where +% $\ell \notin \BL(\EC)$ and +% $H^\ell = \{\OpCase{\ell}{p}{r} \mapsto N_\ell\}$. \smallskip\\ +% % +% Pick $M' = +% \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}$. Clearly +% $M' \approxa \sdtrans{M}$. We compute $N'$ via reduction as follows. +% \begin{derivation} +% & \sdtrans{\ShallowHandle\;\EC[\Do\;\ell~V]\;\With\;H}\\ +% =& \reason{definition of $\sdtrans{-}$}\\ +% &\bl +% \Let\;z \revto \Handle\;\sdtrans{\EC}[\Do\;\ell~\sdtrans{V}]\;\With\;\sdtrans{H}\;\In\\ +% \Let\;\Record{f;g} = z\;\In\;g\,\Unit +% \el\\ +% \reducesto^+& \reason{\semlab{Op} using assumption $\ell \notin \BL(\sdtrans{\EC})$, \semlab{Let}, \semlab{Let}}\\ +% % &\bl +% % \Let\;z \revto +% % (\bl +% % \Let\;r \revto \lambda x.\Let\;z \revto r~x\;\In\;\Let\;\Record{f;g} = z\;\In\;f\,\Unit\;\In\\ +% % \Return\;\Record{ +% % \bl +% % \lambda\Unit.\Let\;x \revto \Do\;\ell~p\;\In\;r~x;\\ +% % \lambda\Unit.\sdtrans{N_\ell}})[ +% % \bl +% % \sdtrans{V}/p,\\ +% % \lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H}/r] +% % \el +% % \el +% % \el\\ +% % \In\;\Let\;\Record{f;g} = z\;\In\;g\,\Unit +% % \el\\ +% % \reducesto^+& \reason{\semlab{Let}, \semlab{Let}}\\ +% &\bl +% \Let\;\Record{f;g} = \Record{ +% \bl +% \lambda\Unit.\Let\;x \revto \Do\;\ell~\sdtrans{V}\;\In\;r~x;\\ +% \lambda\Unit.\sdtrans{N_\ell}}[\lambda x. +% \bl +% \Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ +% \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,\sdtrans{V}/p]\;\In\; g\,\Unit +% \el +% \el\\ +% \el\\ +% \reducesto^+ &\reason{\semlab{Split}, \semlab{App}}\\ +% &\sdtrans{N_\ell}[\lambda x. +% \bl +% \Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ +% \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,\sdtrans{V}/p] +% \el\\ +% =& \reason{by Lemma~\ref{lem:sdtrans-subst}}\\ +% &\sdtrans{N_\ell[\lambda x. +% \bl +% \Let\;z \revto (\lambda y.\Handle\;\EC[\Return\;y]\;\With\;H)~x\;\In\\ +% \Let\;\Record{f;g} = z\;\In\;f\,\Unit/r,V/p]} +% \el +% \end{derivation} +% % +% We take the above computation term to be our $N'$. If +% $r \notin \FV(N_\ell)$ then the two terms $N'$ and +% $\sdtrans{N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]}$ are the +% identical, and thus by reflexivity of the $\approxa$-relation it +% follows that +% $N' \approxa \sdtrans{N_\ell[V/p,\lambda +% y.\EC[\Return\;y]/r]}$. Otherwise $N'$ approximates +% $N_\ell[V/p,\lambda y.\EC[\Return\;y]/r]$ at least up to a use of +% $r$. We need to show that the approximation remains faithful during +% any application of $r$. Specifically, we proceed to show that for +% any value $W \in \ValCat$ +% % +% \[ +% (\bl +% \lambda x.\Let\;z \revto (\lambda y.\Handle\;\sdtrans{\EC}[\Return\;y]\;\With\;\sdtrans{H})~x\;\In\\ +% \Let\;\Record{f;g} = z \;\In\;f\,\Unit)~W \approxa (\lambda y.\sdtrans{\EC}[\Return\;y])~W. +% \el +% \] +% % +% The right hand side reduces to $\sdtrans{\EC}[\Return\;W]$. Two +% applications of \semlab{App} on the left hand side yield the term +% % +% \[ +% \Let\;z \revto \Handle\;\sdtrans{\EC}[\Return\;W]\;\With\;\sdtrans{H}\;\In\;\Let\;\Record{f;g} = z \;\In\;f\,\Unit. +% \] +% % +% Define +% % +% $\EC' \defas \Let\;z \revto \Handle\; [\,]\;\With\;\sdtrans{H}\;\In\;\Let\;\Record{f;g} = z \;\In\;f\,\Unit$ +% % +% such that $\EC'$ is an administrative evaluation context by +% Lemma~\ref{lem:sdtrans-admin}. Then it follows by +% Defintion~\ref{def:approx-admin} that +% $\EC'[\sdtrans{\EC}[\Return\;W]] \approxa +% \sdtrans{\EC}[\Return\;W]$. +% \end{proof} -Generalised continuations provide a versatile implementation strategy -for effect handlers as exemplified in the previous section. In this -section we add further emphasis on the versatility of generalised -continuations by demonstrating how to adapt the continuation structure -to accommodate parameterised handlers. In order to support -parameterised handlers, each effect continuation must store the -current value of the handler parameter. Thus, an effect continuation -becomes a triple consisting of the parameter, return clause, and -operation clause(s). Furthermore, the return clause gets transformed -into a binary function, that takes the current value of the handler -parameter as its first argument and the return value of the handled -computation as its second argument. Similarly, the operation clauses -are transformed into a binary function, that takes the handler -parameter first and the operation package second. This strategy -effectively amounts to explicit state passing as the parameter value -gets threaded through every handler continuation -function. Operationally, the pure continuation invocation rule -$\usemlab{KAppNil}$ requires a small adjustment to account for the -handler parameter. +\section{Parameterised handlers as ordinary deep handlers} +\label{sec:param-desugaring} +\newcommand{\PD}[1]{\mathcal{P}\cps{#1}} % -\[ - \kapp \; (\dRecord{\dnil, \dRecord{q, \vhret, \vhops}} \dcons \dhk) \, V \reducesto \vhret \dapp \Record{q,V} \dapp \dhk -\] +As mentioned in Section~\ref{sec:unary-parameterised-handlers}, +parameterised handlers codify the parameter-passing idiom. They may be +seen as an optimised form of parameter-passing deep handlers. We now +show formally that parameterised handlers are special instances of +ordinary deep handlers. % -The pure continuation $v$ is now applied to a pair consisting of the -current value of the handler parameter $q$ and the return value -$V$. Similarly, the resumption rule $\usemlab{Res}$ must also be -adapted to update the value of the handler parameter. +We define a local transformation $\PD{-}$ which translates +parameterised handlers into ordinary deep handlers. Formally, the +translation is defined on terms, types, environments, and +substitutions. We omit the homomorphic cases and show only the +interesting cases. % \[ \bl - \Let\;r=\Res^\ddag\,(\dRecord{q,\vhret,\vhops} \dcons \dots \dcons V_1 \dcons \dnil)\;\In\;N\reducesto\\ - \qquad N[\dlam \dRecord{q',x}\,\dhk.\kapp\;(V_1 \dcons \dots \dcons \dRecord{q',\vhret,\vhops} \dcons \dhk)\;x/r] - \el + \PD{-} : \HandlerTypeCat \to \HandlerTypeCat\\ + \PD{\Record{C; A} \Rightarrow^\param B \eff E} \defas \PD{C} \Rightarrow (\PD{A} \to \PD{B \eff E})\eff \PD{E} \medskip\\ + \PD{-} : \CompCat \to \CompCat\\ + \PD{\ParamHandle\;M\;\With\;(q.\,H)(W)} \defas \left(\Handle\; \PD{M}\; \With\; \PD{H}_q\right)~\PD{W} \medskip\\ + \PD{-} : \HandlerCat \times \ValCat \to \HandlerCat\\ + \ba{@{}l@{~}c@{~}l} + \PD{\{\Return\;x \mapsto M\}}_q &\defas& \{\Return\;x \mapsto \lambda q. \PD{M}\}\\ + \PD{\{\OpCase{\ell}{p}{r} \mapsto M\}}_q &\defas& + \{\OpCase{\ell}{p}{r} \mapsto \lambda q. + \Let\; r \revto \Return\;\lambda \Record{x;q'}. r~x~q'\; + \In\; \PD{M}\} + \ea + \el \] % -The rule is not much different from the original $\usemlab{Res}$ -rule. The difference is that this rule unpacks the current handler -parameter $q$ along with the return clause, $\vhret$, and operation -clauses, $\vhops$. The reduction constructs a resumption function, -whose first parameter $q'$ binds the updated value of the handler -parameter. The $q'$ is packaged with the original $\vhret$ and -$\vhops$ such that the next activation of the handler gets the -parameter value $q'$ rather than $q$. +The parameterised $\ParamHandle$ construct becomes an application of a +$\Handle$ construct to the translation of the parameter. The +translation of $\Return$ and operation clauses are parameterised by +the name of the handler parameter as each clause body is enclosed in a +$\lambda$-abstraction whose formal parameter is the handler parameter +$q$. As a result the ordinary deep resumption $r$ is a curried +function. However, the uses of $r$ in $M$ expects a binary +function. To repair this discrepancy, we construct an uncurried +interface of $r$ by embedding it under a binary $\lambda$-abstraction. +% -The CPS translation is updated accordingly to account for the triple -effect continuation structure. This involves updating the cases that -scrutinise the effect continuation structure as it now includes the -additional state value. The cases that need to be updated are shown in -Figure~\ref{fig:param-cps}. We write $\xi$ to denote static handler -parameters. +To illustrate the translation in action consider the following example +program that adds the results obtained by performing two invocations +of some stateful operation $\dec{Incr} : \UnitType \opto \Int$, which +increments some global counter and returns its prior value. % -% The translation of $\Let$ unpacks and repacks the effect continuation -% to maintain the continuation length invariant. -The translation of $\Do$ invokes the effect continuation -$\reify \svhops$ with a triple consisting of the value of the handler -parameter, the operation, and the operation payload. The parameter is -also pushed onto the reversed resumption stack. This is necessary to -account for the case where the effect continuation $\reify \svhops$ -does not handle operation $\ell$. -% An alternative option is to push the parameter back -% on the resumption stack during effect forwarding. However that means -% the resumption stack will be nonuniform as the top element sometimes -% will be a pair. +\[ + \ba{@{~}l@{~}l} + &\mathcal{P}\left\llbracket + \ba[m]{@{}l} + \ParamHandle\;\Do\;\dec{Incr}\,\Unit + \Do\;\dec{Incr}\,\Unit\;\With\\ + \left( q.\ba[m]{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& \Return\;\Record{x;q}\\ + \OpCase{\dec{Incr}}{\Unit}{r} &\mapsto& r\,\Record{q;q+1} + \ea\right)~40 + \ea + \right\rrbracket \medskip\\ + =& \left( + \ba[m]{@{}l} + \Handle\;\Do\;\dec{Incr}\,\Unit + \Do\;\dec{Incr}\,\Unit\;\With\\ + \quad\ba[t]{@{~}l@{~}c@{~}l} + \Return\;x &\mapsto& \lambda q.\Return\;\Record{x;q}\\ + \OpCase{\dec{Incr}}{\Unit}{r} &\mapsto& \lambda q. \Let\;r \revto \Return\;\lambda\Record{x;q}.r~x~q\;\In\;r\,\Record{q;q+1} + \ea + \ea\right)~40 + \ea +\] % +Evaluation of the program on either side of the equals sign yields +$\Record{81;42} : \Int$. The translation desugars the parameterised +handler into an ordinary deep handler that makes use of the +parameter-passing idiom to maintain the state of the handled +computation~\cite{Pretnar15}. -The translation of the return and operation clauses are parameterised -by the name of the binder for the handler parameter. Each translation -yields functions that take a pair as input in addition to the current -continuation. The forwarding case is adjusted in the same way as the -translation for $\Do$. The current continuation $k$ is deconstructed -in order to identify the next effect continuation $\vhops$ and its -parameter $q$. Then $\vhops$ is invoked with the updated resumption -stack and the value of its parameter $q$. +The translation commutes with substitution and preserves typeability. % -The top-level translation adds a `dummy' unit value, which is ignored -by both the pure continuation and effect continuation.% The amended -% CPS translation for parameterised handlers is not a zero cost -% translation for shallow and ordinary deep handlers as they will have -% to thread a ``dummy'' parameter value through. -We can avoid the use of such values entirely if the target language -had proper sums to tag effect continuation frames -accordingly. Obviously, this entails performing a case analysis every -time an effect continuation frame is deconstructed. - -\section{Related work} -\label{sec:cps-related-work} - -\paragraph{CPS transforms for effect handlers} +\begin{lemma}\label{lem:pd-subst} + Let $\sigma$ denote a substitution. The translation $\PD{-}$ + commutes with substitution, i.e. + % + \[ + \PD{V}\PD{\sigma} = \PD{V\sigma},\quad + \PD{M}\PD{\sigma} = \PD{M\sigma},\quad + \PD{(q.\,H)}\PD{\sigma} = \PD{(q.\,H)\sigma}. + \] + % +\end{lemma} % -The one-pass higher-order CPS translation for deep, shallow, and -parameterised handlers draws on insights from the literature on CPS -translations for delimited control operators such as shift and -reset~\citep{DanvyF90,DanvyF92,DanvyN03,MaterzokB12}. +\begin{proof} + By induction on the structures of $V$, $M$, and $q.H$. +\end{proof} % -% \citet{DybvigJS07} develop a lean monadic framework for implementing -% multi-prompt delimited continuations. -% \paragraph{CPS translations for handlers} +\begin{theorem} +If $\Delta; \Gamma \vdash M : C$ then $\PD{\Delta}; +\sdtrans{\Gamma} \vdash \PD{M} : \PD{C}$. +\end{theorem} % -Other CPS translations for handlers use a monadic approach. For -example, \citet{Leijen17} implements deep and parameterised handlers -in Koka~\citep{Leijen14} by translating them into a free monad -primitive in the runtime. \citeauthor{Leijen17} uses a selective CPS -translation to lift code into the monad. The selective aspect is -important in practice to avoid overhead in code that does not use -effect handlers. +\begin{proof} + By induction on the typing derivations. +\end{proof} % -Scala Effekt~\citep{BrachthauserS17,BrachthauserSO20} provides an -implementation of effect handlers as a library for the Scala -programming language. The implementation is based closely on the -monadic delimited control framework of \citet{DybvigJS07}. +This translation of parameterised handlers simulates the native +semantics. As with the simulation of deep handlers via shallow +handlers in Section~\ref{sec:deep-as-shallow}, this simulation is not +quite on the nose as the image simulates the source only up to +congruence due to the need for an application of a pure function to a +variable to be reduced. % -A variation of the Scala Effekt library is used to implement effect -handlers as an interface for programming with delimited continuations -in Java~\citep{BrachthauserSO18}. The implementation of delimited -continuations depend on special byte code instructions, inserted via a -selective type-driven CPS translation. - -The Effekt language (which is distinct from the Effekt library) -implements handlers by a translation into capability-passing style, -which may more informatively be dubbed \emph{handler-passing style} as -handlers are passed downwards to the invocation sites of their -respective operations~\cite{SchusterBO20,BrachthauserSO20b}. The -translation into capability-passing style is realised by way of a -effect-type directed iterated CPS transform, which introduces a -continuation argument per handler in scope~\cite{SchusterBO20}. The -idea of iterated CPS is due to \citet{DanvyF90}, who used it to give -develop a CPS transform for shift and reset. +\begin{theorem}[Simulation up to congruence] + \label{thm:param-simulation} + If $M \reducesto N$ then $\PD{M} \reducesto^+_{\Cong} \PD{N}$. +\end{theorem} % -\citet{XieBHSL20} have devised an \emph{evidence-passing translation} -for deep effect handlers. The basic idea is similar to -capability-passing style as evidence for handlers are passed downwards -to their operations in shape of a vector containing the handlers in -scope through computations. \citet{XieL20} have realised handlers by -evidence-passing style as a Haskell library. +\begin{proof} + By case analysis on the relation $\reducesto$ using + Lemma~\ref{lem:pd-subst}. The interesting case is + \semlab{Op^\param}, which is where we need to reduce under the + $\lambda$-abstraction representing the parameterised resumption. + % \begin{description} + % \item[Case] + % $M = \ParamHandle\; \Return\;V\;\With\;(q.~H)(W) \reducesto + % N[V/x,W/q]$, where $\hret = \{ \Return\; x \mapsto N \}$. + % % + % \begin{derivation} + % &\PD{\ParamHandle\;\Return\;V\;\With\;(q.~H)(W)}\\ + % =& \reason{definition of $\PD{-}$}\\ + % &(\Handle\;\Return\;\PD{V}\;\With\;\PD{H}_q)~\PD{W}\\ + % \reducesto& \reason{$\semlab{Ret}$ with $\PD{\hret}_q = \{\Return~x \mapsto \lambda q. \PD{N}\}$}\\ + % &(\lambda q. \PD{N}[\PD{V}/x])~\PD{W}\\ + % \reducesto& \reason{$\semlab{App}$}\\ + % &\PD{N}[\PD{V}/x,\PD{W}/q]\\ + % =& \reason{lemma~\ref{lem:pd-subst}}\\ + % &\PD{N[V/x,W/q]} + % \end{derivation} + % \item[Case] $M = \ParamHandle\; \EC[\Do\;\ell~V]\;\With\;(q.~H)(W) \reducesto \\ + % \qquad + % N[V/p,W/q,\lambda \Record{x,q'}. \ParamHandle\;\EC[\Return\;x]\;\With\;(q.~H)(q')/r]$, where $\hell = \{ \OpCase{\ell}{p}{r} \mapsto N \}$. + \begin{derivation} + &\PD{\ParamHandle\;\EC[\Do\;\ell~V]\;\With\;(q.~H)(W)}\\ + =& \reason{definition of $\PD{-}$}\\ + &(\Handle\;\PD{\EC}[\Do\;\ell~\PD{V}]\;\With\;\PD{H}_q)~\PD{W}\\ + \reducesto& \reason{$\semlab{Op}$ with $\hell = \{\OpCase{\ell}{p}{r} \mapsto N\}$}\\ + &((\lambda q. \bl + \Let\;r \revto \lambda \Record{x;q'}. r~x~q\;\In \\ + \PD{N})[\PD{V}/p,\lambda x.\Handle\;\PD{\EC}[\Return\;x]\;\With\;\PD{H}_q/r])~\PD{W}\\ + \el \\ + =& \reason{definition of $[-]$}\\ + &(\lambda q. \bl + \Let\; r \revto \lambda \Record{x,q'}. (\lambda x. \Handle\;\PD{\EC}[\Return\;x]\;\With\; \PD{H}_q)~x~q'\;\In \\ + \PD{N}[\PD{V}/p])\,\PD{W}\\ + \el \\ + \reducesto& \reason{\semlab{App}}\\ + &\bl + \Let\; r \revto \lambda \Record{x;q'}. (\lambda x. \Handle\;\PD{\EC}[\Return\;x]\;\With\; \PD{H}_q)~x~q'\;\In\\ + \PD{N}[\PD{V}/p,\PD{W}/q] + \el\\ + \reducesto_\Cong& \reason{\semlab{App} under $\lambda\Record{x;q'}.\cdots$}\\ + &\bl + \Let\; r \revto \lambda \Record{x;q'}.(\Handle\;\PD{\EC}[\Return\;x]\;\With\; \PD{H}_q)~q'\;\In\\ + \PD{N}[\PD{V}/p,\PD{W}/q] + \el\\ + \reducesto& \reason{\semlab{Let}}\\ + &\PD{N}[\bl + \PD{V}/p,\PD{W}/q, \\ + \lambda \Record{x,q'}. (\Handle\;\PD{\EC}[\Return\;x]\;\With\; \PD{H}_q)~q'/r]\\ + \el \\ + =& \reason{definition of $\PD{-}$ and Lemma~\ref{lem:pd-subst}}\\ + &\PD{N[V/p,W/q,\lambda \Record{x,q'}. \ParamHandle\;\EC[\Return\;x]\;\With\; (q.~H)(q')/r]} + \end{derivation} + % \end{description} +\end{proof} +\section{Related work} -There are clear connections between the CPS translations presented in -this chapter and the continuation monad implementation of -\citet{KammarLO13}. Whereas \citeauthor{KammarLO13} present a -practical Haskell implementation depending on sophisticated features -such as type classes, which to some degree obscures the essential -structure, here we have focused on a foundational formal treatment. -% -\citeauthor{KammarLO13} obtain impressive performance results by -taking advantage of the second class nature of type classes in Haskell -coupled with the aggressive fusion optimisations GHC -performs~\citep{WuS15}. +Precisely how effect handlers fit into the landscape of programming +language features is largely unexplored in the literature. The most +relevant related work in this area is due to my collaborators and +myself on the inherited efficiency of effect handlers +(c.f. Chapter~\ref{ch:handlers-efficiency}) and \citet{ForsterKLP17}, who +investigate various relationships between effect handlers, delimited +control in the form of shift/reset, and monadic reflection using the +notions of typeability-preserving macro-expressiveness and untyped +macro-expressiveness~\cite{ForsterKLP17,ForsterKLP19}. They show that +in an untyped setting all three are interdefinable, whereas in a +simply typed setting effect handlers cannot macro-express +either. \citet{PirogPS19} build upon the work of +\citeauthor{ForsterKLP17} as they show that with sufficient +polymorphism effect handlers and delimited control can simulate one +another. -\paragraph{Plotkin's colon translation} -% -The original method for proving the correctness of a CPS -translation is by way of a simulation result. Simulation states that -every reduction sequence in a given source program is mimicked by its -CPS transformation. -% -Static administrative redexes in the image of a CPS translation -provide hurdles for proving simulation, since these redexes do not -arise in the source program. -% -\citet{Plotkin75} uses the so-called \emph{colon translation} to -overcome static administrative reductions. -% -Informally, it is defined such that given some source term $M$ and -some continuation $k$, then the term $M : k$ is the result of -performing all static administrative reductions on $\cps{M}\,k$, that -is to say $\cps{M}\,k \areducesto^* M : k$. +The work of \citet{Shan04,Shan07} is related in spirit to the work +presented in this chapter. \citeauthor{Shan04} shows that static and +dynamic notions of delimited control are interdefinable in an untyped +setting. The work in this chapter has a similar flavour to +\citeauthor{Shan04}'s work as we can view deep handlers as a kind of +static control facility and shallow handlers as a kind of dynamic +control facility. In order to simulate dynamic control using static +control, \citeauthor{Shan04}'s translation makes use of recursive +delimited continuations to construct the dynamic context surrounding +and including the invocation context. A recursive continuation allows +the captured context and continuation invocation context to coincide. + +% \chapter{Computability, complexity, and expressivness} +% \label{ch:expressiveness} +% \section{Notions of expressiveness} +% Felleisen's macro-expressiveness, Longley's type-respecting +% expressiveness, Kammar's typeability-preserving expressiveness. + +\chapter{Asymptotic speedup with effect handlers} +\label{ch:handlers-efficiency} % -Thus this translation makes it possible to bypass administrative -reductions and instead focus on the reductions inherited from the -source program. +When extending some programming language $\LLL \subset \LLL'$ with +some new feature it is desirable to know exactly how the new feature +impacts the language. At a bare minimum it is useful to know whether +the extended language $\LLL'$ is unsound as a result of inhabiting the +new feature (although, some languages are designed deliberately to be +unsound~\cite{BiermanAT14}). More fundamentally, it may be useful for +theoreticians and practitioners alike to know whether the extended +language is more expressive than the base language as it may inform +programming practice. % -The colon translation captures precisely the intuition that drives CPS -transforms, namely, that if in the source $M \reducesto^\ast \Return\;V$ -then in the image $\cps{M}\,k \reducesto^\ast k\,\cps{V}$. +Specifically, it may be of interest to know whether the extended +language $\LLL'$ exhibits any \emph{essential} expressivity when +compared to the base language $\LLL$. Questions about essential +expressivity fall under three different headings. -%\dhil{Check whether the first pass marks administrative redexes} +% There are various ways in which we can consider how some new feature +% impacts the expressiveness of its host language. For instance, +% \citet{Felleisen91} considers the question of whether a language +% $\LLL$ admits a translation into a sublanguage $\LLL'$ in a way which +% respects not only the behaviour of programs but also aspects of their +% global or local syntactic structure. If the translation of some +% $\LLL$-program into $\LLL'$ requires a complete global restructuring, +% we may say that $\LLL'$ is in some way less expressive than $\LLL$. -% CPS The colon translation captures the -% intuition tThe colon translation is itself a CPS translation which -% yields +% Effect handlers are capable of codifying a wealth of powerful +% programming constructs and features such as exceptions, state, +% backtracking, coroutines, await/async, inversion of control, and so +% on. +% +% Partial continuations as the difference of continuations a duumvirate of control operators -% In his seminal work, \citet{Plotkin75} devises CPS translations for -% call-by-value lambda calculus into call-by-name lambda calculus and -% vice versa. \citeauthor{Plotkin75} establishes the correctness of his -% translations by way of simulations, which is to say that every -% reduction sequence in a given source program is mimicked by the -% transformed program. +% Thus, effect handlers are expressive enough to implement a wide +% variety of other programming abstractions. % % -% His translations generate static administrative redexes, and as argued -% previously in this chapter from a practical view point this is an -% undesirable property in practice. However, it is also an undesirable -% property from a theoretical view point as the presence of -% administrative redexes interferes with the simulation proofs. - -% To handle the static administrative redexes, \citeauthor{Plotkin75} -% introduced the so-called \emph{colon translation} to bypass static -% administrative reductions, thus providing a means for focusing on -% reductions induced by abstractions inherited from the source program. -% % -% The colon translation is itself a CPS translation, that given a source -% expression, $e$, and some continuation, $K$, produces a CPS term such -% that $\cps{e}K \reducesto e : K$. - -% \citet{DanvyN03} used this insight to devise a one-pass CPS -% translation that contracts all administrative redexes at translation -% time. +% We may wonder about the exact nature of this expressiveness, i.e. do +% effect handlers exhibit any \emph{essential} expressivity? -% \paragraph{Partial evaluation} +% In today's programming languages we find a wealth of powerful +% constructs and features --- exceptions, higher-order store, dynamic +% method dispatch, coroutines, explicit continuations, concurrency +% features, Lisp-style `quote' and so on --- which may be present or +% absent in various combinations in any given language. There are of +% course many important pragmatic and stylistic differences between +% languages, but here we are concerned with whether languages may differ +% more essentially in their expressive power, according to the selection +% of features they contain. -\paragraph{ANF vs CPS} +% One can interpret this question in various ways. For instance, +% \citet{Felleisen91} considers the question of whether a language +% $\LLL$ admits a translation into a sublanguage $\LLL'$ in a way which +% respects not only the behaviour of programs but also aspects of their +% (global or local) syntactic structure. If the translation of some +% $\LLL$-program into $\LLL'$ requires a complete global restructuring, +% we may say that $\LLL'$ is in some way less expressive than $\LLL$. -\paragraph{Selective CPS transforms} -\citet{Nielsen01} \citet{DanvyH92} \citet{DanvyH93} \citet{Leijen17} +% Start with talking about the power of backtracking (folklore) +% giving a precise and robust mathematical characterisation of this phenomenon -\chapter{Abstract machine semantics} -\label{ch:abstract-machine} -%\dhil{The text is this chapter needs to be reworked} +% However, in this chapter we will look at even more fundamental +% expressivity differences that would not be bridged even if +% whole-program translations were admitted. These fall under two +% headings. -Abstract machine semantics are an operational semantics that makes -program control more apparent than context-based reduction -semantics. In a some sense abstract machine semantics are a lower -level semantics than reduction semantics as they provide a model of -computation based on \emph{abstract machines}, which capture some core -aspects of how actual computers might go about executing programs. +% Questions regarding essential expressivity differences fall under two +% headings. % -Abstract machines come in different style and flavours, though, a -common trait is that they are defined in terms of -\emph{configurations}. A configuration includes the essentials to -describe the machine state as it were, i.e. some abstract notion of -call stack, memory, program counter, etc. - -In this chapter I will demonstrate an application of generalised -continuations (Section~\ref{sec:generalised-continuations}) to -abstract machines that emphasises the usefulness of generalised -continuations to implement various kinds of effect handlers. The key -takeaway from this application is that it is possible to plug the -generalised continuation structure into a standard framework to -achieve a simultaneous implementation of deep, shallow, and -parameterised effect handlers. +\begin{description} +\item[Programmability] Are there programmable operations that can be + done more easily in $\LLL'$ than in $\LLL$? +\item[Computability] Are there operations of a given type + that are programmable in $\LLL'$ but not expressible at all in $\LLL$? +\item[Complexity] Are there operations programmable in $\LLL'$ + with some asymptotic runtime bound (e.g. `$\BigO(n^2)$') that cannot be + achieved in $\LLL$? +\end{description} % -Specifically I will change the continuation structure of a standard -\citeauthor{FelleisenF86} style \emph{CEK machine} to fit generalised -continuations. +% We may also ask: are there examples of \emph{natural, practically +% useful} operations that manifest such differences? If so, this +% might be considered as a significant advantage of $\LLL$ over $\LLL'$. -The CEK machine (CEK is an acronym for Control, Environment, -Kontinuation~\cite{FelleisenF86}) is an abstract machine with an -explicit environment, which models the idea that processor registers -name values as an environment associates names with values. Thus by -using the CEK formalism we depart from the substitution-based model of -computation used in the preceding chapters and move towards a more -`realistic' model of computation (realistic in the sense of emulating -how a computer executes a program). Another significant difference is -that in the CEK formalism evaluation contexts are no longer -syntactically intertwined with the source program. Instead evaluation -contexts are separately managed through the continuation of the CEK -machine. +% If the `operations' we are asking about are ordinary first-order +% functions, that is both their inputs and outputs are of ground type +% (strings, arbitrary-size integers etc), then the situation is easily +% summarised. At such types, all reasonable languages give rise to the +% same class of programmable functions, namely the Church-Turing +% computable ones. As for complexity, the runtime of a program is +% typically analysed with respect to some cost model for basic +% instructions (e.g.\ one unit of time per array access). Although the +% realism of such cost models in the asymptotic limit can be questioned +% (see, e.g., \citet[Section~2.6]{Knuth97}), it is broadly taken as read +% that such models are equally applicable whatever programming language +% we are working with, and moreover that all respectable languages can +% represent all algorithms of interest; thus, one does not expect the +% best achievable asymptotic run-time for a typical algorithm (say in +% number theory or graph theory) to be sensitive to the choice of +% programming language, except perhaps in marginal cases. -% In this chapter we will demonstrate an application of generalised -% continuations (Section~\ref{sec:generalised-continuations}) to -% \emph{abstract machines}. An abstract machine is a model of -% computation that makes program control more apparent than standard -% reduction semantics. Abstract machines come in different styles and -% flavours, though, a common trait is that they closely model how an -% actual computer might go about executing a program, meaning they -% embody some high-level abstract models of main memory and the -% instruction fetch-execute cycle of processors~\cite{BryantO03}. +% The situation changes radically, however, if we consider +% \emph{higher-order} operations: programmable operations whose inputs +% may themselves be programmable operations. Here it turns out that +% both what is computable and the efficiency with which it can be +% computed can be highly sensitive to the selection of language features +% present. This is in fact true more widely for \emph{abstract data +% types}, of which higher-order types can be seen as a special case: a +% higher-order value will be represented within the machine as ground +% data, but a program within the language typically has no access to +% this internal representation, and can interact with the value only by +% applying it to an argument. -\paragraph{Relation to prior work} The work in this chapter is based -on work in the following previously published papers. -% -\begin{enumerate}[i] - \item \bibentry{HillerstromL16} \label{en:ch-am-HL16} - \item \bibentry{HillerstromL18} \label{en:ch-am-HL18} - \item \bibentry{HillerstromLA20} \label{en:ch-am-HLA20} -\end{enumerate} -% -The particular presentation in this chapter is adapted from -item~\ref{en:ch-am-HLA20}. +% Most work in this area to date has focused on computability +% differences. One of the best known examples is the \emph{parallel if} +% operation which is computable in a language with parallel evaluation +% but not in a typical `sequential' programming +% language~\cite{Plotkin77}. It is also well known that the presence of +% control features or local state enables observational distinctions +% that cannot be made in a purely functional setting: for instance, +% there are programs involving call/cc that detect the order in which a +% (call-by-name) `+' operation evaluates its arguments +% \citep{CartwrightF92}. Such operations are `non-functional' in the +% sense that their output is not determined solely by the extension of +% their input (seen as a mathematical function +% $\N_\bot \times \N_\bot \rightarrow \N_\bot$); +% %% +% however, there are also programs with `functional' behaviour that can +% be implemented with control or local state but not without them +% \citep{Longley99}. More recent results have exhibited differences +% lower down in the language expressivity spectrum: for instance, in a +% purely functional setting \textit{\`a la} Haskell, the expressive +% power of \emph{recursion} increases strictly with its type level +% \citep{Longley18a}, and there are natural operations computable by +% low-order recursion but not by high-order iteration +% \citep{Longley19}. Much of this territory, including the mathematical +% theory of some of the natural notions of higher-order computability +% that arise in this way, is mapped out by \citet{LongleyN15}. -\section{Configurations with generalised continuations} -\label{sec:machine-configurations} -Syntactically, the CEK machine consists of three components: 1) the -control component, which focuses the term currently being evaluated; -2) the environment component, which maps free variables to machine -values, and 3) the continuation component, which describes what to -evaluate next (some literature uses the term `control string' in lieu -of continuation to disambiguate it from programmatic continuations in -the source language). -% -Intuitively, the continuation component captures the idea of call -stack from actual programming language implementations. +% Relatively few results of this character have so far been established +% on the complexity side. \citet{Pippenger96} gives an example of an +% `online' operation on infinite sequences of atomic symbols +% (essentially a function from streams to streams) such that the first +% $n$ output symbols can be produced within time $\BigO(n)$ if one is +% working in an `impure' version of Lisp (in which mutation of `cons' +% pairs is admitted), but with a worst-case runtime no better than +% $\Omega(n \log n)$ for any implementation in pure Lisp (without such +% mutation). This example was reconsidered by \citet{BirdJdM97} who +% showed that the same speedup can be achieved in a pure language by +% using lazy evaluation. Another candidate is the familiar $\log n$ +% overhead involved in implementing maps (supporting lookup and +% extension) in a pure functional language \cite{Okasaki99}, although to +% our knowledge this situation has not yet been subjected to theoretical +% scrutiny. \citet{Jones01} explores the approach of manifesting +% expressivity and efficiency differences between certain languages by +% artificially restricting attention to `cons-free' programs; in this +% setting, the classes of representable first-order functions for the +% various languages are found to coincide with some well-known +% complexity classes. -The abstract machine is formally defined in terms of configurations. A -configuration $\cek{M \mid \env \mid \shk \circ \shk'} \in \MConfCat$ -is a triple consisting of a computation term $M \in \CompCat$, an -environment $\env \in \MEnvCat$, and a pair of generalised -continuations $\kappa,\kappa' \in \MGContCat$. -% -The complete abstract machine syntax is given in -Figure~\ref{fig:abstract-machine-syntax-gencont}. -% -The control and environment components are completely standard as they -are similar to the components in \citeauthor{FelleisenF86}'s original -CEK machine modulo the syntax of the source language. -% -However, the structure of the continuation component is new. This -component comprises two generalised continuations, where the latter -continuation $\kappa'$ is an entirely administrative object that -materialises only during operation invocations as it is used to -construct the reified segment of the continuation up to an appropriate -enclosing handler. For the most part $\kappa'$ is empty, therefore we -will write $\cek{M \mid \env \mid \shk}$ as syntactic sugar for -$\cek{M \mid \env \mid \shk \circ []}$ where $[]$ is the empty -continuation (an alternative is to syntactically differentiate between -regular and administrative configurations by having both three-place -and four-place configurations as for example as \citet{BiernackaBD03} -do). +The purpose of this chapter is to give a clear example of an essential +complexity difference. Specifically, we will show that if we take a +typical PCF-like base language, $\BPCF$, and extend it with effect +handlers, $\HPCF$, then there exists a class of programs that have +asymptotically more efficient realisations in $\HPCF$ than possible in +$\BPCF$, hence establishing that effect handlers enable an asymptotic +speedup for some programs. % +% The purpose of this chapter is to give a clear example of such an +% inherent complexity difference higher up in the expressivity spectrum. -An environment is either empty, written $\emptyset$, or an extension -of some other environment $\env$, written $\env[x \mapsto v]$, where -$x$ is the name of a variable and $v$ is a machine value. +To this end, we consider the following \emph{generic count} problem, +parametric in $n$: given a boolean-valued predicate $P$ on the space +$\mathbb{B}^n$ of boolean vectors of length $n$, return the number of +such vectors $q$ for which $P\,q = \True$. We shall consider boolean +vectors of any length to be represented by the type $\Nat \to \Bool$; +thus for each $n$, we are asking for an implementation of a certain +third-order function. % -The machine values consist of function closures, recursive function -closures, type function closures, records, variants, and reified -continuations. The three abstraction forms are paired with an -environment that binds the free variables in the their bodies. The -records and variants are transliterated from the value forms of the -source calculi. Figure~\ref{fig:abstract-machine-val-interp} defines -the value interpretation function, which turns any source language -value into a corresponding machine value. +\[ \Count_n : ((\Nat \to \Bool) \to \Bool) \to \Nat \] % -A continuation $\shk$ is a stack of generalised continuation frames -$[\shf_1, \dots, \shf_n]$. As in -Section~\ref{sec:generalised-continuations} each continuation frame -$\shf = (\slk, \chi)$ consists of a pure continuation $\slk$, -corresponding to a sequence of let bindings, interpreted under some -handler, which in this context is represented by the handler closure -$\chi$. -% -A pure continuation is a stack of pure frames. A pure frame -$(\env, x, N)$ closes a let-binding $\Let \;x=[~] \;\In\;N$ over -environment $\env$. The pure continuation structure is similar to the -continuation structure of \citeauthor{FelleisenF86}'s original CEK -machine. -% -There are three kinds of handler closures, one for each kind of -handler. A deep handler closure is a pair $(\env, H)$ which closes a -deep handler definition $H$ over environment $\env$. Similarly, a -shallow handler closure $(\env, H^\dagger)$ closes a shallow handler -definition over environment $\env$. Finally, a parameterised handler -closure $(\env, (q.\,H))$ closes a parameterised handler definition -over environment $\env$. As a syntactic shorthand we write $H^\depth$ -to range over deep, shallow, and parameterised handler -definitions. Sometimes $H^\depth$ will range over just two kinds of -handler definitions; it will be clear from the context which handler -definition is omitted. -% -We extend the clause projection notation to handler closures and -generalised continuation frames, i.e. -% -\[ - \ba{@{~}l@{~}c@{~}l@{~}c@{~}l@{~}l} - \theta^{\mathrm{ret}} &\defas& (\sigma, \chi^{\mathrm{ret}}) &\defas& \hret, &\quad \text{where } \chi = (\env, H^\depth)\\ - \theta^{\ell} &\defas& (\sigma, \chi^{\ell}) &\defas& \hell, &\quad \text{where } \chi = (\env, H^\depth) - \el -\] -% -Values are annotated with types where appropriate to facilitate type -reconstruction in order to make the results of -Section~\ref{subsec:machine-correctness} easier to state. -% -% -\begin{figure}[t] -\flushleft -\begin{syntax} -\slab{Configurations} & \conf \in \MConfCat &::= & \cek{M \mid \env \mid \shk \circ \shk'} \\ -\slab{Value\textrm{ }environments} &\env \in \MEnvCat &::= & \emptyset \mid \env[x \mapsto v] \\ -\slab{Values} &v, w \in \MValCat &::= & (\env, \lambda x^A . M) \mid (\env, \Rec^{A \to C}\,x.M)\\ - & &\mid& (\env, \Lambda \alpha^K . M) \\ - & &\mid& \Record{} \mid \Record{\ell = v; w} \mid (\ell\, v)^R \\ - & &\mid& \shk^A \mid (\shk, \slk)^A \medskip\\ -\slab{Continuations} &\shk \in \MGContCat &::= & \nil \mid \shf \cons \shk \\ -\slab{Continuation\textrm{ }frames} &\shf \in \MGFrameCat &::= & (\slk, \chi) \\ -\slab{Pure\textrm{ }continuations} &\slk \in \MPContCat &::= & \nil \mid \slf \cons \slk \\ -\slab{Pure\textrm{ }continuation\textrm{ }frames} &\slf \in \MPFrameCat &::= & (\env, x, N) \\ -\slab{Handler\textrm{ }closures} &\chi \in \MHCloCat &::= & (\env, H) \mid (\env, H^\dagger) \mid (\env, (q.\,H)) \medskip \\ -\end{syntax} +A \naive implementation strategy is simply to apply $P$ to each of the +$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. +Nonetheless, under the mild condition that $P$ must inspect all $n$ +components of the given vector before returning, both these approaches +will have a $\Omega(n 2^n)$ runtime. Moreover, we shall show that in +$\BPCF$, a typical call-by-value language without advanced control +features, one cannot improve on this: \emph{any} implementation of +$\Count_n$ must necessarily take time $\Omega(n2^n)$ on \emph{any} +predicate $P$. Conversely, in the extended language $\HPCF$ it +becomes possible to bring the runtime down to $\BigO(2^n)$: an +asymptotic gain of a factor of $n$. -\caption{Abstract machine syntax.} -\label{fig:abstract-machine-syntax-gencont} -\end{figure} +% The \emph{generic search} problem is just like the generic count +% problem, except rather than counting the vectors $q$ such that $P\,q = +% \True$, it returns the list of all such vectors. +% % +% The $\Omega(n 2^n)$ runtime for purely functional implementations +% transfers directly to generic search, as generic count reduces to +% generic search composed with computing the length of the resulting +% list. +% % +% In Section~\ref{sec:count-vs-search} we illustrate that the +% $\BigO(2^n)$ runtime for generic count with effect handlers also +% transfers to generic search. + +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. % -\begin{figure} -\[ -\bl -\multicolumn{1}{c}{\val{-} : \ValCat \times \MEnvCat \to \MValCat}\\[1ex] -\ba[t]{@{}r@{~}c@{~}l@{}} -\val{x}{\env} &\defas& \env(x) \\ -\val{\lambda x^A.M}{\env} &\defas& (\env, \lambda x^A.M) \\ -\val{\Rec\,g^{A \to C}\,x.M}{\env} &\defas& (\env, \Rec\,g^{A \to C}\,x.M) \\ -\val{\Lambda \alpha^K.M}{\env} &\defas& (\env, \Lambda \alpha^K.M) \\ -\ea -\qquad -\ba[t]{@{}r@{~}c@{~}l@{}} -\val{\Record{}}{\env} &\defas& \Record{} \\ -\val{\Record{\ell = V; W}}{\env} &\defas& \Record{\ell = \val{V}{\env}; \val{W}{\env}} \\ -\val{(\ell\, V)^R}{\env} &\defas& (\ell\, \val{V}{\env})^R \\ -\ea -\el -\] - \caption{Value interpretation definition.} - \label{fig:abstract-machine-val-interp} -\end{figure} +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$. % - -\section{Generalised continuation-based machine semantics} -\label{sec:machine-transitions} +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$. 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. % -\begin{figure}[p] -\rotatebox{90}{ -\begin{minipage}{0.99\textheight}% -\[ -\bl -%\multicolumn{1}{c}{\stepsto \subseteq \MConfCat \times \MConfCat}\\[1ex] -\ba{@{}l@{\quad}r@{~}c@{~}l@{~~}l@{}} -% \mlab{Init} & \multicolumn{3}{@{}c@{}}{M \stepsto \cek{M \mid \emptyset \mid [(\nil, (\emptyset, \{\Return\;x \mapsto \Return\;x\}))]}} \\[1ex] -% App -&&\multicolumn{2}{@{}l}{\stepsto\, \subseteq\! \MConfCat \times \MConfCat}\\ -\mlab{App} & \cek{ V\;W \mid \env \mid \shk} - &\stepsto& \cek{ M \mid \env'[x \mapsto \val{W}{\env}] \mid \shk}, - &\text{if }\val{V}{\env} = (\env', \lambda x^A.M) \\ +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$. -\mlab{AppRec} & \cek{ V\;W \mid \env \mid \shk} - &\stepsto& \cek{ M \mid \env'[g \mapsto (\env', \Rec\,g^{A \to C}\,x.M), x \mapsto \val{W}{\env}] \mid \shk}, - &\text{if }\val{V}{\env} = (\env', \Rec\,g^{A \to C}\,x.M) \\ +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. -% TyApp -\mlab{AppType} & \cek{ V\,T \mid \env \mid \shk} - &\stepsto& \cek{ M[T/\alpha] \mid \env' \mid \shk}, - &\text{if }\val{V}{\env} = (\env', \Lambda \alpha^K . \, M) \\ +% 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 +% analysis of the efficiency achievable in various settings without +% getting bogged down in inessential implementation details, we shall +% work concretely and operationally with the languages in question, +% using a CEK-style abstract machine semantics as our basic model of +% execution time, and with some specific programs in these languages. +% In the first instance, we formulate our results as a comparison +% between a purely functional base language (a version of call-by-value +% PCF) and an extension with first-class control; we then indicate how +% these results can be extended to base languages with other features +% such as mutable state. -% Deep resumption application -\mlab{Resume} & \cek{ V\;W \mid \env \mid \shk} - &\stepsto& \cek{ \Return \; W \mid \env \mid \shk' \concat \shk}, - &\text{if }\val{V}{\env} = (\shk')^A \\ +\paragraph{Relation to prior work} This chapter is based entirely on +the following previously published paper. +\begin{enumerate} + \item[~] \bibentry{HillerstromLL20} +\end{enumerate} +The contents of Sections~\ref{sec:calculi}, +\ref{sec:abstract-machine-semantics}, \ref{sec:generic-search}, +\ref{sec:pure-counting}, \ref{sec:robustness}, and +\ref{sec:experiments} are almost verbatim copies of Sections 3, 4, 5, +6, 7, and 8 of the above paper. I have made a few stylistic +adjustments to make the Sections fit with the rest of this +dissertation. -% Shallow resumption application -\mlab{Resume^\dagger} & \cek{ V\,W \mid \env \mid (\slk, \chi) \cons \shk} - &\stepsto& - \cek{\Return\; W \mid \env \mid \shk' \concat ((\slk' \concat \slk, \chi) \cons \shk)}, - &\text{if } \val{V}{\env} = (\shk', \slk')^A \\ +% In summary, our purpose is to exhibit an efficiency gap which, in our +% view, manifests a fundamental feature of the programming language +% landscape, challenging a common assumption that all real-world +% programming languages are essentially `equivalent' from an asymptotic +% point of view. We believe that such results are important not only +% for a rounded understanding of the relative merits of existing +% languages, but also for informing future language design. -% Deep resumption application -\mlab{Resume^\param} & \cek{ V\,\Record{W;W'} \mid \env \mid \shk} - &\stepsto& \cek{ \Return \; W \mid \env \mid \shk' \concat [(\sigma,(\env'[q \mapsto \val{W'}\env],q.\,H))] \concat \shk},&\\ - &&&\quad\text{if }\val{V}{\env} = \shk' \concat [(\sigma,(\env',q.\,H))])^A \\ -% -\mlab{Split} & \cek{ \Let \; \Record{\ell = x;y} = V \; \In \; N \mid \env \mid \shk} - &\stepsto& \cek{ N \mid \env[x \mapsto v, y \mapsto w] \mid \shk}, - &\text{if }\val{V}{\env} = \Record{\ell=v; w} \\ +% For their convenience as structured delimited control operators we +% adopt effect handlers as our universal control abstraction of choice, +% but our results adapt mutatis mutandis to other first-class control +% abstractions such as `call/cc'~\cite{AbelsonHAKBOBPCRFRHSHW85}, `control' +% ($\mathcal{F}$) and 'prompt' ($\textbf{\#}$)~\citep{Felleisen88}, or +% `shift' and `reset'~\citep{DanvyF90}. -% Case -\mlab{Case} & \cek{ \Case\; V\, \{ \ell~x \mapsto M; y \mapsto N\} \mid \env \mid \shk} - &\stepsto& \left\{\ba{@{}l@{}} - \cek{ M \mid \env[x \mapsto v] \mid \shk}, \\ - \cek{ N \mid \env[y \mapsto \ell'\, v] \mid \shk}, \\ - \ea \right. - & - \ba{@{}l@{}} - \text{if }\val{V}{\env} = \ell\, v \\ - \text{if }\val{V}{\env} = \ell'\, v \text{ and } \ell \neq \ell' \\ - \ea \\ +% The rest of the paper is structured as follows. +% \begin{itemize} +% \item Section~\ref{sec:handlers-primer} provides an introduction to +% effect handlers as a programming abstraction. +% \item Section~\ref{sec:calculi} presents a PCF-like language +% $\BPCF$ and its extension $\HPCF$ with effect handlers. +% \item Section~\ref{sec:abstract-machine-semantics} defines abstract +% machines for $\BPCF$ and $\HPCF$, yielding a runtime cost model. +% \item Section~\ref{sec:generic-search} introduces generic count and +% some associated machinery, and presents an implementation in +% $\HPCF$ with runtime $\BigO(2^n)$. +% \item Section~\ref{sec:pure-counting} establishes that any generic +% count implementation in $\BCalc$ must have runtime $\Omega(n2^n)$. +% \item Section~\ref{sec:robustness} shows that our results scale to +% richer settings including support for a wider class of predicates, +% the adaptation from generic count to generic search, and an +% extension of the base language with state. +% \item Section~\ref{sec:experiments} evaluates implementations of +% generic search based on $\BPCF$ and $\HPCF$ in Standard ML. +% \item Section \ref{sec:conclusions} concludes. +% \end{itemize} +% % +% The languages $\BPCF$ and $\HPCF$ are rather minimal versions of +% previously studied systems --- we only include the machinery needed +% for illustrating the generic search efficiency phenomenon. +% % +% Auxiliary results are included in the appendices of the extended +% version of the paper~\citep{HillerstromLL20}. -% Let - eval M -\mlab{Let} & \cek{ \Let \; x \revto M \; \In \; N \mid \env \mid (\slk, \chi) \cons \shk} - &\stepsto& \cek{ M \mid \env \mid ((\env,x,N) \cons \slk, \chi) \cons \shk} \\ - -% Handle -\mlab{Handle^\depth} & \cek{ \Handle^\depth \, M \; \With \; H^\depth \mid \env \mid \shk} - &\stepsto& \cek{ M \mid \env \mid (\nil, (\env, H^\depth)) \cons \shk} \\ - -\mlab{Handle^\param} & \cek{ \Handle^\param \, M \; \With \; (q.\,H)(W) \mid \env \mid \shk} - &\stepsto& \cek{ M \mid \env \mid (\nil, (\env[q \mapsto \val{W}\env], H)) \cons \shk} \\ - -% Return - let binding -\mlab{PureCont} &\cek{ \Return \; V \mid \env \mid ((\env',x,N) \cons \slk, \chi) \cons \shk} - &\stepsto& \cek{ N \mid \env'[x \mapsto \val{V}{\env}] \mid (\slk, \chi) \cons \shk} \\ - -% Return - handler -\mlab{GenCont} & \cek{ \Return \; V \mid \env \mid (\nil, (\env',H^\delta)) \cons \shk} - &\stepsto& \cek{ M \mid \env'[x \mapsto \val{V}{\env}] \mid \shk}, - &\text{if } \hret = \{\Return\; x \mapsto M\} \\ - -% Deep -\mlab{Do^\depth} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid ((\slk, (\env', H^\depth)) \cons \shk) \circ \shk'} - &\stepsto& \cek{M \mid \env'[p \mapsto \val{V}{\env}, - r \mapsto (\shk' \concat [(\slk, (\env', H^\depth))])^B] \mid \shk},\\ -&&&\quad\text{if } \ell : A \to B \in E \text{ and } \hell = \{\OpCase{\ell}{p}{r} \mapsto M\} \\ - -% Shallow -\mlab{Do^\dagger} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid ((\slk, (\env', H)^\dagger) \cons \shk) \circ \shk'} &\stepsto& \cek{M \mid \env'[p \mapsto \val{V}{\env}, - r \mapsto (\shk', \slk)^B] \mid \shk},\\ - &&&\quad\text{if } \ell : A \to B \in E \text{ and } \hell = \{\OpCase{\ell}{p}{r} \mapsto M\} \\ +%% +%% Base calculus +%% +\section{Simply-typed base and handler calculi} +\label{sec:calculi} +In this section, we present a base language $\BPCF$ and its extension +with effect handlers $\HPCF$, both of which amounts to simply-typed +variations of $\BCalc$ and $\HCalc$, +respectively. Sections~\ref{sec:base-calculus}--\ref{sec:handler-machine} +essentially recast the developments of +Chapters~\ref{ch:base-language}, \ref{ch:unary-handlers}, and +\ref{ch:abstract-machine} to fit the calculi $\BPCF$ and $\HPCF$. I +reproduce the details here, even though, they are mostly the same as +in the previous chapters save for a few tricks such as a crucial +design decision in Section~\ref{sec:handlers-calculus} which makes it +possible to implement continuation reification as a constant time +operation. -% Forward -\mlab{Forward} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid (\theta \cons \shk) \circ \shk'} - &\stepsto& \cek{ (\Do \; \ell \; V)^E \mid \env \mid \shk \circ (\shk' \concat [\theta])}, - &\text{if } \gell = \emptyset -\ea -\el -\] -\caption{Abstract machine transitions.} -\label{fig:abstract-machine-semantics-gencont} -\end{minipage} -} -\end{figure} +\subsection{Base calculus} +\label{sec:base-calculus} +The base calculus $\BPCF$ is a fine-grain +call-by-value~\cite{LevyPT03} variation of PCF~\cite{Plotkin77}. % +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. + \begin{figure} -\[ -\bl -\ba{@{~}l@{\quad}l@{~}l} - \multicolumn{2}{l}{\textbf{Initial continuation}}\\ - \multicolumn{3}{l}{\quad\shk_0 \defas [(\nil, (\emptyset, \{\Return\;x \mapsto x\}))]} -\medskip\\ -% - \textbf{Initialisation} & \stepsto \subseteq \CompCat \times \MConfCat\\ - \quad\mlab{Init} & \multicolumn{2}{l}{\quad M \stepsto \cek{M \mid \emptyset \mid \shk_0}} -\medskip\\ -% - \textbf{Finalisation} & \stepsto \subseteq \MConfCat \times \ValCat\\ - \quad\mlab{Halt} & \multicolumn{2}{l}{\quad\cek{\Return\;V \mid \env \mid \nil} \stepsto \val{V}\env} -\ea -\el -\] -\caption{Machine initialisation and finalisation.} -\label{fig:machine-init-final} + \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 \\ +\slab{Values} &V,W\in\ValCat &::= & x \mid k \mid c \mid \lambda x^A .\, M \mid \Rec \; f^{A \to B}\, x.M \\ + & &\mid& \Unit \mid \Record{V, W} \mid (\Inl\, V)^B \mid (\Inr\, W)^A\\ +% & & & +\slab{Computations} &M,N\in\CompCat + &::= & V\,W + \mid \Let\; \Record{x,y} = V \; \In \; N \\ + & &\mid&\Case \; V \;\{ \Inl \; x \mapsto M; \Inr \; y \mapsto N\}\\ + & &\mid& \Return\; V + \mid \Let \; x \revto M \; \In \; N \\ +\end{syntax} +\caption{Syntax of $\BPCF$.}\label{fig:bpcf} \end{figure} % -The semantics of the abstract machine is defined in terms of a -transition relation $\stepsto \subseteq \MConfCat \times \MConfCat$ on -machine configurations. The definition of the transition relation is -given in Figure~\ref{fig:abstract-machine-semantics-gencont}. +Figure~\ref{fig:bpcf} depicts the type syntax, type environment +syntax, and term syntax of $\BPCF$. % -A fair amount of the transition rules involve manipulating the -continuation. We adopt the same stack notation conventions used in the -CPS translation with generalised continuations -(Section~\ref{sec:cps-gen-conts}) and write $\nil$ for an empty stack, -$x \cons s$ for the result of pushing $x$ on top of stack $s$, and -$s \concat s'$ for the concatenation of stack $s$ on top of $s'$. We -use pattern matching to deconstruct stacks. - -The first eight rules enact the elimination of values. +The ground types are $\Nat$ and $\One$ which classify natural number +values and the unit value, respectively. The function type $A \to B$ +classifies functions that map values of type $A$ to values of type +$B$. The binary product type $A \times B$ classifies pairs of values +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$. % -The first three rules concern closures (\mlab{App}, \mlab{AppRec}, -\mlab{AppType}); they all essentially work the same. For example, the -\mlab{App} uses the value interpretation function $\val{-}$ to -interpret the abstractor $V$ in the machine environment $\env$ to -obtain the closure. The body $M$ of closure gets put into the control -component. Before the closure environment $\env'$ gets installed as -the new machine environment, it gets extended with a binding of the -formal parameter of the abstraction to the interpretation of argument -$W$ in the previous environment $\env$. The rule \mlab{AppRec} behaves -the almost the same, the only difference is that it binds the variable -$g$ to the recursive closure in the environment. The rule -\mlab{AppType} does not extend the environment, instead the type is -substituted directly into the body. In either rule continuation -component remains untouched. +Type environments $\Gamma$ map variables to their types. -The resumption rules (\mlab{Resume}, \mlab{Resume^\dagger}, -\mlab{Resume^\param}), however, manipulate the continuation component -as they implement the context restorative behaviour of deep, shallow, -and parameterised resumption application respectively. The -\mlab{Resume} rule handles deep resumption invocations. A deep -resumption is syntactically a generalised continuation, and therefore -it can be directly composed with the machine continuation. Following a -deep resumption invocation the argument gets placed in the control -component, whilst the reified continuation $\kappa'$ representing the -resumptions gets concatenated with the machine continuation $\kappa$ -in order to restore the captured context. +We let $k$ range over natural numbers and $c$ range over primitive +operations on natural numbers ($+, -, =$). % -The rule \mlab{Resume^\dagger} realises shallow resumption -invocations. Syntactically, a shallow resumption consists of a pair -whose first component is a dangling pure continuation $\sigma'$, which -is leftover after removal of its nearest enclosing handler, and the -second component contains a reified generalised continuation -$\kappa'$. The dangling pure continuation gets adopted by the top-most -handler $\chi$ as $\sigma'$ gets appended onto the pure continuation -$\sigma$ running under $\chi$. The resulting continuation gets -composed with the reified continuation $\kappa'$. +We let $x, y, z$ range over term variables. % -The rule \mlab{Resume^\param} implements the behaviour of -parameterised resumption invocations. Syntactically, a parameterised -resumption invocation is generalised continuation just like an -ordinary deep resumption. The primary difference between \mlab{Resume} -and \mlab{Resume^\param} is that in the latter rule the top-most frame -of $\kappa'$ contains a parameterised handler definition, whose -parameter $q$ needs to be updated following an invocation. The handler -closure environment $\env'$ gets extended by a mapping of $q$ to the -interpretation of the argument $W'$ such that this value of $q$ is -available during the next activation of the handler. Following the -environment update the reified continuation gets reconstructed and -appended onto the current machine continuation. - -The rules $\mlab{Split}$ and $\mlab{Case}$ concern record destructing -and variant scrutinising, respectively. Record destructing binds both -the variable $x$ to the value $v$ at label $\ell$ in the record $V$ -and the variable $y$ to the tail of the record in current environment -$\env$. +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. % -Case splitting dispatches to the first branch with the variable $x$ -bound to the variant payload in the environment if the label of the -variant $V$ matches $\ell$, otherwise it dispatches to the second -branch with the variable $y$ bound to the interpretation of $V$ in the -environment. +% 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 rules \mlab{Let}, \mlab{Handle^\depth}, and \mlab{Handle^\param} -augment the current continuation with let bindings and handlers. The -rule \mlab{Let} puts the computation $M$ of a let expression into the -control component and extends the current pure continuation with the -closure of the (source) continuation of the let expression. % -The \mlab{Handle^\depth} rule covers both ordinary deep and shallow -handler installation. The computation $M$ is placed in the control -component, whilst the continuation is extended by an additional -generalised frame with an empty pure continuation and the closure of -the handler $H$. +% 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$. % -The rule \mlab{Handle^\param} covers installation of parameterised -handlers. The only difference here is that the parameter $q$ is -initialised to the interpretation of $W$ in handler environment -$\env'$. - -The current continuation gets shrunk by rules \mlab{PureCont} and -\mlab{GenCont}. If the current pure continuation is nonempty then the -rule \mlab{PureCont} binds a returned value, otherwise the rule -\mlab{GenCont} invokes the return clause of a handler if the pure -continuation is empty. - -The forwarding continuation is used by rules \mlab{Do^\depth}, -\mlab{Do^\dagger}, and \mlab{Forward}. The rule \mlab{Do^\depth} -covers operation invocations under deep and parameterised handlers. If -the top-most handler handles the operation $\ell$, then corresponding -clause computation $M$ gets placed in the control component, and the -handler environment $\env'$ is installed with bindings of the -operation payload and the resumption. The resumption is the forwarding -continuation $\kappa'$ extended by the current generalised -continuation frame. +All elimination forms are computation terms. Abstraction is eliminated +using application ($V\,W$). % -The rule \mlab{Do^\dagger} is much like \mlab{Do^\depth}, except it -constructs a shallow resumption, discarding the current handler but -keeping the current pure continuation. +The product eliminator $(\Let \; \Record{x,y} = V \; \In \; N)$ splits +a pair $V$ into its constituents and binds them to $x$ and $y$, +respectively. Sums are eliminated by a case split ($\Case\; V\; +\{\Inl\; x \mapsto M; \Inr\; y \mapsto N\}$). % -The rule \mlab{Forward} appends the current continuation -frame onto the end of the forwarding continuation. +A trivial computation $(\Return\;V)$ returns value $V$. The sequencing +expression $(\Let \; x \revto M \; \In \; N)$ evaluates $M$ and binds +the result value to $x$ in $N$. -As a slight abuse of notation, we overload $\stepsto$ to inject -computation terms into an initial machine configuration as well as -projecting values. Figure~\ref{fig:machine-init-final} depicts the -structure of the initial machine continuation and two additional -pseudo transitions. The initial continuation consists of a single -generalised continuation frame with an empty pure continuation running -under an identity handler. The \mlab{Init} rule provides a canonical -way to map a computation term onto a configuration, whilst \mlab{Halt} -provides a way to extract the final value of some computation from a -configuration. +\begin{figure*} +\raggedright\textbf{Values} +\begin{mathpar} +% Variable + \inferrule*[Lab=\tylab{Var}] + {x : A \in \Gamma} + {\typv{\Gamma}{x : A}} -\subsection{Putting the machine into action} -\newcommand{\chiid}{\ensuremath{\chi_{\text{id}}}} -\newcommand{\kappaid}{\ensuremath{\kappa_{\text{id}}}} -\newcommand{\incr}{\dec{incr}} -\newcommand{\Incr}{\dec{Incr}} -\newcommand{\prodf}{\dec{prod}} -\newcommand{\consf}{\dec{cons}} +% Unit + \inferrule*[Lab=\tylab{Unit}] + { } + {\typv{\Gamma}{\Unit : \One}} + +% n : Nat + \inferrule*[Lab=\tylab{Nat}] + { k \in \mathbb{N} } + {\typv{\Gamma}{k : \Nat}} + +% c : A + \inferrule*[Lab=\tylab{Const}] + {c : A \to B} + {\typv{\Gamma}{c : A \to B}} +\\ +% Abstraction + \inferrule*[Lab=\tylab{Lam}] + {\typ{\Gamma, x : A}{M : B}} + {\typv{\Gamma}{\lambda x^A .\, M : A \to B}} + +% Recursion + \inferrule*[Lab=\tylab{Rec}] + {\typ{\Gamma, f : A \to B, x : A}{M : B}} + {\typv{\Gamma}{\Rec\; f^{A \to B}\,x .\, M : A \to B}} +\\ +% Products + \inferrule*[Lab=\tylab{Prod}] + { \typv{\Gamma}{V : A} \\ + \typv{\Gamma}{W : B} + } + {\typv{\Gamma}{\Record{V,W} : A \times B}} + +% Left injection + \inferrule*[Lab=\tylab{Inl}] + {\typv{\Gamma}{V : A}} + {\typv{\Gamma}{(\Inl\,V)^B : A + B}} + +% Right injection + \inferrule*[Lab=\tylab{Inr}] + {\typv{\Gamma}{W : B}} + {\typv{\Gamma}{(\Inr\,W)^A : A + B}} +\end{mathpar} + +\textbf{Computations} +\begin{mathpar} +% Application + \inferrule*[Lab=\tylab{App}] + {\typv{\Gamma}{V : A \to B} \\ + \typv{\Gamma}{W : A} + } + {\typ{\Gamma}{V\,W : B}} + +% Split + \inferrule*[Lab=\tylab{Split}] + {\typv{\Gamma}{V : A \times B} \\ + \typ{\Gamma, x : A, y : B}{N : C} + } + {\typ{\Gamma}{\Let \; \Record{x,y} = V\; \In \; N : C}} + +% Case + \inferrule*[Lab=\tylab{Case}] + { \typv{\Gamma}{V : A + B} \\ + \typ{\Gamma,x : A}{M : C} \\ + \typ{\Gamma,y : B}{N : C} + } + {\typ{\Gamma}{\Case \; V \;\{\Inl\; x \mapsto M; \Inr \; y \mapsto N \} : C}} +\\ +% Return + \inferrule*[Lab=\tylab{Return}] + {\typv{\Gamma}{V : A}} + {\typ{\Gamma}{\Return \; V : A}} + +% Let + \inferrule*[Lab=\tylab{Let}] + {\typ{\Gamma}{M : A} \\ + \typ{\Gamma, x : A}{N : C} + } + {\typ{\Gamma}{\Let \; x \revto M\; \In \; N : C}} +\end{mathpar} +\caption{Typing rules for $\BPCF$.} +\label{fig:typing} +\end{figure*} + +The typing rules are given in Figure~\ref{fig:typing}. % -To gain a better understanding of how the abstract machine concretely -transitions between configurations we will consider a small program -consisting of a deep, parameterised, and shallow handler. +We require two typing judgements: one for values and the other for +computations. % -For the deep handler we will use the $\nondet$ handler from -Section~\ref{sec:tiny-unix-time} which handles invocations of the -operation $\Fork : \UnitType \opto \Bool$; it is reproduced here in -fine-grain call-by-value syntax. +The judgement $\typ{\Gamma}{\square : A}$ states that a $\square$-term +has type $A$ under type environment $\Gamma$, where $\square$ is +either a value term ($V$) or a computation term ($M$). % -\[ - \bl - % H_\nondet : \alpha \eff \{\Choose : \UnitType \opto \Bool\} \Harrow \List~\alpha\\ - % H_\nondet \defas - % \ba[t]{@{~}l@{~}c@{~}l} - % \Return\;x &\mapsto& [x]\\ - % \OpCase{\Choose}{\Unit}{resume} &\mapsto& resume~\True \concat resume~\False - % \ea \smallskip\\ - \nondet : (\UnitType \to \alpha \eff \{\Fork : \UnitType \opto \Bool\}) \to \List~\alpha\\ - \nondet~m \defas \bl - \Handle\;m\,\Unit\;\With\\ - ~\ba{@{~}l@{~}c@{~}l} - \Return\;x &\mapsto& [x]\\ - \OpCase{\Fork}{\Unit}{resume} &\mapsto& - \bl - \Let\;xs \revto resume~\True\;\In\\ - \Let\;ys \revto resume~\False\;\In\; - xs \concat ys - \el - \ea - \el - \el -\] +The constants have the following types. % -As for the parameterised handler we will use a handler, which -implements a simple counter that supports one operation -$\Incr : \UnitType \opto \Int$, which increments the value of the -counter and returns the previous value. It is defined as follows. +{ +\begin{mathpar} +\{(+), (-)\} : \Nat \times \Nat \to \Nat + +(=) : \Nat \times \Nat \to \One + \One +\end{mathpar}} % -\[ - \bl - % H_\incr : \Record{\Int;\alpha \eff \{\Incr : \UnitType \opto \Int\}} \Harrow^\param \alpha\\ - % H_\incr \defas - % i.\,\ba[t]{@{~}l@{~}c@{~}l} - % \Return\;x &\mapsto& x\\ - % \OpCase{\Incr}{\Unit}{resume} &\mapsto& resume\,\Record{i+1;i} - % \ea \smallskip\\ - \incr : \Record{\Int;\UnitType \to \alpha\eff \{\Incr : \UnitType \opto \Int\}} \to \alpha\\ - \incr\,\Record{i_0;m} \defas - \bl - \ParamHandle\;m\,\Unit\;\With\\ - ~\left(i.\,\ba{@{~}l@{~}c@{~}l} - \Return\;x &\mapsto& \Return\;x\\ - \OpCase{\Incr}{\Unit}{resume} &\mapsto& \Let\;i' \revto i+1\;\In\;resume\,\Record{i';i} - \ea\right)~i_0 - \el - \el -\] +\begin{figure*} +\begin{reductions} +\semlab{App} & (\lambda x^A . \, M) V &\reducesto& M[V/x] \\ +\semlab{AppRec} & (\Rec\; f^A \,x.\, M) V &\reducesto& M[(\Rec\;f^A\,x .\,M)/f,V/x]\\ +\semlab{Const} & c~V &\reducesto& \Return\;(\const{c}\,(V)) \\ +\semlab{Split} & \Let \; \Record{x,y} = \Record{V,W} \; \In \; N &\reducesto& N[V/x,W/y] \\ +\semlab{Case\textrm{-}inl} & + \Case \; (\Inl\, V)^B \; \{\Inl \; x \mapsto M;\Inr \; y \mapsto N\} &\reducesto& M[V/x] \\ +\semlab{Case\textrm{-}inr} & + \Case \; (\Inr\, V)^A \; \{\Inl \; x \mapsto M; \Inr \; y \mapsto N\} &\reducesto& N[V/y]\\ +\semlab{Let} & + \Let \; x \revto \Return \; V \; \In \; N &\reducesto& N[V/x] \\ +\semlab{Lift} & + \EC[M] &\reducesto& \EC[N], \hfill \text{if }M \reducesto N \\ +\end{reductions} +\begin{syntax} +\slab{Evaluation\textrm{ }contexts} & \mathcal{E} \in \EvalCat &::=& [\,] \mid \Let \; x \revto \mathcal{E} \; \In \; N +\end{syntax} +\caption{Contextual small-step operational semantics.} +\label{fig:small-step} +\end{figure*} % -We will use the $\Pipe$ and $\Copipe$ shallow handlers from -Section~\ref{sec:pipes} to construct a small pipeline. +We give a small-step operational semantics for $\BPCF$ with +\emph{evaluation contexts} in the style of \citet{Felleisen87}. The +reduction rules are given in Figure~\ref{fig:small-step}. % -\[ - \bl - \Pipe : \Record{\UnitType \to \alpha \eff \{ \Yield : \beta \opto \UnitType \}; \UnitType \to \alpha\eff\{ \Await : \UnitType \opto \beta \}} \to \alpha \\ - \Pipe\, \Record{p; c} \defas - \bl - \ShallowHandle\; c\,\Unit \;\With\; \\ - ~\ba[m]{@{}l@{~}c@{~}l@{}} - \Return~x &\mapsto& \Return\;x \\ - \OpCase{\Await}{\Unit}{resume} &\mapsto& \Copipe\,\Record{resume; p} \\ - \ea - \el\medskip\\ +We write $M[V/x]$ for $M$ with $V$ substituted for $x$ and $\const{c}$ +for the usual interpretation of constant $c$ as a meta-level function +on closed values. The reduction relation $\reducesto$ is defined on +computation terms. The statement $M \reducesto N$ reads: term $M$ +reduces to term $N$ in one step. +% +% We write $R^+$ for the transitive closure of relation $R$ and $R^*$ +% for the reflexive, transitive closure of relation $R$. - \Copipe : \Record{\beta \to \alpha\eff\{ \Await : \UnitType \opto \beta\}; \UnitType \to \alpha\eff\{ \Yield : \beta \opto \UnitType\}} \to \alpha \\ - \Copipe\, \Record{c; p} \defas - \bl - \ShallowHandle\; p\,\Unit \;\With\; \\ - ~\ba[m]{@{}l@{~}c@{~}l@{}} - \Return~x &\mapsto& \Return\;x \\ - \OpCase{\Yield}{y}{resume} &\mapsto& \Pipe\,\Record{resume; \lambda \Unit. c\, y} \\ - \ea \\ - \el \\ -\el -\] +\paragraph{Notation} % -We use the following the producer and consumer computations for the -pipes. +We elide type annotations when clear from context. % -\[ - \bl - \prodf : \UnitType \to \alpha \eff \{\Incr : \UnitType \opto \Int; \Yield : \Int \opto \UnitType\}\\ - \prodf\,\Unit \defas - \bl - \Let\;j \revto \Do\;\Incr\,\Unit\;\In\; - \Let\;x \revto \Do\;\Yield~j\; - \In\;\dec{prod}\,\Unit - \el\smallskip\\ - \consf : \UnitType \to \Int \eff \{\Fork : \UnitType \opto \Bool; \Await : \UnitType \opto \Int\}\\ - \consf\,\Unit \defas - \bl - \Let\;b \revto \Do\;\Fork\,\Unit\;\In\; - \Let\;x \revto \Do\;\Await\,\Unit\;\In\\ - % \Let\;y \revto \Do\;\Await\,\Unit\;\In\\ - \If\;b\;\Then\;x*2\;\Else\;x*x - \el - \el -\] +For convenience we often write code in direct-style assuming the +standard left-to-right call-by-value elaboration into fine-grain +call-by-value~\citep{Moggi91, FlanaganSDF93}. % -The producer computation $\prodf$ invokes the operation $\Incr$ to -increment and retrieve the previous value of some counter. This value -is supplied as the payload to an invocation of $\Yield$. +For example, the expression $f\,(h\,w) + g\,\Unit$ is syntactic sugar +for: % -The consumer computation $\consf$ first performs an invocation of -$\Fork$ to duplicate the stream, and then it performs an invocation -$\Await$ to retrieve some value. The return value of $\consf$ depends -on the instance runs in the original stream or forked stream. The -original stream multiplies the retrieved value by $2$, and the -duplicate squares the value. +{ +\[ + \ba[t]{@{~}l} + \Let\; x \revto h\,w \;\In\; + \Let\; y \revto f\,x \;\In\; + \Let\; z \revto g\,\Unit \;\In\; + y + z + \ea +\]}% % -Finally, the top-level computation plugs all of the above together. -% -\begin{equation} - \nondet\,(\lambda\Unit.\incr\,\Record{1;\lambda\Unit.\Pipe\,\Record{\prodf;\consf}})\label{eq:abs-prog} -\end{equation} +We define sequencing of computations in the standard way. % +{ +\[ + M;N \defas \Let\;x \revto M \;\In\;N, \quad \text{where $x \notin FV(N)$} +\]}% % -Function interpretation is somewhat heavy notation-wise as -environments need to be built. To make the notation a bit more -lightweight I will not define the initial environments for closures -explicitly. By convention I will subscript initial environments with -the name of function, e.g. $\env_\consf$ denotes the initial -environment for the closure of $\consf$. Extensions of initial -environments will use superscripts to differentiate themselves, -e.g. $\env_\consf'$ is an extension of $\env_\consf$. As a final -environment simplification, I will take the initial environments to -contain the bindings for parameters of their closures, that is, an -initial environment is really the environment for the body of its -closure. In a similar fashion, I will use superscripts and subscripts -to differentiate handler closures, e.g. $\chi^\dagger_\Pipe$ denotes -the handler closure for the shallow handler definition in $\Pipe$. The -environment of a handler closure is to be understood -implicitly. Furthermore, the definitions above should be understood to -be implicitly $\Let$-sequenced, whose tail computation is -\eqref{eq:abs-prog}. Evaluation of this sequence gives rise to a -`toplevel' environment, which binds the closures for the definition. I -shall use $\env_0$ to denote this environment. - -The machine executes the top-level computation in an initial -configuration with the top-level environment $\env_0$. The first -couple of transitions install the three handlers in order: $\nondet$, -$\incr$, and $\Pipe$. +We make use of standard syntactic sugar for pattern matching. For +instance, we write % -\begin{derivation} - &\nondet\,(\lambda\Unit.\incr\,\Record{1;\lambda\Unit.\Pipe\,\Record{\prodf;\consf}})\\ - \stepsto& \reason{\mlab{Init} with $\env_0$}\\ - &\cek{\nondet\,(\lambda\Unit.\incr\,\Record{0;\lambda\Unit.\Pipe\,\Record{\prodf;\consf}}) \mid \env_0 \mid \sks_0}\\ - \stepsto^+& \reason{$3\times$(\mlab{App}, \mlab{Handle^\delta})}\\ - &% \bl - \cek{c\,\Unit \mid \env_\Pipe \mid (\nil,\chi^\dagger_\Pipe) \cons (\nil, \chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0}\\ - % \text{where } - % \bl - % % \env_\Pipe = \env_0[c \mapsto (\env_0, \consf), p \mapsto (\env_0, \prodf)]\\ - % % \chi^\dagger_\Pipe = (\env_\Pipe, H^\dagger_\Pipe)\\ - % % \env_\incr = \env_0[m \mapsto (\env_0, \lambda\Unit.\Pipe\cdots),i \mapsto 0]\\ - % % \chi^\param_\incr = (\env_\incr, H^\param_\incr)\\ - % % \env_\nondet = \env_0[m \mapsto (\env_0, \lambda\Unit.\incr \cdots)]\\ - % % \chi_\nondet = (\env_\nondet, H_\nondet) - % \el - % \el\\ -\end{derivation} +{ +\[ + \lambda\Unit.M \defas \lambda x^{\One}.M, \quad \text{where $x \notin FV(M)$} +\]}% % -At this stage the continuation consists of four frames. The first -three frames each corresponds to an installed handler, whereas the -last frame is the identity handler. The control component focuses the -application of consumer computation provided as an argument to -$\Pipe$. The next few transitions get us to the first operation -invocation. +for suspended computations, and if the binder has a type other than +$\One$, we write: % -\begin{derivation} - \stepsto^+& \reason{\mlab{App}, \mlab{Let}}\\ - &\bl - \cek{\Do\;\Fork\,\Unit \mid \env_\consf \mid ([(\env_\consf,b,\Let\;x \revto \cdots)],\chi^\dagger_\Pipe) \cons (\nil, \chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0}\\ - \el\\ - \stepsto^+& \reason{\mlab{Forward}, \mlab{Forward}}\\ - &\bl - \cek{\Do\;\Fork\,\Unit \mid \env_\consf \mid [(\nil, \chi_\nondet),(\nil,\chiid)] \circ \kappa'}\\ - \text{where } \kappa' = [([(\env_\consf,b,\Let\;x \revto \cdots)],\chi^\dagger_\Pipe),(\nil, \chi^\param_\incr)] - \el\\ -\end{derivation} +{ +\[ + \lambda\_^A.M \defas \lambda x^A.M, \quad \text{where $x \notin FV(M)$} +\]}% % -The pure continuation under $\chi^\dagger_\Pipe$ has been augmented -with the pure frame corresponding to $\Let$-binding of the invocation -of $\Fork$. Operation invocation causes the machine to initiate a -search for a suitable handler, as the top-most handler $\Pipe$ does -not handle $\Fork$. The machine performs two $\mlab{Forward}$ -transitions, which moves the two top-most frames from the program -continuation onto the forwarding continuation. +We use the standard encoding of booleans as a sum: +{ +\begin{mathpar} +\Bool \defas \One + \One + +\True \defas \Inl~\Unit + +\False \defas \Inr~\Unit + +\If\;V\;\Then\;M\;\Else\;N \defas \Case\;V\;\{\Inl~\Unit \mapsto M; \Inr~\Unit \mapsto N\} +\end{mathpar}}% + % -As a result the, now, top-most frame of the program continuation -contains a suitable handler for $\Fork$. Thus the following -transitions transfer control to $\Fork$-case inside the $\nondet$ -handler. +% Handlers extension % -\begin{derivation} - \stepsto^+& \reason{\mlab{Do}, \mlab{Let}}\\ - &\bl - \cek{resume~\True \mid \env_\nondet' \mid \kappa_0'}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\nondet' &=& \env_\nondet[resume \mapsto \kappa' \concat [(\nil, \chi_\nondet)]]\\ - \kappa_0' &=& [([(\env_\nondet',xs,\Let\;ys\revto\cdots)],\chiid)] - \el - \el -\end{derivation} +\subsection{Handler calculus} +\label{sec:handlers-calculus} + +We now define $\HPCF$ as an extension of $\BPCF$. % -The $\mlab{Do}$ transition is responsible for activating the handler, -and the $\mlab{Let}$ transition focuses the first resumption -invocation. The resumption $resume$ is bound in the environment to the -forwarding continuation $\kappa'$ extended with the frame for the -current handler. The pure continuation running under the identity -handler gets extended with the $\Let$-binding containing the first -resumption invocation. The next transitions reassemble the program -continuation and focuses control on the invocation of $\Await$. +{ +\begin{syntax} +\slab{Operation\textrm{ }symbols} &\ell \in \mathcal{L} & & \\ +\slab{Signatures} &\Sigma\in\CatName{Sig} &::=& \cdot \mid \{\ell : A \to B\} \cup \Sigma\\ +\slab{Handler\textrm{ }types} &F \in \HandlerTypeCat &::=& C \Rightarrow D\\ +\slab{Computations} &M, N \in \CompCat &::=& \dots \mid \Do \; \ell \; V + \mid \Handle \; M \; \With \; H \\ +\slab{Handlers} &H&::=& \{ \Return \; x \mapsto M \} + \mid \{ \OpCase{\ell}{p}{r} \mapsto N \} \uplus H\\ +\end{syntax}}% % -\begin{derivation} - \stepsto^+& \reason{\mlab{Resume}, \mlab{PureCont}, \mlab{Let}}\\ - &\bl - \cek{\Do\;\Await\,\Unit \mid \env_\consf' \mid ([(\env_\consf',x,\If\;b\cdots)], \chi^\dagger_\Pipe) \cons (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\consf' &=& \env_\consf[b \mapsto \True]\\ - \ea - \el -\end{derivation} +We assume a countably infinite set $\mathcal{L}$ of operation symbols +$\ell$. % -At this stage the context of $\consf$ has been restored with $b$ being -bound to the value $\True$. The pure continuation running under -$\Pipe$ has been extended with pure frame corresponding to the -continuation of the $\Let$-binding of the $\Await$ -invocation. Handling of this invocation requires no use of the -forwarding continuation as the top-most frame contains a suitable -handler. +An effect signature $\Sigma$ is a map from operation symbols to their +types, thus we assume that each operation symbol in a signature is +distinct. An operation type $A \to B$ classifies operations that take +an argument of type $A$ and return a result of type $B$. % -\begin{derivation} - \stepsto& \reason{\mlab{Do^\dagger}}\\ - &\bl - \cek{\Copipe\,\Record{resume;p} \mid \env_\Pipe' \mid (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\Pipe' &=& \env_\Pipe[resume \mapsto (\nil, [(\env_\consf',x,\If\;b\cdots)])]\\ - \ea - \el -\end{derivation} +We write $\dom(\Sigma) \subseteq \mathcal{L}$ for the set of operation +symbols in a signature $\Sigma$. % -Now the $\Await$-case of the $\Pipe$ handler has been activated. The -resumption $resume$ is bound to the shallow resumption in the -environment. The generalised continuation component of the shallow -resumption is empty, because no forwarding was involved in locating -the handler. The next transitions install the $\Copipe$ handler and -runs the producer computation. +A handler type $C \Rightarrow D$ classifies effect handlers that +transform computations of type $C$ into computations of type $D$. % -\begin{derivation} - \stepsto^+& \reason{\mlab{App}, \mlab{Handle^\dagger}}\\ - &\bl - \cek{p\,\Unit \mid \env_\Copipe' \mid (\nil, \chi^\dagger_\Copipe) \cons \kappa'}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\Copipe' &=& \env_\Copipe[c \mapsto (\nil, [(\env_\consf',x,\If\;b\cdots)])]\\ - % \chi_\Copipe &=& (\env_\Copipe,H^\dagger_\Copipe)\\ - \ea - \el\\ - \stepsto^+& \reason{\mlab{AppRec}, \mlab{Let}}\\ - &\cek{\Do\;\Incr\,\Unit \mid \env_\prodf \mid ([(\env_\prodf,j,\Let\;x\revto\cdots)],\chi^\dagger_\Copipe) \cons \kappa'}\\ - \stepsto^+& \reason{\mlab{Forward}, \mlab{Do^\param}, \mlab{Let}, \mlab{App}, \mlab{PureCont}}\\ - &\bl - \cek{resume\,\Record{i';i} \mid \env_\incr' \mid (\nil, \chi_\nondet) \cons \kappa_0'}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\incr' &=& \env_\incr[\bl - i \mapsto 1, i' \mapsto 2,\\ - resume \mapsto [([(\env_\prodf,j,\Let\;x\revto\cdots)],\chi^\dagger_\Copipe),(\nil,\chi^\param_\incr)]] - \el - \ea - \el -\end{derivation} +Following \citet{Pretnar15}, we assume a global signature for every +program. % -The producer computation performs the $\Incr$ operation, which -requires one $\mlab{Forward}$ transition in order to locate a suitable -handler for it. The $\Incr$-case of the $\incr$ handler increments the -counter $i$ by one. The environment binds the current value of the -counter. The following $\mlab{Resume^\param}$ transition updates the -counter value to be that of $i'$ and continues the producer -computation. +Computations are extended with operation invocation ($\Do\;\ell\;V$) +and effect handling ($\Handle\; M \;\With\; H$). % -\begin{derivation} - \stepsto^+&\reason{\mlab{Resume^\param}, \mlab{PureCont}, \mlab{Let}}\\ - &\bl - \cek{\Do\;\Yield~j \mid \env_{\prodf}'' \mid ([(\env_{\prodf}'',x,\prodf\,\Unit)],\chi^\dagger_\Copipe) \cons (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\prodf'' &=& \env_\prodf'[j \mapsto 1]\\ - \ea - \el\\ - \stepsto& \reason{\mlab{Do^\dagger}}\\ - &\bl - \cek{\Pipe\,\Record{resume;\lambda\Unit.c\,y} \mid \env_\Pipe' \mid (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\Pipe' &=& \env_\Pipe[y \mapsto 1, resume \mapsto (\nil,[(\env_{\prodf}'',x,\prodf\,\Unit)])] - \ea - \el\\ - \stepsto^+& \reason{\mlab{App}, \mlab{Handle^\dagger}, \mlab{Resume^\dagger}, \mlab{PureCont}}\\ - &\bl - \cek{\If\;b\;\Then\;x * 2\;\Else\;x*x \mid \env_\consf'' \mid (\nil, \chi^\dagger_\Pipe) \cons (\nil,\chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0'}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\consf'' &=& \env_\consf'[x \mapsto 1] - \ea - \el -\end{derivation} +Handlers are constructed from one success clause $(\{\Return\; x \mapsto +M\})$ and one operation clause $(\{ \OpCase{\ell}{p}{r} \mapsto N \})$ for +each operation $\ell$ in $\Sigma$. % -The $\Yield$ operation causes another instance of the $\Pipe$ to be -installed in place of the $\Copipe$. The $\mlab{Resume^\dagger}$ -transition occurs because the consumer argument provided to $\Pipe$ is -the resumption of captured by the original instance of $\Pipe$, thus -invoking it causes the context of the original consumer computation to -be restored. Since $b$ is $\True$ the $\If$-expression will dispatch -to the $\Then$-branch, meaning the computation will ultimately return -$2$. This return value gets propagated through the handler stack. +Following \citet{PlotkinP13}, we adopt the convention that a handler +with missing operation clauses (with respect to $\Sigma$) is syntactic +sugar for one in which all missing clauses perform explicit +forwarding: % -\begin{derivation} - \stepsto^+& \reason{\mlab{Case}, \mlab{App}, \mlab{GenCont}, \mlab{GenCont}, \mlab{GenCont}}\\ - &\cek{\Return\;[x] \mid \env_\nondet[x \mapsto 2] \mid \kappa_0'} -\end{derivation} +\[ + \{\OpCase{\ell}{p}{r} \mapsto \Let\; x \revto \Do \; \ell \, p \;\In\; r \, x\}. +\] % -The $\Return$-clauses of the $\Pipe$ and $\incr$ handlers are -identities, and thus, the return value $x$ passes through -unmodified. The $\Return$-case of $\nondet$ lifts the value into a -singleton list. Next the pure continuation is invoked, which restores -the handling context of the first operation invocation $\Fork$. +\paragraph{Remark} + This convention makes effect forwarding explicit, whereas in + $\HCalc$ effect forwarding was implicit. As we shall see soon, an + important semantic consequence of making effect forwarding explicit + is that the abstract machine model in + Section~\ref{sec:handler-machine} has no rule for effect forwarding + as it instead happens as a sequence of explicit $\Do$ invocations in + the term language. As a result, we become able to reason about + continuation reification as a constant time operation, because a + $\Do$ invocation will just reify the top-most continuation frame.\medskip + +\begin{figure*} +\raggedright +\textbf{Computations} +\begin{mathpar} + \inferrule*[Lab=\tylab{Do}] + {(\ell : A \to B) \in \Sigma \\ \typ{\Gamma}{V : A} } + {\typ{\Gamma}{\Do \; \ell \; V : B}} + +\inferrule*[Lab=\tylab{Handle}] + {\typ{\Gamma}{M : C} \\ + \Gamma \vdash H : C \Rightarrow D} + {\typ{\Gamma}{\Handle \; M \; \With \; H : D}} +\end{mathpar} +\textbf{Handlers} +\begin{mathpar} +\inferrule*[Lab=\tylab{Handler}] + { \hret = \{\Return \; x \mapsto M\} \\ + [\hell = \{\OpCase{\ell}{p}{r} \mapsto N_\ell\}]_{\ell \in dom(\Sigma)} \\\\ + \typ{\Gamma, x : C}{M : D} \\ + [\typ{\Gamma, p : A_\ell, r : B_\ell \to D}{N_\ell : D}]_{(\ell : A_\ell \to B_\ell) \in \Sigma} + } + {{\Gamma} \vdash {H : C \Rightarrow D}} +\end{mathpar} + +\caption{Additional typing rules for $\HPCF$.} +\label{fig:typing-handlers} +\end{figure*} + +The typing rules for $\HPCF$ are those of $\BPCF$ +(Figure~\ref{fig:typing}) plus three additional rules for operations, +handling, and handlers given in Figure~\ref{fig:typing-handlers}. % -\begin{derivation} - \stepsto& \reason{\mlab{PureCont}, \mlab{Let}}\\ - &\bl - \cek{resume~\False \mid \env_\nondet' \mid \kappa_0''}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\nondet'' &=& \env_\nondet'[xs \mapsto [3]]\\ - \kappa_0'' &=& [([(\env_\nondet'',ys,xs \concat ys)],\chiid)] - \ea - \el\\ - \stepsto^+& \reason{\mlab{Resume}, \mlab{PureCont}, \mlab{Let}}\\ - &\bl - \cek{\Do\;\Await\,\Unit \mid \env_\consf''' \mid ([(\env_\consf''',x,\If\;b\cdots)], \chi^\dagger_\Pipe) \cons (\nil, \chi^\param_\incr) \cons (\nil, \chi_\nondet) \cons \kappa_0''}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\consf''' &=& \env_\consf''[b \mapsto \False]\\ - \ea - \el\\ -\end{derivation} +The \tylab{Do} rule ensures that an operation invocation is only +well-typed if the operation $\ell$ appears in the effect signature +$\Sigma$ and the argument type $A$ matches the type of the provided +argument $V$. The result type $B$ determines the type of the +invocation. % -The second invocation of the resumption $resume$ interprets $\Fork$ as -$\False$. The consumer computation is effectively restarted with $b$ -bound to $\False$. The previous transitions will be repeated. +The \tylab{Handle} rule types handler application. % -\begin{derivation} - \stepsto^+ & \reason{same reasoning as above}\\ - &\bl - \cek{resume\,\Record{i';i} \mid \env_\incr'' \mid (\nil, \chi_\nondet) \cons \kappa_0''}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\incr' &=& \env_\incr[\bl - i \mapsto 2, i' \mapsto 3,\\ - resume \mapsto [([(\env_\prodf'',i,\Let\;x\revto\cdots)],\chi^\dagger_\Copipe),(\nil,\chi^\param_\incr)]]\el - \ea - \el -\end{derivation} +The \tylab{Handler} rule ensures that the bodies of the success clause +and the operation clauses all have the output type $D$. The type of +$x$ in the success clause must match the input type $C$. The type of +the parameter $p$ ($A_\ell$) and resumption $r$ ($B_\ell \to D$) in +operation clause $\hell$ is determined by the type of $\ell$; the +return type of $r$ is $D$, as the body of the resumption will itself +be handled by $H$. % -After some amount transitions the parameterised handler $\incr$ will -be activated again. The counter variable $i$ is bound to the value -computed during the previous activation of the handler. The machine -proceeds as before and eventually reaches concatenation application -inside the $\Fork$-case. +We write $\hell$ and $\hret$ for projecting success and operation +clauses. +{ +\[ + \ba{@{~}r@{~}c@{~}l@{~}l} + \hret &\defas& \{\Return\, x \mapsto N \}, &\quad \text{where } \{\Return\, x \mapsto N \} \in H\\ + \hell &\defas& \{\OpCase{\ell}{p}{r} \mapsto N \}, &\quad \text{where } \{\OpCase{\ell}{p}{r} \mapsto N \} \in H + \ea +\]}% + +We extend the operational semantics to $\HPCF$. Specifically, we add +two new reduction rules: one for handling return values and another +for handling operation invocations. % -\begin{derivation} - \stepsto^+& \reason{same reasoning as above}\\ - &\bl - \cek{xs \concat ys \mid \env_\nondet'' \mid \kappa_0}\\ - \text{where } - \ba[t]{@{~}r@{~}c@{~}l} - \env_\nondet'' &=& \env_\nondet'[ys \mapsto [4]] - \ea - \el\\ - \stepsto& \reason{\mlab{App}, \mlab{GenCont}, \mlab{Halt}}\\ - & [3,4] -\end{derivation} +{ +\begin{reductions} +\semlab{Ret} & \Handle \; (\Return \; V) \; \With \; H &\reducesto& N[V/x], \qquad + \text{where } \hret = \{ \Return \; x \mapsto N \} \smallskip\\ + \semlab{Op} & \Handle \; \EC[\Do \; \ell \, V] \; \With \; H &\reducesto& N[V/p,(\lambda y.\Handle \; \EC[\Return \; y] \; \With \; H)/r],\\ + \multicolumn{4}{@{}r@{}}{ + \hfill\text{where } \hell = \{ \OpCase{\ell}{p}{r} \mapsto N \} + } +\end{reductions}}% % -\section{Realisability and efficiency implications} -\label{subsec:machine-realisability} - -A practical benefit of the abstract machine semantics over the -context-based small-step reduction semantics with explicit -substitutions is that it provides either a blueprint for a high-level -interpreter-based implementation or an outline for how stacks should -be manipulated in a low-level implementation along with a more -practical and precise cost model. The cost model is more practical in -the sense of modelling how actual hardware might go about executing -instructions, and it is more precise as it eliminates the declarative -aspect of the contextual semantics induced by the \semlab{Lift} -rule. For example, the asymptotic cost of handler lookup is unclear in -the contextual semantics, whereas the abstract machine clearly tells -us that handler lookup involves a linear search through the machine -continuation. - -The abstract machine is readily realisable using standard persistent -functional data structures such as lists and -maps~\cite{Okasaki99}. The concrete choice of data structures required -to realise the abstract machine is not set in stone, although, its -definition is suggestive about the choice of data structures it leaves -space for interpretation. +The first rule invokes the success clause. % -For example, generalised continuations can be implemented using lists, -arrays, or even heaps. However, the concrete choice of data structure -is going to impact the asymptotic time and space complexity of the -primitive operations on continuations: continuation augmentation -($\cons$) and concatenation ($\concat$). +The second rule handles an operation via the corresponding operation +clause. % -For instance, a linked list provides a fast constant time -implementations of either operation, whereas a fixed-size array can -only provide implementations of either operation that run in linear -time due to the need to resize and copy contents in the extreme case. +If we were \naively to extend evaluation contexts with the handle +construct then our semantics would become nondeterministic, as it may +pick an arbitrary handler in scope. % -An implementation based on a singly-linked list admits constant time -for both continuation augmentation as this operation corresponds -directly to list cons. However, it admits only a linear time -implementation for continuation concatenation. Alternatively, an -implementation based on a \citeauthor{Hughes86} list~\cite{Hughes86} -reverses the cost as a \citeauthor{Hughes86} list uses functions to -represent cons cells, thus meaning concatenation is simply function -composition, but accessing any element, including the head, always -takes linear time in the size of the list. In practice, this -difference in efficiency means we can either trade-off fast -interpretation of $\Let$-bindings and $\Handle$-computations for -`slow' handling and context restoration or vice versa depending on -what we expect to occur more frequently. +In order to ensure that the semantics is deterministic, we instead add +a distinct form of evaluation context for effectful computation, which +we call handler contexts. +% +{ +\begin{syntax} +\slab{Handler\textrm{ }contexts} & \HC \in \CatName{HCtx} &::= & [\,] \mid \Handle \; \HC \; \With \; H + \mid \Let\;x \revto \HC\; \In\; N\\ +\end{syntax}}% +% +We replace the $\semlab{Lift}$ rule with a corresponding rule for +handler contexts. +{ +\[ + \HC[M] ~\reducesto~ \HC[N], \qquad\hfill\text{if } M \reducesto N +\]}% +% +The separation between pure evaluation contexts $\EC$ and handler +contexts $\HC$ ensures that the $\semlab{Op}$ rule always selects the +innermost handler. -The pervasiveness of $\Let$-bindings in fine-grain call-by-value means -that the top-most pure continuation is likely to be augmented and -shrunk repeatedly, thus it is a sensible choice to simply represent -generalisation continuations as singly linked list in order to provide -constant time pure continuation augmentation (handler installation -would be constant time too). However, the continuation component -contains two generalisation continuations. In the rule \mlab{Forward} -the forwarding continuation is extended using concatenation, thus we -may choose to represent the forwarding continuation as a -\citeauthor{Hughes86} list for greater efficiency. A consequence of -this choice is that upon resumption invocation we must convert the -forwarding continuation into singly linked list such that it can be -concatenated with the program continuation. Both the conversion and -the concatenation require a full linear traversal of the forwarding -continuation. +We now characterise normal forms and state the standard type soundness +property of $\HPCF$. +% +\begin{definition}[Computation normal forms] + A computation term $N$ is normal with respect to $\Sigma$, if $N = + \Return\;V$ for some $V$ or $N = \EC[\Do\;\ell\,W]$ for some $\ell + \in dom(\Sigma)$, $\EC$, and $W$. +\end{definition} % -A slightly clever choice is to represent both continuations using -\citeauthor{Huet97}'s Zipper data structure~\cite{Huet97}, which -essentially boils down to using a pair of singly linked lists, where -the first component contains the program continuation, and the second -component contains the forwarding continuation. We can make a -non-asymptotic improvement by representing the forwarding continuation -as a reversed continuation such that we may interpret the -concatenation operation ($\concat$) in \mlab{Forward} as regular cons -($\cons$). In the \mlab{Resume^\delta} rules we must then interpret -concatenation as reverse append, which needs to traverse the -forwarding continuation only once. -\paragraph{Continuation copying} -A convenient consequence of using persistent functional data structure -to realise the abstract machine is that multi-shot resumptions become -efficiency as continuation copying becomes a constant time -operation. However, if we were only interested one-shot or linearly -used resumptions, then we may wish to use in-place mutations to -achieve greater efficiency. In-place mutations do not exclude support -for multi-shot resumptions, however, with mutable data structures the -resumptions needs to be copied before use. One possible way to copy -resumptions is to expose an explicit copy instruction in the source -language. Alternatively, if the source language is equipped with a -linear type system, then the linear type information can be leveraged -to provide an automatic insertion of copy instructions prior to -resumption invocations. +\begin{theorem}[Type Soundness] + If $\typ{}{M : C}$, then either there exists $\typ{}{N : C}$ such + that $M \reducesto^* N$ and $N$ is normal with respect to $\Sigma$, + or $M$ diverges. +\end{theorem} -% The structure of a generalised continuation lends itself to a -% straightforward implementation using a persistent singly-linked -% list. A particularly well-suited data structure for the machine -% continuation is \citeauthor{Huet97}'s Zipper data -% structure~\cite{Huet97}, which is essentially a pair of lists. +%% +%% Abstract machine semantics +%% +\subsection{The role of types} -% copy-on-write environments +Readers familiar with backtracking search algorithms may wonder where +types come into the expressiveness picture. +% +Types will not play a direct role in our proofs but rather in the +characterisation of which programs can be meaningfully compared. In +particular, types are used to rule out global approaches such as +continuation passing style (CPS): without types one could obtain an +efficient pure generic count program by CPS transforming the entire +program. -% The definition of abstract machine in this chapter is highly -% suggestive of the choice of data structures required for a realisation -% of the machine. The machine presented in this chapter can readily be -% realised using standard functional data structures such as lists and -% maps~\cite{Okasaki99}. +Readers familiar with effect handlers may wonder why our handler +calculus does not include an effect type system. +% +As types frame the comparison of programs between languages, we +require that types be fixed across languages; hence $\HPCF$ does not +include effect types. +% +Future work includes reconciling effect typing with our approach to +expressiveness. -\section{Simulation of the context-based reduction semantics} -\label{subsec:machine-correctness} -\begin{figure}[t] -\flushleft -\newcommand{\contapp}[2]{#1 #2} -\newcommand{\contappp}[2]{#1(#2)} -%% \newcommand{\contapp}[2]{#1[#2]} -%% \newcommand{\contapp}[2]{#1\mathbin{@}#2} -%% \newcommand{\contappp}[2]{#1\mathbin{@}(#2)} +\section{A practical model of computation} +\label{sec:abstract-machine-semantics} +Thus far we have introduced the base calculus $\BPCF$ and its +extension with effect handlers $\HPCF$. % -\textbf{Configurations} -\begin{displaymath} -\inv{\cek{M \mid \env \mid \shk \circ \shk'}} \defas \contappp{\inv{\shk' \concat \shk}}{\inv{M}\env} - \defas \contappp{\inv{\shk'}}{\contapp{\inv{\shk}}{\inv{M}\env}} -\end{displaymath} +For each calculus we have given a \emph{small-step operational + semantics} which uses a substitution model for evaluation. Whilst +this model is semantically pleasing, it falls short of providing a +realistic account of practical computation as substitution is an +expensive operation. Instead we shall use a slightly simpler variation +of the abstract machine from Chapter~\ref{ch:abstract-machine} as it +provides a more practical model of computation (it is simpler, because +the source language is simpler). + +\subsection{Base machine} +\label{sec:base-abstract-machine} + +\newcommand{\Conf}{\dec{Conf}} +\newcommand{\EConf}{\dec{EConf}} +\newcommand{\MVal}{\dec{MVal}} + +The base machine operates on configurations of the form +$\cek{M \mid \gamma \mid \sigma}$. The first component contains the +computation currently being evaluated. The second component contains +the environment $\gamma$ which binds free variables. The third +component contains the continuation which instructs the machine how to +proceed once evaluation of the current computation is complete. % -\textbf{Pure continuations} -\begin{displaymath} -\contapp{\inv{[]}}{M} \defas M \qquad \contapp{\inv{((\env, x, N) \cons \slk)}}{M} - \defas \contappp{\inv{\slk}}{\Let\; x \revto M \;\In\; \inv{N}(\env \res \{x\})} -\end{displaymath} +The syntax of abstract machine states is as follows. +{ +\begin{syntax} +\slab{Configurations} & \conf \in \Conf &::=& \cek{M \mid \env \mid \sigma} \\ + % & &\mid& \cekop{M \mid \env \mid \kappa \mid \kappa'} \\ +\slab{Environments} &\env \in \Env &::=& \emptyset \mid \env[x \mapsto v] \\ +\slab{Machine\textrm{ }values} &v, w \in \MValCat &::= & x \mid n \mid c \mid \Unit \mid \Record{v, w} \\ + & &\mid& (\env, \lambda x^A .\, M) \mid (\env, \Rec\, f^{A \to B}\,x . \, M)\\ + & &\mid& (\Inl\, v)^B \mid (\Inr\,w)^A \\ +\slab{Pure\textrm{ }continuations} &\sigma \in \MPContCat &::=& \nil \mid (\env, x, N) \cons \sigma \\ +\end{syntax}}% % -\textbf{Continuations} -\begin{displaymath} -\contapp{\inv{[]}}{M} - \defas M \qquad -\contapp{\inv{(\slk, \chi) \cons \shk}}{M} - \defas \contapp{\inv{\shk}}{(\contappp{\inv{\chi}}{\contappp{\inv{\slk}}{M}})} -\end{displaymath} +Values consist of function closures, constants, pairs, and left or +right tagged values. % -\textbf{Handler closures} -\begin{displaymath} -\contapp{\inv{(\env, H^\depth)}}{M} - \defas \Handle^\depth\;M\;\With\;\inv{H^\depth}\env -\end{displaymath} +We refer to continuations of the base machine as \emph{pure}. +% +A pure continuation is a stack of pure continuation frames. A pure +continuation frame $(\env, x, N)$ closes a let-binding $\Let \;x +\revto [~] \;\In\;N$ over environment $\env$. +% +We write $\nil$ for an empty pure continuation and $\phi \cons \sigma$ +for the result of pushing the frame $\phi$ onto $\sigma$. We use +pattern matching to deconstruct pure continuations. % -\textbf{Computation terms} -\begin{equations} -\inv{V\,W}\env &\defas& \inv{V}\env\,\inv{W}{\env} \\ -\inv{V\,T}\env &\defas& \inv{V}\env\,T \\ -\inv{\Let\;\Record{\ell = x; y} = V \;\In\;N}\env - &\defas& \Let\;\Record{\ell = x; y} =\inv{V}\env \;\In\; \inv{N}(\env \res \{x, y\}) \\ -\inv{\Case\;V\,\{\ell\;x \mapsto M; y \mapsto N\}}\env - &\defas& \Case\;\inv{V}\env \,\{\ell\;x \mapsto \inv{M}(\env \res \{x\}); y \mapsto \inv{N}(\env \res \{y\})\} \\ -\inv{\Return\;V}\env &\defas& \Return\;\inv{V}\env \\ -\inv{\Let\;x \revto M \;\In\;N}\env - &\defas& \Let\;x \revto\inv{M}\env \;\In\; \inv{N}(\env \res \{x\}) \\ -\inv{\Do\;\ell\;V}\env - &\defas& \Do\;\ell\;\inv{V}\env \\ -\inv{\Handle^\depth\;M\;\With\;H}\env - &\defas& \Handle^\depth\;\inv{M}\env\;\With\;\inv{H}\env \\ -\end{equations} -\textbf{Handler definitions} -\begin{equations} -\inv{\{\Return\;x \mapsto M\}}\env - &\defas& \{\Return\;x \mapsto \inv{M}(\env \res \{x\})\} \\ -\inv{\{\OpCase{\ell}{p}{r} \mapsto M\} \uplus H^\depth}\env - &\defas& \{\OpCase{\ell}{p}{r} \mapsto \inv{M}(\env \res \{p, r\}\} \uplus \inv{H^\depth}\env \\ -\inv{(q.\,H)}\env &\defas& \inv{H}(\env \res \{q\}) -\end{equations} +\begin{figure*} +\raggedright +\textbf{Transition relation} $\qquad\qquad~~\,\stepsto\, \subseteq\! \MConfCat \times \MConfCat$ +\[ + \bl + \ba{@{}l@{\quad}r@{~}c@{~}l@{~~}l@{}} +% App +\mlab{App} & \cek{ V\;W \mid \env \mid \sigma} + &\stepsto& \cek{ M \mid \env'[x \mapsto \val{W}{\env}] \mid \sigma},\\ + &&& \quad\text{ if }\val{V}{\env} = (\env', \lambda x^A . \, M)\\ -\textbf{Value terms and values} -\begin{displaymath} -\ba{@{}c@{}} +% App rec +\mlab{AppRec} & \cek{ V\;W \mid \env \mid \sigma} + &\stepsto& \cek{ M \mid \env'[\bl + f \mapsto (\env', \Rec\,f^{A \to B}\,x. M), \\ + x \mapsto \val{W}{\env}] \mid \sigma},\\ + \el \\ + &&& \quad\text{ if }\val{V}{\env} = (\env', \Rec\, f^{A \to B}\, x. M)\\ + +% Constant +\mlab{Const} & \cek{ V~W \mid \env \mid \sigma} + &\stepsto& \cek{ \Return\; (\const{c}\,(\val{W}\env)) \mid \env \mid \sigma},\\ + &&& \quad\text{ if }\val{V}{\env} = c \\ +\ea \medskip\\ +\ba{@{}l@{\quad}r@{~}c@{~}l@{~~}l@{}} +\mlab{Split} & \cek{ \Let \; \Record{x,y} = V \; \In \; N \mid \env \mid \sigma} + &\stepsto& \cek{ N \mid \env[x \mapsto v, y \mapsto w] \mid \sigma}, \\ + &&& \quad\text{ if }\val{V}{\env} = \Record{v; w} \\ + +% Case left +\mlab{CaseL} & \ba{@{}l@{}l@{}} + \cekl \Case\; V\, \{&\Inl\, x \mapsto M; \\ + &\Inr\, y \mapsto N\} \mid \env \mid \sigma \cekr \\ + \ea + &\stepsto& \ba[t]{@{~}l}\cek{ M \mid \env[x \mapsto v] \mid \sigma},\\ + \quad\text{ if }\val{V}{\env} = \Inl\, v\ea \\ + +% Case right +\mlab{CaseR} & \ba{@{}l@{}l@{}} + \cekl \Case\; V\, \{&\Inl\, x \mapsto M; \\ + &\Inr\, y \mapsto N\} \mid \env \mid \sigma \cekr \\ + \ea + &\stepsto& \ba[t]{@{~}l}\cek{ N \mid \env[y \mapsto v] \mid \sigma},\\ + \quad\text{ if }\val{V}{\env} = \Inr\, v \ea\\ + +% Let - eval M +\mlab{Let} & \cek{ \Let \; x \revto M \; \In \; N \mid \env \mid \sigma} + &\stepsto& \cek{ M \mid \env \mid (\env,x,N) \cons \sigma} \\ + +% Return - let binding +\mlab{PureCont} &\cek{ \Return \; V \mid \env \mid (\env',x,N) \cons \sigma} + &\stepsto& \cek{ N \mid \env'[x \mapsto \val{V}{\env}] \mid \sigma} \\ + +\ea +\el +\] + +\textbf{Value interpretation} $\qquad\qquad~~\,\val{-} : \ValCat \times \MEnvCat \to \MValCat$ +\[ +\bl \begin{eqs} -\inv{x}\env &\defas& \inv{v}, \quad \text{ if }\env(x) = v \\ -\inv{x}\env &\defas& x, \quad \text{ if }x \notin \dom(\env) \\ -\inv{\lambda x^A.M}\env &\defas& \lambda x^A.\inv{M}(\env \res \{x\}) \\ -\inv{\Lambda \alpha^K.M}\env &\defas& \Lambda \alpha^K.\inv{M}\env \\ -\inv{\Record{}}\env &\defas& \Record{} \\ -\inv{\Record{\ell=V; W}}\env &\defas& \Record{\ell=\inv{V}\env; \inv{W}\env} \\ -\inv{(\ell\;V)^R}\env &\defas& (\ell\;\inv{V}\env)^R \\ +\val{x}{\env} &\defas& \env(x) \\ +\val{\Unit{}}{\env} &\defas& \Unit{} \\ \end{eqs} -\quad +\qquad\qquad \begin{eqs} -\inv{\shk^A} &\defas& \lambda x^A.\inv{\shk}(\Return\;x) \\ -\inv{(\shk, \slk)^A} &\defas& \lambda x^A.\inv{\slk}(\inv{\shk}(\Return\;x)) \\ -\inv{(\env, \lambda x^A.M)} &\defas& \lambda x^A.\inv{M}(\env \res \{x\}) \\ -\inv{(\env, \Lambda \alpha^K.M)} &\defas& \Lambda \alpha^K.\inv{M}\env \\ -\inv{\Record{}} &\defas& \Record{} \\ -\inv{\Record{\ell=v; w}} &\defas& \Record{\ell=\inv{v}; \inv{w}} \\ -\inv{(\ell\;v)^R} &\defas& (\ell\;\inv{v})^R \\ -\end{eqs} \smallskip\\ -\inv{\Rec\,g^{A \to C}\,x.M}\env \defas \Rec\,g^{A \to C}\,x.\inv{M}(\env \res \{g, x\}) - \defas \inv{(\env, \Rec\,g^{A \to C}\,x.M)} \\ +\val{n}{\env} &\defas& n \\ +\val{c}\env &\defas& c \\ +\end{eqs} +\qquad\qquad +\begin{eqs} +\val{\lambda x^A.M}{\env} &\defas& (\env, \lambda x^A.M) \\ +\val{\Rec\, f^{A \to B}\, x.M}{\env} &\defas& (\env, \Rec\,f^{A \to B}\, x.M) \\ +\end{eqs} +\medskip \\ +\begin{eqs} +\val{\Record{V, W}}{\env} &\defas& \Record{\val{V}{\env}, \val{W}{\env}} \\ +\end{eqs} +\qquad\qquad +\ba{@{}r@{~}c@{~}l@{}} +\val{(\Inl\, V)^B}{\env} &\defas& (\Inl\; \val{V}{\env})^B \\ +\val{(\Inr\, V)^A}{\env} &\defas& (\Inr\; \val{V}{\env})^A \\ \ea -\end{displaymath} +\el +\] -\caption{Mapping from abstract machine configurations to terms.} -\label{fig:config-to-term} -\end{figure} -% -We now show that the base abstract machine is correct with respect to -the combined context-based small-step semantics of $\HCalc$, $\SCalc$, -and $\HPCalc$ via a simulation result. +\caption{Abstract machine semantics for $\BPCF$.} +\label{fig:abstract-machine-semantics} +\end{figure*} -Initial states provide a canonical way to map a computation term onto -the abstract machine. +The abstract machine semantics is given in +Figure~\ref{fig:abstract-machine-semantics}. % -A more interesting question is how to map an arbitrary configuration -to a computation term. +The transition relation ($\stepsto$) makes use of the value +interpretation ($\val{-}$) from value terms to machine values. % -Figure~\ref{fig:config-to-term} describes such a mapping $\inv{-}$ -from configurations to terms via a collection of mutually recursive -functions defined on configurations, continuations, handler closures, -computation terms, handler definitions, value terms, and machine -values. The mapping makes use of a domain operation and a restriction -operation on environments. +The machine is initialised by placing a term in a configuration +alongside the empty environment ($\emptyset$) and identity +pure continuation ($\nil$). % -\begin{definition} - The domain of an environment is defined recursively as follows. - % - \[ - \bl - \dom : \MEnvCat \to \VarCat\\ - \ba{@{}l@{~}c@{~}l} - \dom(\emptyset) &\defas& \emptyset\\ - \dom(\env[x \mapsto v]) &\defas& \{x\} \cup \dom(\env) - \ea - \el - \] - % - We write $\env \res \{x_1, \dots, x_n\}$ for the restriction of - environment $\env$ to $\dom(\env) \res \{x_1, \dots, x_n\}$. -\end{definition} +The rules (\mlab{App}), (\mlab{AppRec}), (\mlab{Const}), +(\mlab{Split}), (\mlab{CaseL}), and (\mlab{CaseR}) eliminate values. % -The $\inv{-}$ function enables us to classify the abstract machine -reduction rules according to how they relate to the context-based -small-step semantics. +The (\mlab{Let}) rule extends the current pure continuation with let +bindings. % -Both the rules \mlab{Let} and \mlab{Forward} are administrative in the -sense that $\inv{-}$ is invariant under either rule. +The (\mlab{PureCont}) rule pops the top frame of the pure continuation +and extends the environment with the returned value. % -This leaves the $\beta$-rules \mlab{App}, \mlab{AppRec}, \mlab{TyApp}, -\mlab{Resume^\delta}, \mlab{Split}, \mlab{Case}, \mlab{PureCont}, and -\mlab{GenCont}. Each of these corresponds directly with performing a -reduction in the small-step semantics. We extend the notion of -transition to account for administrative steps. +Given an input of a well-typed closed computation term $\typ{}{M : + A}$, the machine will either diverge or return a value of type $A$. % -\begin{definition}[Auxiliary reduction relations] - We write $\stepsto_{\textrm{a}}$ for administrative steps and - $\simeq_{\textrm{a}}$ for the symmetric closure of - $\stepsto_{\textrm{a}}^*$. We write $\stepsto_\beta$ for - $\beta$-steps and $\Stepsto$ for a sequence of steps of the form - $\stepsto_{\textrm{a}}^\ast \stepsto_\beta$. -\end{definition} +A final state is given by a configuration of the form $\cek{\Return\;V + \mid \env \mid \nil}$ in which case the final return value is given +by the denotation $\val{V}{\env}$ of $V$ under environment $\gamma$. % -The following lemma describes how we can simulate each reduction in -the small-step reduction semantics by a sequence of administrative -steps followed by one $\beta$-step in the abstract machine. + +\paragraph{Correctness} % -\begin{lemma} -\label{lem:machine-simulation} -Suppose $M$ is a computation and $\conf$ is configuration such that -$\inv{\conf} = M$, then if $M \reducesto N$ there exists $\conf'$ such -that $\conf \Stepsto \conf'$ and $\inv{\conf'} = N$, or if -$M \not\reducesto$ then $\conf \not\Stepsto$. -\end{lemma} +The base machine faithfully simulates the operational semantics for +$\BPCF$; most transitions correspond directly to $\beta$-reductions, +but $\mlab{Let}$ performs an administrative step to bring the +computation $M$ into evaluation position. % -\begin{proof} -By induction on the derivation of $M \reducesto N$. -\end{proof} +The proof of correctness is similar to the proof of +Theorem~\ref{thm:handler-simulation} and the required proof gadgetry +is the same. The full details are published in Appendix A of +\citet{HillerstromLL20a}. +% We formally state and prove the correspondence in +% Appendix~\ref{sec:base-machine-correctness}, relying on an +% inverse map $\inv{-}$ from configurations to +% terms~\citep{HillerstromLA20}. % -The correspondence here is rather strong: there is a one-to-one -mapping between $\reducesto$ and the quotient relation of $\Stepsto$ -and $\simeq_{\textrm{a}}$. % The inverse of the lemma is straightforward -% as the semantics is deterministic. +\newcommand{\contapp}[2]{#1 #2} +\newcommand{\contappp}[2]{#1(#2)} + +\subsection{Handler machine} +\label{sec:handler-machine} +\newcommand{\HClosure}{\dec{HClo}} % -Notice that Lemma~\ref{lem:machine-simulation} does not require that -$M$ be well-typed. This is mostly a convenience to simplify the -lemma. The lemma is used in the following theorem where it is being -applied only on well-typed terms. +We now extend the $\BPCF$ machine with functionality to evaluate +$\HPCF$ terms. The resulting machine is almost the same as the machine +in Chapter~\ref{ch:abstract-machine}, though, this machine supports +only deep handlers. % -\begin{theorem}[Simulation]\label{thm:handler-simulation} - If $\typc{}{M : A}{E}$ and $M \reducesto^+ N$ such that $N$ is - normal with respect to $E$, then - $\cek{M \mid \emptyset \mid \kappa_0} \stepsto^+ \conf$ such that - $\inv{\conf} = N$, or $M \not\reducesto$ then - $\cek{M \mid \emptyset \mid \kappa_0} \not\stepsto$. -\end{theorem} +The syntax is extended as follows. % -\begin{proof} -By repeated application of Lemma~\ref{lem:machine-simulation}. -\end{proof} - -\section{Related work} -The literature on abstract machines is vast and rich. I describe here -the basic structure of some selected abstract machines from the -literature. - -\paragraph{Handler machines} Chronologically, the machine presented in -this chapter was the first abstract machine specifically designed for -effect handlers to appear in the literature. Subsequently, this -machine has been extended and used to explain the execution model for -the Multicore OCaml -implementation~\cite{SivaramakrishnanDWKJM21}. Their primary extension -captures the finer details of the OCaml runtime as it models the -machine continuation as a heterogeneous sequence consisting of -interleaved OCaml and C frames. - -An alternative machine has been developed by \citet{BiernackiPPS19} -for the Helium language. Although, their machine is based on -\citeauthor{BiernackaBD05}'s definitional abstract machine for the -control operators shift and reset~\cite{BiernackaBD05}, the -continuation structure of the resulting machine is essentially the -same as that of a generalised continuation. The primary difference is -that in their presentation a generalised frame is either pair -consisting of a handler closure and a pure continuation (as in the -presentation in this chapter) or a coercion paired with a pure -continuation. - -\paragraph{SECD machine} \citeauthor{Landin64}'s SECD machine was the -first abstract machine for $\lambda$-calculus viewed as a programming -language~\cite{Landin64,Danvy04}. The machine is named after its -structure as it consists of a \emph{stack} component, -\emph{environment} component, \emph{control} component, and a -\emph{dump} component. The stack component maintains a list of -intermediate value. The environment maps free variables to values. The -control component holds a list of directives that manipulate the stack -component. The dump acts as a caller-saved register as it maintains a -list of partial machine state snapshots. Prior to a closure -application, the machine snapshots the state of the stack, -environment, and control components such that this state can be -restored once the stack has been reduced to a single value and the -control component is empty. The structure of the SECD machine lends -itself to a simple realisation of the semantics of -\citeauthor{Landin98}'s the J operator as its behaviour can realised -by reifying the dump the as value. +{ +\begin{syntax} + \slab{Configurations} &\conf \in \Conf &::=& \cek{M \mid \env \mid \kappa}\\ + \slab{Resumptions} &\rho \in \dec{Res} &::=& (\sigma, \chi)\\ + \slab{Continuations} &\kappa \in \MGContCat &::=& \nil \mid \rho \cons \kappa\\ + \slab{Handler\textrm{ }closures} &\chi \in \MGFrameCat &::=& (\env, H) \\ + \slab{Machine\textrm{ }values} &v, w \in \MValCat &::=& \cdots \mid \rho \\ +\end{syntax}}% % -\citet{Plotkin75} proved the correctness of the machine in style of a -simulation result with respect to a reduction -semantics~\cite{AgerBDM03}. - -The SECD machine is a precursor to the CEK machine as the latter can -be viewed as a streamlined variation of the SECD machine, where the -continuation component unifies stack and dump components of the SECD -machine. +The notion of configurations changes slightly in that the continuation +component is replaced by a generalised continuation +$\kappa \in \MGContCat$; a continuation is now a list of +resumptions. A resumption is a pair of a pure continuation (as in the +base machine) and a handler closure ($\chi$). % -For a deep dive into the operational details of -\citeauthor{Landin64}'s SECD machine, the reader may consult -\citet{Danvy04}, who dissects the SECD machine, and as a follow up on -that work \citet{DanvyM08} perform several rational deconstructions -and reconstructions of the SECD machine with the J operator. - -\paragraph{Krivine machine} The \citeauthor{Krivine07} machine takes -its name after its designer \citet{Krivine07}. It is designed for -call-by-name $\lambda$-calculus computation as it performs reduction -to weak head normal form~\cite{Krivine07,Leroy90}. +A handler closure consists of an environment and a handler definition, +where the former binds the free variables that occur in the latter. % -The structure of the \citeauthor{Krivine07} machine is similar to that -of the CEK machine as it features a control component, which focuses -the current term under evaluation; an environment component, which -binds variables to closures; and a stack component, which contains a -list of closures. +The identity continuation is a singleton list containing the identity +resumption, which is an empty pure continuation paired with the +identity handler closure: % -Evaluation of an application term pushes the argument along with the -current environment onto the stack and continues to evaluate the -abstractor term. Dually evaluation of a $\lambda$-abstraction places -the body in the control component, subsequently it pops the top most -closure from the stack and extends the current environment with this -closure~\cite{DouenceF07}. - -\citet{Krivine07} has also designed a variation of the machine which -supports a call-by-name variation of the callcc control operator. In -this machine continuations have the same representation as the stack -component, and they can be stored on the stack. Then the continuation -capture mechanism of callcc can be realised by popping and installing -the top-most closure from the stack, and then saving the tail of the -stack as the continuation object, which is to be placed on top of the -stack. An application of a continuation can be realised by replacing -the current stack with the stack embedded inside the continuation -object~\cite{Krivine07}. - - -\paragraph{ZINC machine} The ZINC machine is a strict variation of -\citeauthor{Krivine07}'s machine, though it was designed independently -by \citet{Leroy90}. The machine is used as the basis for the OCaml -byte code interpreter~\cite{Leroy90,LeroyDFGRV20}. +{ +\[ +\kappa_0 \defas [(\nil, (\emptyset, \{\Return\;x \mapsto x\}))] +\]}% % -There are some cosmetic difference between \citeauthor{Krivine07}'s -machine and the ZINC machine. For example, the latter decomposes the -stack component into an argument stack, holding arguments to function -calls, and a return stack, which holds closures. +Machine values are augmented to include resumptions as an operation +invocation causes the topmost frame of the machine continuation to be +reified (and bound to the resumption parameter in the operation +clause). % -A peculiar implementation detail of the ZINC machine that affects the -semantics of the OCaml language is that for $n$-ary function -application to be efficient, function arguments are evaluated -right-to-left rather than left-to-right as customary in call-by-value -language~\cite{Leroy90}. The OCaml manual leaves the evaluation order -for function arguments unspecified~\cite{LeroyDFGRV20}. However, for a -long time the native code compiler for OCaml would emit code utilised -left-to-right evaluation order for function arguments, consequently -the compilation method could affect the semantics of a program, as the -evaluation order could be observed using effects, e.g. by raising an -exception~\cite{CartwrightF92}. Anecdotally, Damien Doligez told me in -person at ICFP 2017 that unofficially the compiler has been aligned -with the byte code interpreter such that code running on either -implementation exhibits the same semantics. Even though the evaluation -order remains unspecified in the manual any other observable order -than right-to-left evaluation order is now considered a bug (subject -to some exceptions, notably short-circuiting logical and/or -functions). -\paragraph{Mechanical machine derivations} -% -There are deep mathematical connections between environment-based -abstract machine semantics and standard reduction semantics with -explicit substitutions. +The handler machine adds transition rules for handlers, and modifies +$(\mlab{Let})$ and $(\mlab{PureCont})$ from the base machine to account +for the richer continuation +structure. Figure~\ref{fig:abstract-machine-semantics-handlers} +depicts the new and modified rules. % -For example, \citet{AgerBDM03,AgerDM04,AgerBDM03a} relate abstract -machines and functional evaluators by way of a two-way derivation that -consists of closure conversion, transformation into CPS, and -defunctionalisation of continuations. +The $(\mlab{Handle})$ rule pushes a handler closure along with an +empty pure continuation onto the continuation stack. % -\citet{BiernackaD07} demonstrate how to formally derive an abstract -machine from a small-step reduction strategy. Their presentation has -been formalised by \citet{Swierstra12} in the dependently-typed -programming language Agda. +The $(\mlab{GenCont})$ rule transfers control to the success clause +of the current handler once the pure continuation is empty. % -\citet{HuttonW04} demonstrate how to calculate a -correct-by-construction abstract machine from a given specification -using structural induction. Notably, their example machine supports -basic computational effects in the form of exceptions. +The $(\mlab{Op})$ rule transfers control to the matching +operation clause on the topmost handler, and during the process it +reifies the handler closure. Finally, the $(\mlab{Resume})$ rule +applies a reified handler closure, by pushing it onto the continuation +stack. % -\citet{AgerDM05} also extended their technique to derive abstract -machines from monadic-style effectful evaluators. - +The handler machine has two possible final states: either it yields a +value or it gets stuck on an unhandled operation. -\part{Expressiveness} -\label{p:expressiveness} -\chapter{Interdefinability of effect handlers} -\label{ch:deep-vs-shallow} +\begin{figure*} +\raggedright -On the surface, shallow handlers seem to offer more flexibility than -deep handlers as they do not enforce a particular recursion scheme -over effectful computations. An interesting hypothesis worth -investigating is whether this flexibility is a mere programming -convenience or whether it enables shallow handlers to implement -programs that would otherwise be impossible to implement with deep -handlers. Put slightly different, the hypothesis to test is whether -handlers can implement one another. To test this sort of hypothesis we -first need to pin down what it means for `something to be able to -implement something else'. +\textbf{Transition relation} +\[ + \bl + \ba{@{}l@{~}r@{~}c@{~}l@{~~}l@{}} +% Resume resumption +\mlab{Resume} & \cek{ V\;W \mid \env \mid \kappa} + &\stepsto& \cek{ \Return \; W \mid \env \mid (\sigma, \chi) \cons \kappa},\\ + &&&\quad\text{ if }\val{V}{\env} = (\sigma, \chi) \\ -For example in Section~\ref{sec:pipes} I asserted that shallow -handlers provide the natural basis for implementing pipes, suggesting -that an implementation based on deep handlers would be fiddly. If we -were to consider the wider design space of programming language -features, then it turns out that deep handlers offer a direct -implementation of pipes by shifting recursion from terms to the level -of types (the interested reader may consult either \citet{KammarLO13} -or \citet{HillerstromL18} for the precise details). Thus in some sense -pipes are implementable with deep handlers, however, this particular -implementation strategy is not realisable in the $\HCalc$-calculus -since it has no notion of recursive types, meaning we cannot use this -strategy to argue that deep handlers can implement pipes in our -setting. -% +% Let - eval M +\mlab{Let} & \cek{ \Let \; x \revto M \; \In \; N \mid \env \mid (\sigma, \chi) \cons \kappa} + &\stepsto& \cek{ M \mid \env \mid ((\env,x,N) \cons \sigma, \chi) \cons \kappa} \\ -We will restrict our attention to the calculi $\HCalc$, $\SCalc$, and -$\HPCalc$ and use the notion of \emph{typeability-preserving - macro-expressiveness} to determine whether handlers are -interdefinable~\cite{ForsterKLP19}. In our particular setting, -typeability-preserving macro-expressiveness asks whether there exists -a \emph{local} transformation that can transform one kind of handler -into another kind of handler, whilst preserving typeability in the -image of the transformation. By mandating that the transform is local -we rule out the possibility of rewriting the entire program in, say, -CPS notation to implement deep and shallow handlers as in -Chapter~\ref{ch:cps}. -% -In this chapter we use the notion of typeability-preserving -macro-expressiveness to show that shallow handlers and general -recursion can simulate deep handlers up to congruence, and that deep -handlers can simulate shallow handlers up to administrative -reductions. % The -% latter construction generalises the example of pipes implemented -% using deep handlers that we gave in Section~\ref{sec:pipes}. -% -\paragraph{Relation to prior work} The results in this chapter has -been published previously in the following papers. -% -\begin{enumerate}[i] - \item \bibentry{HillerstromL18} \label{en:ch-def-HL18} - \item \bibentry{HillerstromLA20} \label{en:ch-def-HLA20} -\end{enumerate} -% -The results of Sections~\ref{sec:deep-as-shallow} and -\ref{sec:shallow-as-deep} appear in item \ref{en:ch-def-HL18}, whilst -the result of Section~\ref{sec:param-desugaring} appear in item -\ref{en:ch-def-HLA20}. +% Apply (machine) continuation - let binding +\mlab{PureCont} &\cek{ \Return \; V \mid \env \mid ((\env',x,N) \cons \sigma, \chi) \cons \kappa} + &\stepsto& \cek{ N \mid \env'[x \mapsto \val{V}{\env}] \mid (\sigma, \chi) \cons \kappa} \\ -\section{Deep as shallow} -\label{sec:deep-as-shallow} +% Handle +\mlab{Handle} & \cek{ \Handle \; M \; \With \; H \mid \env \mid \kappa} + &\stepsto& \cek{ M \mid \env \mid (\nil, (\env, H)) \cons \kappa} \\ -\newcommand{\dstrans}[1]{\mathcal{S}\llbracket #1 \rrbracket} +% Return - handler +\mlab{GenCont} & \cek{ \Return \; V \mid \env \mid (\nil, (\env',H)) \cons \kappa} + &\stepsto& \cek{ M \mid \env'[x \mapsto \val{V}{\env}] \mid \kappa},\\ + &&&\quad\text{ if } \hret = \{\Return\; x \mapsto M\} \\ -The implementation of deep handlers using shallow handlers (and -recursive functions) is by a direct local translation, similar to how -one would implement a fold (catamorphism) in terms of general -recursion. Each handler is wrapped in a recursive function and each -resumption has its body wrapped in a call to this recursive function. -% -Formally, the translation $\dstrans{-}$ is defined as the homomorphic -extension of the following equations to all terms and substitutions. -% -\[ -\bl - \dstrans{-} : \CompCat \to \CompCat\\ - \dstrans{\Handle \; M \; \With \; H} \defas - (\Rec~h~f.\ShallowHandle\; f\,\Unit \; \With \; \dstrans{H}_h)\,(\lambda \Unit{}.\dstrans{M}) \medskip\\ - % \dstrans{H}h &=& \dstrans{\hret}h \uplus \dstrans{\hops}h \\ - \dstrans{-} : \HandlerCat \times \ValCat \to \HandlerCat\\ - \ba{@{}l@{~}c@{~}l} - \dstrans{\{ \Return \; x \mapsto N\}}_h &\defas& - \{ \Return \; x \mapsto \dstrans{N} \}\\ - \dstrans{\{ \OpCase{\ell}{p}{r} \mapsto N_\ell \}_{\ell \in \mathcal{L}}}_h &\defas& - \{ \OpCase{\ell}{p}{r} \mapsto - \bl - \Let \; r \revto \Return \; \lambda x.h\,(\lambda \Unit{}.r\,x)\\ - \In\;\dstrans{N_\ell} \}_{\ell \in \mathcal{L}} - \el - \ea +% Handle op +\mlab{Op} & \cek{ \Do \; \ell~V \mid \env \mid (\sigma, (\env', H)) \cons \kappa } + &\stepsto& \cek{ M \mid \env'[\bl + p \mapsto \val{V}\env, \\ + r \mapsto (\sigma, (\env', H))] \mid \kappa }, \\ + \el \\ + &&&\quad\bl + \text{ if } \ell : A \to B \in \Sigma\\ + \text{ and } \hell = \{\OpCase{\ell}{p}{r} \mapsto M\} + \el\\ +\ea \el \] -% -The translation of $\Handle$ uses a $\Rec$-abstraction to introduce a -fresh name $h$ for the handler $H$. This name is used by the -translation of the handler definitions. The translation of -$\Return$-clauses is the identity, and thus ignores the handler -name. However, the translation of operation clauses uses the name to -simulate a deep resumption by guarding invocations of the shallow -resumption $r$ with $h$. +\caption{Abstract machine semantics for $\HPCF$.} +\label{fig:abstract-machine-semantics-handlers} +\end{figure*} -In order to exemplify the translation, let us consider a variation of -the $\environment$ handler from Section~\ref{sec:tiny-unix-env}, which -handles an operation $\Ask : \UnitType \opto \Int$. +\paragraph{Correctness} % -\[ - \ba{@{~}l@{~}l} - &\mathcal{D}\left\llbracket - \ba[m]{@{}l} - \Handle\;\Do\;\Ask\,\Unit + \Do\;\Ask\,\Unit\;\With\\ - \quad\ba[m]{@{~}l@{~}c@{~}l} - \Return\;x &\mapsto& \Return\;x\\ - \OpCase{\Ask}{\Unit}{r} &\mapsto& r~42 - \ea - \ea - \right\rrbracket \medskip\\ - =& \bl - (\Rec\;env~f. - \bl - \ShallowHandle\;f\,\Unit\;\With\\ - \quad\ba[t]{@{~}l@{~}c@{~}l} - \Return\;x &\mapsto& \Return\;x\\ - \OpCase{\Ask}{\Unit}{r} &\mapsto& - \bl - \Let\;r \revto \Return\;\lambda x.env~(\lambda\Unit.r~x)\;\In\\ - r~42)~(\lambda\Unit.\Do\;\Ask\,\Unit + \Do\;\Ask\,\Unit) - \el - \ea - \el - \el - \ea -\] +The handler machine faithfully simulates the operational semantics of +$\HPCF$. % -The deep semantics are simulated by generating the name $env$ for the -shallow handlers and recursively apply the handler under the modified -resumption. +The proof of correctness is almost a carbon copy of the proof of +Theorem~\ref{thm:handler-simulation}. The full details are published +in Appendix B of \citet{HillerstromLL20a}.% Extending the result for +% the base machine, we formally state and prove the correspondence in +% Appendix~\ref{sec:handler-machine-correctness}. -The translation commutes with substitution and preserves typeability. +\subsection{Realisability and asymptotic complexity} +\label{sec:realisability} +As discussed in Section~\ref{subsec:machine-realisability} the machine +is readily realisable using standard persistent functional data +structures. % -\begin{lemma}\label{lem:dstrans-subst} - Let $\sigma$ denote a substitution. The translation $\dstrans{-}$ - commutes with substitution, i.e. - % - \[ - \dstrans{V}\dstrans{\sigma} = \dstrans{V\sigma},\quad - \dstrans{M}\dstrans{\sigma} = \dstrans{M\sigma},\quad - \dstrans{H}\dstrans{\sigma} = \dstrans{H\sigma}. - \] - % -\end{lemma} +Pure continuations on the base machine and generalised continuations +on the handler machine can be implemented using linked lists with a +time complexity of $\BigO(1)$ for the extension operation +$(\_\cons\_)$. % -\begin{proof} - By induction on the structures of $V$, $M$, and $H$. -\end{proof} - -\begin{theorem} -If $\Delta; \Gamma \vdash M : C$ then $\Delta; \Gamma \vdash -\dstrans{M} : \dstrans{C}$. -\end{theorem} +The topmost pure continuation on the handler machine may also be +extended in time $\BigO(1)$, as extending it only requires reaching +under the topmost handler closure. % -\begin{proof} - By induction on the typing derivations. -\end{proof} +Environments, $\env$, can be realised using a map, with a time +complexity of $\BigO(\log|\env|)$ for extension and +lookup~\citep{Okasaki99}. -In order to obtain a simulation result, we allow reduction in the -simulated term to be performed under lambda abstractions (and indeed -anywhere in a term), which is necessary because of the redefinition of -the resumption to wrap the handler around its body. +The worst-case time complexity of a single machine transition is +exhibited by rules which involve operations on the environment, since +any other operation is constant time, hence the worst-time complexity +of a transition is $\BigO(\log|\env|)$. % -Nevertheless, the simulation proof makes minimal use of this power, -merely using it to rename a single variable. +The value interpretation function $\val{-}\env$ is defined +structurally on values. Its worst-time complexity is exhibited by a +nesting of pairs of variables $\val{\Record{x_1,\dots,x_n}}\env$ which +has complexity $\BigO(n\log|\env|)$. + +\paragraph{Continuation copying} On the handler machine the topmost +continuation frame can be copied in constant time due to the +persistent runtime and the layout of machine continuations. An +alternative design would be to make the runtime non-persistent % -% We write $R_{\Cong}$ for the compatible closure of relation -% $R$, that is the smallest relation including $R$ and closed under term -% constructors for $\SCalc$. -%% , otherwise known as \emph{reduction up to -%% congruence}. +in which case copying a continuation frame $((\sigma, \_) \cons +\_)$ would be a $\BigO(|\sigma|)$ time operation. -\begin{theorem}[Simulation up to congruence] -If $M \reducesto N$ then $\dstrans{M} \reducesto_{\Cong}^+ -\dstrans{N}$. -\end{theorem} +\paragraph{Primitive operations on naturals} +% +Our model assumes that arithmetic operations on arbitrary natural +numbers take $\BigO(1)$ time. This is common practice in the study of +algorithms when the main interest lies +elsewhere~\citep[Section~2.2]{CormenLRS09}. If desired, one could +adopt a more refined cost model that accounted for the bit-level +complexity of arithmetic operations; however, doing so would have the +same impact on both of the situations we are wishing to compare, and +thus would add nothing but noise to the overall analysis. -\begin{proof} - By case analysis on $\reducesto$ using - Lemma~\ref{lem:dstrans-subst}. The interesting case is - $\semlab{Op}$, which is where we apply a single $\beta$-reduction, - renaming a variable, under the $\lambda$-abstraction representing - the resumption. The proof of this case is as follows. - % - \begin{derivation} - & \dstrans{\Handle\;\EC[\Do\;\ell~V]\;\With\;H}\\ - =& \reason{definition of $\dstrans{-}$}\\ - & (\Rec\;h\;f.\ShallowHandle\;f\,\Unit\;\With\;\dstrans{H}_h)\,(\lambda\Unit.\dstrans{\EC}[\Do\;\ell~\dstrans{V}])\\ - \reducesto^+& \reason{\semlab{Rec}, \semlab{App}, \semlab{Op^\dagger} with $\hell = \{\OpCase{\ell}{p}{r} \mapsto N\}$}\\ - & (\Let\;r \revto \Return\;\lambda x.h\,(\lambda\Unit.r~x)\;\In\;\dstrans{N})[\lambda y.\dstrans{\EC}[\Return\;y]/r,\dstrans{V}/p]\\ - =& \reason{definition of [-]}\\ - &(\Let\;r \revto \Return\;\lambda x.h\,(\lambda\Unit.(\lambda y.\dstrans{\EC}[\Return\;y])~x)\;\In\;\dstrans{N}[\dstrans{V}/p]\\ - \reducesto_\Cong & \reason{\semlab{App} reduction under $\lambda x.\cdots$}\\ - &(\Let\;r \revto \Return\;\lambda x.h\,(\lambda\Unit.\dstrans{\EC}[\Return\;x])\;\In\;\dstrans{N}[\dstrans{V}/p]\\ - \reducesto& \reason{\semlab{Let} and Lemma~\ref{lem:dstrans-subst}}\\ - % & \dstrans{N}[\dstrans{V}/p,\lambda x.h\,(\lambda\Unit.\dstrans{\EC}[\Return\;x])/r]\\ - % =& \reason{}\\ - &\dstrans{N[V/p,\lambda x.h\,(\lambda\Unit.\EC[\Return\;x])/r]} - \end{derivation} -\end{proof} -\section{Shallow as deep} -\label{sec:shallow-as-deep} -\newcommand{\sdtrans}[1]{\mathcal{D}\llbracket #1 \rrbracket} +%% +%% Generic search +%% +\section{Predicates, decision trees, and generic count} +\label{sec:generic-search} -Implementing shallow handlers in terms of deep handlers is slightly -more involved than the other way round. +We now come to the crux of the chapter. In this section and the next, we +prove that $\HPCF$ supports implementations of certain operations +with an asymptotic runtime bound that cannot be achieved in $\BPCF$ +(Section~\ref{sec:pure-counting}). % -It amounts to the encoding of a case split by a fold and involves a -translation on handler types as well as handler terms. +While the positive half of this claim essentially consolidates a +known piece of folklore, the negative half appears to be new. % -Formally, the translation $\sdtrans{-}$ is defined as the homomorphic -extension of the following equations to all types, terms, type -environments, and substitutions. +To establish our result, it will suffice to exhibit a single +`efficient' program in $\HPCF$, then show that no equivalent program +in $\BPCF$ can achieve the same asymptotic efficiency. We take +\emph{generic search} as our example. + +Generic search is a modular search procedure that takes as input +a predicate $P$ on some multi-dimensional search space, +and finds all points of the space satisfying $P$. +Generic search is agnostic to the specific instantiation of $P$, +and as a result is applicable across a wide spectrum of domains. +Classic examples such as Sudoku solving~\citep{Bird06}, the +$n$-queens problem~\citep{BellS09} and graph colouring +can be cast as instances of generic search, and similar ideas have +been explored in connection with Nash equilibria and +exact real integration~\citep{Simpson98, Daniels16}. + +For simplicity, we will restrict attention to search spaces of the form $\B^n$, +the set of bit vectors of length $n$. +To exhibit our phenomenon in the simplest +possible setting, we shall actually focus on the \emph{generic count} problem: +given a predicate $P$ on some $\B^n$, return the \emph{number of} points +of $\B^n$ satisfying $P$. However, we shall explain why our results +are also applicable to generic search proper. + +We shall view $\B^n$ as the set of functions $\N_n \to \B$, +where $\N_n \defas \{0,\dots,n-1\}$. +In both $\BPCF$ and $\HPCF$ we may represent such functions by terms of type $\Nat \to \Bool$. +We will often informally write $\Nat_n$ in place of $\Nat$ to indicate that +only the values $0,\dots,n-1$ are relevant, but this convention has no +formal status since our setup does not support dependent types. + +To summarise, in both $\BPCF$ and $\HPCF$ we will be working with the types % +{ \[ - \bl - \sdtrans{-} : \HandlerTypeCat \to \HandlerTypeCat\\ - \sdtrans{A\eff E_1 \Rightarrow B\eff E_2} \defas - \sdtrans{A\eff E_1} \Rightarrow \Record{\UnitType \to \sdtrans{C \eff E_1}; \UnitType \to \sdtrans{B \eff E_2}} \eff \sdtrans{E_2} \medskip \medskip\\ - % \sdtrans{C \Rightarrow D} \defas - % \sdtrans{C} \Rightarrow \Record{\UnitType \to \sdtrans{C}; \UnitType \to \sdtrans{D}} \medskip \medskip\\ - \sdtrans{-} : \CompCat \to \CompCat\\ - \sdtrans{\ShallowHandle \; M \; \With \; H} \defas - \ba[t]{@{}l} - \Let\;z \revto \Handle \; \sdtrans{M} \; \With \; \sdtrans{H} \; \In\\ - \Let\;\Record{f; g} = z \;\In\; - g\,\Unit - \ea \medskip\\ - \sdtrans{-} : \HandlerCat \to \HandlerCat\\ - % \sdtrans{H} &=& \sdtrans{\hret} \uplus \sdtrans{\hops} \\ - \ba{@{}l@{~}c@{~}l} - \sdtrans{\{\Return \; x \mapsto N\}} &\defas& - \{\Return \; x \mapsto \Return\; \Record{\lambda \Unit.\Return\; x; \lambda \Unit.\sdtrans{N}}\} \\ - \sdtrans{\{\OpCase{\ell}{p}{r} \mapsto N\}_{\ell \in \mathcal{L}}} &\defas& \{ - \bl - \OpCase{\ell}{p}{r} \mapsto\\ - \qquad\bl\Let \;r \revto - \lambda x. \Let\; z \revto r~x\;\In\; - \Let\; \Record{f; g} = z\; \In\; f\,\Unit\;\In\\ - \Return\;\Record{\lambda \Unit.\Let\; x \revto \Do\;\ell~p\; \In\; r~x; \lambda \Unit.\sdtrans{N}}\}_{\ell \in \mathcal{L}} - \el - \el - \ea - \el +\begin{twoeqs} + \Point & \defas & \Nat \to \Bool & \hspace*{2.0em} & + \Point_n & \defas & \Nat_n \to \Bool \\ + \Predicate & \defas & \Point \to \Bool & & + \Predicate_n & \defas & \Point_n \to \Bool +\end{twoeqs} \] +} % -As evident from the translation of handler types, each shallow handler -is encoded as a deep handler that returns a pair of thunks. It is -worth noting that the handler construction is actually pure, yet we -need to annotate the pair with the translated effect signature -$\sdtrans{E_2}$, because the calculus has no notion of effect -subtyping. Technically we could insert an administrative identity -handler to coerce the effect signature. There are practical reasons -for avoiding administrative handlers, though, as we shall discuss -momentarily the inordinate administrative overhead of this -transformation might conceal the additional overhead incurred by the -introduction of administrative identity handlers. The first component -of the pair forwards all operations, acting as the identity on -computations. The second component interprets a single operation -before reverting to forwarding. -% -The following example illustrates the translation on an instance of -the $\Pipe$ operator from Section~\ref{sec:pipes} using the consumer -computation $\Do\;\Await\,\Unit + \Do\;\Await\,\Unit$ and the -suspended producer computation -$\Rec\;ones\,\Unit.\Do\;\Yield~1;ones\,\Unit$. +and will be looking for programs % +{ \[ - \ba{@{~}l@{~}l} - &\mathcal{D}\left\llbracket - \ba[m]{@{}l} - \ShallowHandle\;\Do\;\Await\,\Unit + \Do\;\Await\,\Unit\;\With\\ - \quad\ba[m]{@{~}l@{~}c@{~}l} - \Return\;x &\mapsto& \Return\;x\\ - \OpCase{\Await}{\Unit}{r} &\mapsto& \Copipe\,\Record{r;\Rec\;ones\,\Unit.\Do\;\Yield~1;ones\,\Unit} - \ea - \ea - \right\rrbracket \medskip\\ - =& \bl - \Let\;z \revto \bl - \Handle\;(\lambda\Unit.\Do\;\Await\,\Unit + \Do\;\Await\,\Unit)\,\Unit\;\With\\ - \quad\ba[t]{@{~}l@{~}c@{~}l} - \Return\;x &\mapsto& \Return\;\Record{\lambda\Unit.\Return\;x;\lambda\Unit.\Return\;x}\\ - \OpCase{\Await}{\Unit}{r} &\mapsto&\\ - \multicolumn{3}{l}{ - \quad\bl - \Let\;r \revto \lambda x.\Let\;z \revto r~x\;\In\;\Let\;\Record{f;g} = z\;\In\;f\,\Unit\;\In\\ - \Return\;\Record{\bl - \lambda\Unit.\Let\;x \revto \Do\;\ell~p\;\In\;r~x;\\ - \lambda\Unit. \sdtrans{\Copipe}\,\Record{r;\Rec\;ones\,\Unit.\Do\;\Yield~1;ones\,\Unit}}\el - \el} - \ea - \el\\ - \In\;\Let\;\Record{f;g} = z\;\In\;g\,\Unit - \el - \ea -\] + \Count_n : \Predicate_n \to \Nat +\]}% % -Evaluation of both the left hand side and right hand side of the -equals sign yields the value $2 : \Int$. The $\Return$-case in the -image contains a redundant pair, because the $\Return$-case of $\Pipe$ -is the identity. The translation of the $\Await$-case sets up the -forwarding component and handling component of the pair of thunks. +such that for suitable terms $P$ representing semantic predicates $\Pi: \B^n \to \B$, +$\Count_n~P$ finds the number of points of $\B^n$ satisfying $\Pi$. -The distinction between deep and shallow handlers is that the latter -is discharged after handling a single operation, whereas the former is -persistent and apt for continual operation interpretations. The -persistence of deep handlers means that any handler in the image of -the translation remains in place for the duration of the handled -computation after handling a single operation, which has noticeable -asymptotic performance implications. Each activation of a handler in -the image introduces another layer of indirection that any subsequent -operation invocation have to follow. Supposing some source program -contains $n$ handlers and performs $k$ operation invocations, then the -image introduces $k$ additional handlers, meaning the total amount of -handlers in the image is $n+k$. Viewed through the practical lens of -the CPS translation (Chapter~\ref{ch:cps}) or abstract machine -(Chapter~\ref{ch:abstract-machine}) it means that in the worst case -handler lookup takes $\BigO(n+k)$ time. For example, consider the -extreme case where $n = 1$, that is, the handler lookup takes -$\BigO(1)$ time in the source, but in the image it takes $\BigO(k)$ -time. -% -Thus this translation is more of theoretical significance than -practical interest. It also demonstrates that typeability-preserving -macro-expressiveness is rather coarse-grained notion of -expressiveness, as it blindly considers whether some construct is -computable using another construct without considering the -computational cost. +Before formalising these ideas more closely, let us look at some examples, +which will also illustrate the machinery of \emph{decision trees} that we will be using. -The translation commutes with substitution and preserves typeability. -% -\begin{lemma}\label{lem:sdtrans-subst} - Let $\sigma$ denote a substitution. The translation $\sdtrans{-}$ - commutes with substitution, i.e. - % - \[ - \sdtrans{V}\sdtrans{\sigma} = \sdtrans{V\sigma},\quad - \sdtrans{M}\sdtrans{\sigma} = \sdtrans{M\sigma},\quad - \sdtrans{H}\sdtrans{\sigma} = \sdtrans{H\sigma}. - \] - % -\end{lemma} -% -\begin{proof} - By induction on the structures of $V$, $M$, and $H$. -\end{proof} -% -\begin{theorem} -If $\Delta; \Gamma \vdash M : C$ then $\sdtrans{\Delta}; -\sdtrans{\Gamma} \vdash \sdtrans{M} : \sdtrans{C}$. -\end{theorem} -% -\begin{proof} - By induction on the typing derivations. -\end{proof} -\newcommand{\admin}{admin} -\newcommand{\approxa}{\gtrsim} +\subsection{Examples of points, predicates and trees} +\label{sec:predicates-points} +Consider first the following terms of type $\Point$: +{ +\begin{mathpar} +\dec{q}_0 \defas \lambda \_. \True -As with the implementation of deep handlers as shallow handlers, the -implementation is again given by a typeability-preserving local -translation. However, this time the administrative overhead is more -significant. Reduction up to congruence is insufficient and we require -a more semantic notion of administrative reduction. +\dec{q}_1 \defas \lambda i. i=0 -\begin{definition}[Administrative evaluation contexts]\label{def:admin-eval} - An evaluation context $\EC \in \EvalCat$ is administrative, - $\admin(\EC)$, when the following two criteria hold. -\begin{enumerate} -\item For all values $V \in \ValCat$, we have: $\EC[\Return\;V] \reducesto^\ast - \Return\;V$ -\item For all evaluation contexts $\EC' \in \EvalCat$, operations - $\ell \in \BL(\EC) \backslash \BL(\EC')$, and values - $V \in \ValCat$: -% -\[ - \EC[\EC'[\Do\;\ell\;V]] \reducesto_\Cong^\ast \Let\; x \revto \Do\;\ell\;V \;\In\; \EC[\EC'[\Return\;x]]. -\] -\end{enumerate} -\end{definition} -% -The intuition is that an administrative evaluation context behaves -like the empty evaluation context up to some amount of administrative -reduction, which can only proceed once the term in the context becomes -sufficiently evaluated. -% -Values annihilate the evaluation context and handled operations are -forwarded. -% -%% The forwarding handler is a technical device which allows us to state -%% the second property in a uniform way by ensuring that the operation is -%% handled at least once. +\dec{q}_2 \defas \lambda i.\, + \If\;i = 0\;\Then\;\True\; + \Else\;\If\;i = 1\;\Then\;\False\; + \Else\;\bot +\end{mathpar}}% +(Here $\bot$ is the diverging term $(\Rec\; f\,i.f\,i)\,\Unit$.) +Then $\dec{q}_0$ represents $\langle{\True,\dots,\True}\rangle \in \B^n$ for any $n$; +$\dec{q}_1$ represents $\langle{\True,\False,\dots,\False}\rangle \in \B^n$ for any $n \geq 1$; +and $\dec{q}_2$ represents $\langle{\True,\False}\rangle \in \B^2$. -\begin{definition}[Approximation up to administrative reduction]\label{def:approx-admin} -Define $\approxa$ as the compatible closure of the following inference -rules. -% +Next some predicates. +First, the following terms all represent the constant true predicate $\B^2 \to \B$: +{ \begin{mathpar} - \inferrule* - { } - {M \approxa M} +\dec{T}_0 \defas \lambda q. \True - \inferrule* - {M \reducesto M' \\ M' \approxa N} - {M \approxa N} +\dec{T}_1 \defas \lambda q.(q\,1; q\,0; \True) - \inferrule* - {\admin(\EC) \\ M \approxa N} - {\EC[M] \approxa N} -\end{mathpar} -% -We say that $M$ approximates $N$ up to administrative reduction if $M -\approxa N$. -\end{definition} -% -Approximation up to administrative reduction captures the property -that administrative reduction may occur anywhere within a term. -% -The following lemma states that the forwarding component of the -translation is administrative. -% -\begin{lemma}\label{lem:sdtrans-admin} -For all shallow handlers $H$, the following context is administrative +\dec{T}_2 \defas \lambda q.(q\,0; q\,0; \True) +\end{mathpar}}% +These illustrate that in the course of evaluating a predicate term $P$ at a point $\dec{q}$, +for each $i0$ where + both claims hold for all $m'0$ where - both claims hold for all $m' (('a -> 'b) -> 'b) -> 'a +% +The typing rule for $\Set$ uses the type embedded in the prompt to fix +the type of the whole computation $N$. Similarly, the typing rule for +$\Cupto$ uses the prompt type of its value argument to fix the answer +type for the continuation $k$. % The type of the $\Cupto$ expression is +% the same as the domain of the continuation, which at first glance may +% seem strange. The intuition is that $\Cupto$ behaves as a let binding +% for the continuation in the context of a $\Set$ expression, i.e. +% % +% \[ +% \bl +% \Set\;p^{\prompttype~B}\;\In\;\EC[\Cupto\;p\;k^{A \to B}.M^B]^B\\ +% \reducesto \Let\;k \revto \lambda x^{A}.\EC[x]^B \;\In\;M^B +% \el +% \] +% % - %% Results: Berger. - Berger & - %%% Integration. - $0.41$ & - $0.35$ & - $0.34$ & - $0.34$ & - $0.37$ & - $0.37$ & - $0.37$ & - $0.37$ & - $0.37$ - \\\hline +The dynamic semantics is generative to accommodate generation of fresh +prompts. Formally, the reduction relation is augmented with a store +$\rho$ that tracks which prompt names have already been allocated. +% +\begin{reductions} + \slab{Value} & + \Set\; p \;\In\; V, \rho &\reducesto& V, \rho\\ + \slab{NewPrompt} & + \newPrompt~\Unit, \rho &\reducesto& p, \rho \uplus \{p\}\\ + \slab{Capture} & + \Set\; p \;\In\; \EC[\Cupto~p~k.M], \rho &\reducesto& M[\qq{\cont_{\EC}}/k], \rho,\\ + \multicolumn{4}{l}{\hfill\text{where $p$ is not active in $\EC$}}\\ + \slab{Resume} & \Continue~\cont_{\EC}~V, \rho &\reducesto& \EC[V], \rho +\end{reductions} +% +The $\slab{Value}$ rule is akin to value rules of shift/reset and +control/prompt. The rule $\slab{NewPrompt}$ allocates a fresh prompt +name $p$ and adds it to the store $\rho$. The $\slab{Capture}$ rule +reifies and aborts the evaluation context up to the nearest enclosing +active prompt $p$. After reification the prompt is removed and +evaluation continues as $M$. The $\slab{Resume}$ rule reinstalls the +captured context $\EC$ with the argument $V$ plugged in. +% - %% Results: Modulus. - Pruned & - %%% Integration. - $0.34$ & - $0.36$ & - $0.35$ & - $0.35$ & - $0.36$ & - $0.35$ & - $0.35$ & - $0.35$ & - $0.36$ - \\\hline +\citeauthor{GunterRR95}'s cupto provides similar behaviour to +\citeauthor{QueinnecS91}'s splitter in regards to being able to `jump +over prompt'. However, the separation of prompt creation from the +control reifier coupled with the ability to set prompts manually +provide a considerable amount of flexibility. For instance, consider +the following example which illustrates how control reifier $\Cupto$ +may escape a matching control delimiter. Let us assume that two +distinct prompts $p$ and $p'$ have already been created. +% +\begin{derivation} + &2 + \Set\; p \;\In\; 3 + \Set\;p'\;\In\;(\Set\;p\;\In\;\lambda\Unit.\Cupto~p~k.k\,(k~1))\,\Unit,\{p,p'\}\\ + \reducesto& \reason{\slab{Value}}\\ + &2 + \Set\; p \;\In\; 3 + \Set\;p'\;\In\;(\lambda\Unit.\Cupto~p~k.k\,(k~1))\,\Unit,\{p,p'\}\\ + \reducesto^+& \reason{\slab{Capture} $\EC = 3 + \Set\;p'\;\In\;[\,]$}\\ + &2 + \qq{\cont_{\EC}}\,(\qq{\cont_{\EC}}\,1),\{p,p'\}\\ + \reducesto& \reason{\slab{Resume} $\EC$ with $1$}\\ + &2 + \qq{\cont_{\EC}}\,(3 + \Set\;p'\;\In\;1),\{p,p'\}\\ + \reducesto^+& \reason{\slab{Value}}\\ + &2 + \qq{\cont_{\EC}}\,4,\{p,p'\}\\ + \reducesto& \reason{\slab{Resume} $\EC$ with $4$}\\ + &2 + \Set\;p'\;In\;4,\{p,p'\}\\ + \reducesto& \reason{\slab{Value}}\\ + &2 + 4,\{p,p'\} \reducesto 6,\{p,p'\} +\end{derivation} +% +The prompt $p$ is used twice, and the dynamic scoping of $\Cupto$ +means when it is evaluated it reifies the continuation up to the +nearest enclosing usage of the prompt $p$. Contrast this with the +morally equivalent example using splitter, which would get stuck on +the application of the control reifier, because it has escaped the +dynamic extent of its matching delimiter. +% - %% Results: Control. - Effectful & - %%% Integration. - $4.93$ & - $3.53$ & - $3.95$ & - $4.20$ & - $3.80$ & - $3.00$ & - $2.62$ & - $2.46$ & - $2.37$ - \\\hline - \end{tabular} - \caption{MLton: integration benchmarks runtime relative to SML/NJ.} - \label{tbl:results-mlton-vs-smlnj-integration} -\end{table*}} +\paragraph{\citeauthor{PlotkinP09}'s effect handlers} In 2009, +\citet{PlotkinP09} introduced handlers for \citeauthor{PlotkinP01}'s +algebraic effects~\cite{PlotkinP01,PlotkinP03,PlotkinP13}. In contrast +to the previous control operators, the mathematical foundations of +handlers were not an afterthought, rather, their origin is deeply +rooted in mathematics. Nevertheless, they turn out to provide a +pragmatic interface for programming with control. Operationally, +effect handlers can be viewed as a small extension to exception +handlers, where exceptions are resumable. Effect handlers are similar +to fcontrol in that handling of control happens at the delimiter and +not at the point of control capture. Unlike fcontrol, the interface of +effect handlers provide a mechanism for handling the return value of a +computation similar to \citeauthor{BentonK01}'s exception handlers +with success continuations~\cite{BentonK01}. +Effect handler definitions occupy their own syntactic category. +% +\begin{syntax} + &A,B \in \ValTypeCat &::=& \cdots \mid A \Harrow B \smallskip\\ + &H \in \HandlerCat &::=& \{ \Return \; x \mapsto M \} + \mid \{ \OpCase{\ell}{p}{k} \mapsto N \} \uplus H\\ +\end{syntax} +% +An effect handler consists of a $\Return$-clause and zero or more +operation clauses. Each operation clause binds the payload of the +matching operation $\ell$ to $p$ and the continuation of the operation +invocation to $k$ in $N$. -\tableone -\tablesmlnjintegration -\tabletwo -\tablemltonintegration -\tablethree -\tablemltonvssmlnjintegration +Effect handlers introduces a new syntactic category of signatures, and +extends the value types with operation types. Operation and handler +application both appear as computation forms. +% +\begin{syntax} + &\Sigma \in \mathsf{Sig} &::=& \emptyset \mid \{ \ell : A \opto B \} \uplus \Sigma\\ + &A,B,C,D \in \ValTypeCat &::=& \cdots \mid A \opto B \smallskip\\ + &M,N \in \CompCat &::=& \cdots \mid \Do\;\ell\,V \mid \Handle \; M \; \With \; H\\[1ex] +\end{syntax} +% +A signature is a collection of labels with operation types. An +operation type $A \opto B$ is similar to the function type in that $A$ +denotes the domain (type of the argument) of the operation, and $B$ +denotes the codomain (return type). For simplicity, we will just +assume a global fixed signature. The form $\Do\;\ell\,V$ is the +application form for operations. It applies an operation $\ell$ with +payload $V$. The construct $\Handle\;M\;\With\;H$ handles a +computation $M$ with handler $H$. +% +\begin{mathpar} + \inferrule* + {{\bl + % C = A \eff \{(\ell_i : A_i \opto B_i)_i; R\} \\ + % D = B \eff \{(\ell_i : P_i)_i; R\}\\ + \{\ell_i : A_i \opto B_i\}_i \in \Sigma \\ + H = \{\Return\;x \mapsto M\} \uplus \{ \OpCase{\ell_i}{p_i}{k_i} \mapsto N_i \}_i \\ + \el}\\\\ + \typ{\Gamma, x : A;\Sigma}{M : D}\\\\ + [\typ{\Gamma,p_i : A_i, k_i : B_i \to D;\Sigma}{N_i : D}]_i + } + {\typ{\Gamma;\Sigma}{H : C \Harrow D}} +\end{mathpar} +\begin{mathpar} + \inferrule* + {\{ \ell : A \opto B \} \in \Sigma \\ \typ{\Gamma;\Sigma}{V : A}} + {\typ{\Gamma;\Sigma}{\Do\;\ell\,V : B}} -%% -%% Experiments -%% -\section{Experiments} -\label{sec:experiments} -The theoretical efficiency gap between realisations of $\BPCF$ and -$\HPCF$ manifests in practice. We observe it empirically on -instantiations of $n$-queens and exact real number integration, which -can be cast as generic search. Tables~\ref{tbl:results-smlnj-queens} -and \ref{tbl:results-smlnj-integration} show the speedup of using an -effectful implementation of generic search over various pure -implementations of the $n$-Queens and integration benchmarks, -respectively. We discuss the benchmarks and results in further detail -below. + \inferrule* + { + \typ{\Gamma}{M : C} \\ + \typ{\Gamma}{H : C \Harrow D} + } + {\typ{\Gamma;\Sigma}{\Handle \; M \; \With\; H : D}} +\end{mathpar} +% +The first typing rule checks that the operation label of each +operation clause is declared in the signature $\Sigma$. The signature +provides the necessary information to construct the type of the +payload parameters $p_i$ and the continuations $k_i$. Note that the +domain of each continuation $k_i$ is compatible with the codomain of +$\ell_i$, and the codomain of $k_i$ is compatible with the codomain of +the handler. +% +The second and third typing rules are application of operations and +handlers, respectively. The rule for operation application simply +inspects the signature to check that the operation is declared, and +that the type of the payload is compatible with the declared type. -% \setlength{\floatsep}{1.0ex} -% \setlength{\textfloatsep}{1.0ex} +This particular presentation is nominal, because operations are +declared up front. Nominal typing is the only sound option in the +absence of an effect system (unless we restrict operations to work +over a fixed type, say, an integer). In +Chapter~\ref{ch:unary-handlers} we see a different presentation based +on structural typing. -\paragraph{Methodology} -We evaluated an effectful implementation of generic search against -three ``pure'' implementations which are realisable in $\BPCF$ -extended with mutable state: +The dynamic semantics of effect handlers are similar to that of +$\fcontrol$, though, the $\slab{Value}$ rule is more interesting. % -\begin{itemize} -\item \Naive: a simple, and rather \naive, functional implementation; -\item Pruned: a generic search procedure with space pruning based on - Longley's technique~\cite{Longley99} (uses local state); -\item Berger: a lazy pure functional generic search procedure based on - Berger's algorithm. -\end{itemize} +\begin{reductions} + \slab{Value} & \Handle\; V \;\With\;H &\reducesto& M[V/x], \text{ where } \{\Return\;x \mapsto M\} \in H\\ + \slab{Capture} & \Handle\;\EC[\Do\;\ell~V] \;\With\; H &\reducesto& M[V/p,\qq{\cont_{\Record{\EC;H}}}/k],\\ + \multicolumn{4}{l}{\hfill\bl\text{where $\ell$ is not handled in $\EC$}\\\text{and }\{\OpCase{\ell}{p}{k} \mapsto M\} \in H\el}\\ + \slab{Resume} & \Continue~\cont_{\Record{\EC;H}}~V &\reducesto& \Handle\;\EC[V]\;\With\;H\\ +\end{reductions} % -Each benchmark was run 11 times. The reported figure is the median -runtime ratio between the particular implementation and the baseline -effectful implementation. Benchmarks that failed to terminate within a -threshold (1 minute for single solution, 8 minutes for enumerations), -are reported as $\tooslow$. The experiments were conducted in -SML/NJ~\cite{AppelM91} v110.97 64-bit with factory settings on an Intel Xeon -CPU E5-1620 v2 @ 3.70GHz powered workstation running Ubuntu 16.04. The -effectful implementation uses an encoding of delimited control akin to -effect handlers based on top of SML/NJ's call/cc. +The \slab{Value} rule differs from previous operators as it is not +just the identity. Instead the $\Return$-clause of the handler +definition is applied to the return value of the computation. % -The complete source code for the benchmarks and instructions on how to -run them are available at: -\begin{center} - \url{https://dl.acm.org/do/10.1145/3410231/abs/} -\end{center} +The \slab{Capture} rule handles operation invocation by checking +whether the handler $H$ handles the operation $\ell$, otherwise the +operation implicitly passes through the term to the context outside +the handler. This behaviour is similar to how exceptions pass through +the context until a suitable handler has been found. +% +If $H$ handles $\ell$, then the context $\EC$ from the operation +invocation up to and including the handler $H$ are reified as a +continuation object, which gets bound in the corresponding clause for +$\ell$ in $H$ along with the payload of $\ell$. % +This form of effect handlers is known as \emph{deep} handlers. They +are deep in the sense that they embody a structural recursion scheme +akin to fold over computation trees induced by effectful +operations. The recursion is evident from $\slab{Resume}$ rule, as +continuation invocation causes the same handler to be reinstalled +along with the captured context. -\paragraph{Queens} -We phrase the $n$-queens problem as a generic search problem. As a -control we include a bespoke implementation hand-optimised for the -problem. We perform two experiments: finding the first solution for $n -\in \{20,24,28\}$ and enumerating all solutions for $n \in -\{8,10,12\}$. The speedup over the \naive implementation is dramatic, -but less so over the Berger procedure. The pruned procedure is more -competitive, but still slower than the baseline. Unsurprisingly, the -baseline is slower than the bespoke implementation. +A classic example of handlers in action is handling of +nondeterminism. Let us fix a signature with two operations. +% +\[ + \Sigma \defas \{\Fail : \UnitType \opto \ZeroType; \Choose : \UnitType \opto \Bool\} +\] +% +The $\Fail$ operation is essentially an exception as its codomain is +the empty type, meaning that its continuation can never be +invoked. The $\Choose$ operation returns a boolean. -\paragraph{Exact real integration} -The integration benchmarks are adapted from \citet{Simpson98}. We -integrate three different functions with varying precision in the -interval $[0,1]$. For the identity function (Id) at precision $20$ the -speedup relative to Berger is $5.18\times$. For the squaring function -the speedups are larger at higher precisions: at precision $14$ the -speedup is $3.78\times$ over the pruned integrator, whilst it is -$4.24\times$ at precision $20$. The speedups are more extreme against -the \naive and Berger integrators. We also integrate the logistic map -$x \mapsto 1 - 2x^2$ at a fixed precision of $15$. We make the -function harder to compute by iterating it up to $5$ times. Between -the pruned and effectful integrator the speedup ratio increases as the -function becomes harder to compute. +We will define a handler for each operation. +% +\[ + \ba{@{~}l@{~}l} + H^{A}_{f} : A \Harrow \Option~A\\ + H_{f} \defas \{ \Return\; x \mapsto \Some~x; &\OpCase{\Fail}{\Unit}{k} \mapsto \None \}\\ + H^B_{c} : B \Harrow \List~B\\ + H_{c} \defas \{ \Return\; x \mapsto [x]; &\OpCase{\Choose}{\Unit}{k} \mapsto k~\True \concat k~\False \} + \ea +\] +% +The handler $H_f$ handles an invocation of $\Fail$ by dropping the +continuation and simply returning $\None$ (due to the lack +polymorphism, the definitions are parameterised by types $A$ and $B$ +respectively. We may consider them as universal type variables). The +$\Return$-case of $H_f$ tags its argument with $\Some$. +% +The $H_c$ definition handles an invocation of $\Choose$ by first +invoking the continuation $k$ with $\True$ and subsequently with +$\False$. The two results are ultimately concatenated. The +$\Return$-case lifts its argument into a singleton list. +% +Now, let us define a simple nondeterministic coin tossing computation +with failure (by convention let us interpret $\True$ as heads and +$\False$ as tails). +% +\[ + \bl + \toss : \UnitType \to \Bool\\ + \toss~\Unit \defas + \ba[t]{@{~}l} + \If\;\Do\;\Choose~\Unit\\ + \Then\;\Do\;\Choose~\Unit\\ + \Else\;\Absurd\;\Do\;\Fail~\Unit + \ea + \el +\] +% +The computation $\toss$ first performs $\Choose$ in order to +branch. If it returns $\True$ then a second instance of $\Choose$ is +performed. Otherwise, it raises the $\Fail$ exception. +% +If we apply $\toss$ outside of $H_c$ and $H_f$ then the computation +gets stuck as either $\Choose$ or $\Fail$, or both, would be +unhandled. Thus, we have to run the computation in the context of both +handlers. However, we have a choice to make as we can compose the +handlers in either order. Let us first explore the composition, where +$H_c$ is the outermost handler. Thus we instantiate $H_c$ at type +$\Option~\Bool$ and $H_f$ at type $\Bool$. +% +\begin{derivation} + & \Handle\;(\Handle\;\toss~\Unit\;\With\; H_f)\;\With\;H_c\\ + \reducesto & \reason{$\beta$-reduction, $\EC = \If\;[\,]\;\Then \cdots$}\\ + & \Handle\;(\Handle\; \EC[\Do\;\Choose~\Unit] \;\With\; H_f)\;\With\;H_c\\ + \reducesto & \reason{\slab{Capture}, $\{\OpCase{\Choose}{\Unit}{k} \mapsto \cdots\} \in H_c$, $\EC' = (\Handle\;\EC\;\cdots)$}\\ + & k~\True \concat k~\False, \qquad \text{where $k = \qq{\cont_{\Record{\EC';H_c}}}$}\\ + \reducesto^+ & \reason{\slab{Resume} with $\True$}\\ + & (\Handle\;(\Handle\;\EC[\True] \;\With\;H_f)\;\With\;H_c) \concat k~\False\\ + \reducesto & \reason{$\beta$-reduction}\\ + & (\Handle\;(\Handle\; \Do\;\Choose~\Unit \;\With\; H_f)\;\With\;H_c) \concat k~\False\\ +\end{derivation} +\begin{derivation} + \reducesto & \reason{\slab{Capture}, $\{\OpCase{\Choose}{\Unit}{k'} \mapsto \cdots\} \in H_c$, $\EC'' = (\Handle\;[\,]\;\cdots)$}\\ + & (k'~\True \concat k'~\False) \concat k~\False, \qquad \text{where $k' = \qq{\cont_{\Record{\EC'';H_c}}}$}\\ + \reducesto& \reason{\slab{Resume} with $\True$}\\ + &((\Handle\;(\Handle\; \True \;\With\; H_f)\;\With\;H_c) \concat k'~\False) \concat k~\False\\ + \reducesto& \reason{\slab{Value}, $\{\Return\;x \mapsto \cdots\} \in H_f$}\\ + &((\Handle\;\Some~\True\;\With\;H_c) \concat k'~\False) \concat k~\False\\ + \reducesto& \reason{\slab{Value}, $\{\Return\;x \mapsto \cdots\} \in H_c$}\\ + & ([\Some~\True] \concat k'~\False) \concat k~\False\\ + \reducesto^+& \reason{\slab{Resume} with $\False$, \slab{Value}, \slab{Value}}\\ + & [\Some~\True] \concat [\Some~\False] \concat k~\False\\ + \reducesto^+& \reason{\slab{Resume} with $\False$}\\ + & [\Some~\True, \Some~\False] \concat (\Handle\;(\Handle\; \Absurd\;\Do\;\Fail\,\Unit \;\With\; H_f)\;\With\;H_c)\\ + \reducesto& \reason{\slab{Capture}, $\{\OpCase{\Fail}{\Unit}{k} \mapsto \cdots\} \in H_f$}\\ + & [\Some~\True, \Some~\False] \concat (\Handle\; \None\; \With\; H_c)\\ + \reducesto& \reason{\slab{Value}, $\{\Return\;x \mapsto \cdots\} \in H_c$}\\ + & [\Some~\True, \Some~\False] \concat [\None] \reducesto [\Some~\True,\Some~\False,\None] +\end{derivation} +% +Note how the invocation of $\Choose$ passes through $H_f$, because +$H_f$ does not handle the operation. This is a key characteristic of +handlers, and it is called \emph{effect forwarding}. Any handler will +implicitly forward every operation that it does not handle. -\paragraph{MLton} -SML/NJ is compiled into CPS, thus providing a particularly efficient -implementation of call/cc. +Suppose we were to swap the order of $H_c$ and $H_f$, then the +computation would yield $\None$, because the invocation of $\Fail$ +would transfer control to $H_f$, which is the now the outermost +handler, and it would drop the continuation and simply return $\None$. + +The alternative to deep handlers is known as \emph{shallow} +handlers. They do not embody a particular recursion scheme, rather, +they correspond to case splits to over computation trees. % -MLton~\cite{Fluet20}, a whole program compiler for SML, implements -call/cc by copying the stack. +To distinguish between applications of deep and shallow handlers, we +will mark the latter with a dagger superscript, i.e. +$\ShallowHandle\; - \;\With\;-$. Syntactically deep and shallow +handler definitions are identical, however, their typing differ. % -We repeated our experiments using MLton 20180207. +\begin{mathpar} +%\mprset{flushleft} + \inferrule* + {{\bl + % C = A \eff \{(\ell_i : A_i \opto B_i)_i; R\} \\ + % D = B \eff \{(\ell_i : P_i)_i; R\}\\ + \{\ell_i : A_i \opto B_i\}_i \in \Sigma \\ + H = \{\Return\;x \mapsto M\} \uplus \{ \OpCase{\ell_i}{p_i}{k_i} \mapsto N_i \}_i \\ + \el}\\\\ + \typ{\Gamma, x : A;\Sigma}{M : D}\\\\ + [\typ{\Gamma,p_i : A_i, k_i : B_i \to C;\Sigma}{N_i : D}]_i + } + {\typ{\Gamma;\Sigma}{H : C \Harrow D}} +\end{mathpar} % -Tables~\ref{tbl:results-mlton-queens} and -\ref{tbl:results-mlton-integration} show the results. The effectful -implementation performs much worse under MLton than SML/NJ, being -surpassed in nearly every case by the pruned search procedure and in -some cases by the Berger search procedure. +The difference is in the typing of the continuation $k_i$. The +codomains of continuations must now be compatible with the return type +$C$ of the handled computation. The typing suggests that an invocation +of $k_i$ does not reinstall the handler. The dynamic semantics reveal +that a shallow handler does not reify its own definition. % -Tables~\ref{tbl:results-mlton-vs-smlnj-queens} and -\ref{tbl:results-mlton-vs-smlnj-integration} summarise the runtime of -MLton relative to SML/NJ for both benchmarks. Berger, Pruned, and -Bespoke run between 1 and 3 times as fast with MLton compared to -SML/NJ. +\begin{reductions} + \slab{Capture} & \ShallowHandle\;\EC[\Do\;\ell~V] \;\With\; H &\reducesto& M[V/p,\qq{\cont_{\EC}}/k],\\ + \multicolumn{4}{l}{\hfill\bl\text{where $\ell$ is not handled in $\EC$}\\\text{and }\{\ell~p~k \mapsto M\} \in H\el}\\ + \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V]\\ +\end{reductions} % -However, the effectful implementation runs between 2 and 14 times as -fast with SML/NJ compared with MLton. - -\section{Related work} -There are relatively little work in the present literature on -expressivity that has focused on complexity difference. +The $\slab{Capture}$ reifies the continuation up to the handler, and +thus the $\slab{Resume}$ rule can only reinstate the captured +continuation without the handler. +% +%\dhil{Revisit the toss example with shallow handlers} +% \begin{reductions} +% \slab{Capture} & \Handle\;\EC[\Do\;\ell~V] \;\With\; H &\reducesto& M[V/p,\qq{\cont_{\EC}}/k],\\ +% \multicolumn{4}{l}{\hfill\bl\text{where $\ell$ is not handled in $\EC$}\\\text{and }\{\ell~p~k \mapsto M\} \in H\el}\\ +% \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V]\\ +% \end{reductions} % -\citet{Pippenger96} gives an example of an online operation on -infinite sequences of atomic symbols (essentially a function from -streams to streams) such that the first $n$ output symbols can be -produced within time $\BigO(n)$ if one is working in an effectful -version of Lisp (one which allows mutation of cons pairs) but with a -worst-case runtime no better than $\Omega(n \log n)$ for any -implementation in pure Lisp (without such mutation). This example was -reconsidered by \citet{BirdJdM97} who showed that the same speedup can -be achieved in a pure language by using lazy evaluation. -\citet{Jones01} explores the approach of manifesting expressivity and -efficiency differences between certain languages by artificially -restricting attention to `cons-free' programs; in this setting, the -classes of representable first-order functions for the various -languages are found to coincide with some well-known complexity -classes. - -The vast majority of work in this area has focused on computability -differences. One of the best known examples is the \emph{parallel if} -operation which is computable in a language with parallel evaluation -but not in a typical sequential programming -language~\cite{Plotkin77}. It is also well known that the presence of -control features or local state enables observational distinctions -that cannot be made in a purely functional setting: for instance, -there are programs involving call/cc that detect the order in which a -(call-by-name) `$+$' operation evaluates its arguments -\citep{CartwrightF92}. Such operations are `non-functional' in the -sense that their output is not determined solely by the extension of -their input (seen as a mathematical function -$\N_\bot \times \N_\bot \rightarrow \N_\bot$); -%% -however, there are also programs with `functional' behaviour that can -be implemented with control or local state but not without them -\citep{Longley99}. More recent results have exhibited differences -lower down in the language expressivity spectrum: for instance, in a -purely functional setting \textit{\`a la} Haskell, the expressive -power of \emph{recursion} increases strictly with its type level -\citep{Longley18a}, and there are natural operations computable by -low-order recursion but not by high-order iteration -\citep{Longley19}. Much of this territory, including the mathematical -theory of some of the natural notions of higher-order computability -that arise in this way, is mapped out by \citet{LongleyN15}. -\part{Conclusions} -\label{p:conclusions} +Chapter~\ref{ch:unary-handlers} contains further examples of deep and +shallow handlers in action. +% +% \dhil{Consider whether to present the below encodings\dots} +% % +% Deep handlers can be used to simulate shift0 and +% reset0~\cite{KammarLO13}. +% % +% \begin{equations} +% \sembr{\shiftz~k.M} &\defas& \Do\;\dec{Shift0}~(\lambda k.M)\\ +% \sembr{\resetz{M}} &\defas& +% \ba[t]{@{~}l}\Handle\;m\,\Unit\;\With\\ +% ~\ba{@{~}l@{~}c@{~}l} +% \Return\;x &\mapsto& x\\ +% \OpCase{\dec{Shift0}}{f}{k} &\mapsto& f\,k +% \ea +% \ea +% \end{equations} +% % -\chapter{Conclusions and future work} -\label{ch:conclusions} +% Shallow handlers can be used to simulate control0 and +% prompt0~\cite{KammarLO13}. +% % +% \begin{equations} +% \sembr{\Controlz~k.M} &\defas& \Do\;\dec{Control0}~(\lambda k.M)\\ +% \sembr{\Promptz~M} &\defas& +% \bl +% prompt0\,(\lambda\Unit.M)\\ +% \textbf{where}\; +% \bl +% prompt0~m \defas +% \ba[t]{@{~}l}\ShallowHandle\;m\,\Unit\;\With\\ +% ~\ba{@{~}l@{~}c@{~}l} +% \Return\;x &\mapsto& x\\ +% \OpCase{\dec{Control0}}{f}{k} &\mapsto& prompt0\,(\lambda\Unit.f\,k) +% \ea +% \ea +% \el +% \el +% \end{equations} % -I will begin this chapter with a brief summary of this -dissertation. The following sections each elaborates and spells out -directions for future work. +%Recursive types are required to type the image of this translation -In Part~\ref{p:background} of this dissertation I have compiled an -extensive survey of first-class control. In this survey I characterise -the various kinds of control phenomena that appear in the literature -as well as providing an overview of the operational characteristics of -control operators appearing in the literature. To the best of my -knowledge this survey is the only of its kind in the present -literature (other studies survey a particular control phenomenon, -e.g. \citet{FelleisenS99} survey undelimited continuations as provided -by call/cc in programming practice, \citet{DybvigJS07} classify -delimited control operators according to their delimiter placement, -\citet{Brachthauser20} provides a delimited control perspective on -effect handlers, and \citet{MouraI09} survey the use of first-class -control operators to implement coroutines). +\paragraph{\citeauthor{Longley09}'s catch-with-continue} +% +The control operator \emph{catch-with-continue} (abbreviated +catchcont) is a delimited extension of the $\Catch$ operator. It was +designed by \citet{Longley09} in 2008~\cite{LongleyW08}. Its origin is +in game semantics, in which program evaluation is viewed as an +interactive dialogue with the ambient environment~\cite{Hyland97} --- +this view aligns neatly with the view of effect handler oriented +programming. Curiously, we can view catchcont and effect handlers as +``siblings'' in the sense that \citeauthor{Longley09} and +\citeauthor{PlotkinP09} them respectively, during the same time, +whilst working in the same department. However, the relationship is +presently just `spiritual' as no formal connections have been drawn +between the two operators. -In Part~\ref{p:design} I have presented the design of a ML-like -programming language equipped an effect-and-type system and a -structural notion of effectful operations and effect handlers. In this -language I have demonstrated how to implement the essence of a -\UNIX{}-like operating system by making, almost, zealous use of deep, -shallow, and parameterised effect handlers. +The catchcont operator appears as a computation form in our calculus. +% +\begin{syntax} + &M,N \in \CompCat &::=& \cdots \mid \Catchcont~f.M +\end{syntax} +% +Unlike other delimited control operators, $\Catchcont$ does not +introduce separate explicit syntactic constructs for the control +delimiter and control reifier. Instead it leverages the higher-order +facilities of $\lambda$-calculus: the syntactic construct $\Catchcont$ +play the role of control delimiter and the name $f$ of function type +is the name of the control reifier. \citet{LongleyW08} describe $f$ as +a `dummy variable'. -In Part~\ref{p:implementation} I have devised two canonical -implementation strategies for the language, one based an -transformation into continuation passing style, and another based on -abstract machine semantics. Both strategies make key use of the notion -of generalised continuations, which provide a high-level model of -segmented runtime stacks. +The typing rule for $\Catchcont$ is as follows. +% +\begin{mathpar} + \inferrule* + {\typ{\Gamma, f : A \to B}{M : C \times D} \\ \Ground\;C} + {\typ{\Gamma}{\Catchcont~f.M : C \times ((A \to B) \to D) + (A \times (B \to (A \to B) \to C \times D))}} +\end{mathpar} +% +The computation handled by $\Catchcont$ must return a pair, where the +first component must be a ground value. This restriction ensures that +the value is not a $\lambda$-abstraction, which means that the value +cannot contain any further occurrence of the control reifier $f$. The +second component is unrestricted, and thus, it may contain further +occurrences of $f$. If $M$ fully reduces then $\Catchcont$ returns a +pair consisting of a ground value (i.e. an answer from $M$) and a +continuation function which allow $M$ to yield further +`answers'. Alternatively, if $M$ invokes the control reifier $f$, then +$\Catchcont$ returns a pair consisting of the argument supplied to $f$ +and the current continuation of the invocation of $f$. -In Part~\ref{p:expressiveness} I have explored how effect handlers fit -into the wider landscape of programming abstractions. I have shown -that deep, shallow, and parameterised effect handlers are -macro-expressible. Furthermore, I shown that effect handlers endow its -host language with additional computational power that provides an -asymptotic improvement in runtime performance for some class of -programs. +The operational rules for $\Catchcont$ are as follows. +% +\begin{reductions} + \slab{Value} & + \Catchcont~f . \Record{V;W} &\reducesto& \Inl\; \Record{V;\lambda\,f. W}\\ + \slab{Capture} & + \Catchcont~f .\EC[\,f\,V] &\reducesto& \Inr\; \Record{V; \lambda x. \lambda f. \Continue~\cont_{\EC}~x}\\ + \slab{Resume} & \Continue~\cont_{\EC}~V &\reducesto& \EC[V] +\end{reductions} +% +The \slab{Value} makes sure to bind any lingering instances of $f$ in +$W$ before escaping the delimiter. The \slab{Capture} rule reifies and +aborts the current evaluation up to, but no including, the delimiter, +which gets uninstalled. The reified evaluation context gets stored in +the second component of the returned pair. Importantly, the second +$\lambda$-abstraction makes sure to bind any instances of $f$ in the +captured evaluation context once it has been reinstated by the +\slab{Resume} rule. -\section{Programming with effect handlers} -Chapters~\ref{ch:base-language} and \ref{ch:unary-handlers} present -the design of a core calculus that forms the basis for Links, which is -a practical programming language with deep, shallow, and parameterised -effect handlers. A distinguishing feature of the core calculus is that -it is based on a structural notion of data and effects, whereas other -literature predominantly consider nominal data and effects. In the -setting of structural effects the effect system play a pivotal role in -ensuring that the standard safety and soundness properties of -statically typed programming languages hold as the effect system is -used to track type and presence information about effectful -operations. In a nominal setting an effect system is not necessary to -ensure soundness (e.g. Section~\ref{sec:handlers-calculus} presents a -sound core calculus with nominal effects, but without an effect -system). Irrespective of nominal or structural notions of effects, an -effect system is a valuable asset when programming with effect -handlers as an effect system enables modular reasoning about the -composition of functions. The effect system provides crucial -information about the introduction and elimination of effects. In the -absence of an effect system programmers are essentially required to -reason globally their programs as for instance the composition of any -two functions may introduce arbitrary effects that need to be handled -accordingly. Alternatively, a composition of any two functions may -inadvertently eliminate arbitrary effects, and as such, programming -with effect handlers without an effect system is prone to error. The -\UNIX{} case study in Chapter~\ref{ch:unary-handlers} demonstrates how -the effect system assists to ensure that effectful function -compositions are meaningful. +Let us consider an example use of catchcont to compute a tree +representing the interaction between a second-order function and its +first-order parameter. +% +\[ + \bl + \dec{odd} : (\Int \to \Bool) \to \Bool \times \UnitType\\ + \dec{odd}~f \defas \Record{\dec{xor}\,(f~0)\,(f~1); \Unit} + \el +\] +% +The function $\dec{odd}$ expects its environment to provide it with an +implementation of a single operation of type $\Int \to \Bool$. The +body of $\dec{odd}$ invokes, or queries, this operation twice with +arguments $0$ and $1$, respectively. The results are tested using +exclusive-or. -The particular effect system that I have used throughout this -dissertation is based on \citeauthor{Remy93}-style row polymorphism -formalism~\cite{Remy93}. Whilst \citeauthor{Remy93}-style row -polymorphism provides a suitable basis for structural records and -variants, its suitability as a basis for practical effect systems is -questionable. From a practical point of view the problem with this -form of row polymorphism is that it leads to verbose type-and-effect -signature due to the presence and absence annotations. In many cases -annotations are redundant, e.g. in second-order functions like -$\dec{map}$ for lists, where the effect signature of the function is -the same as the signature of its functional argument. From a -theoretical point of view this verbosity is not a concern. However, in -practice verbosity may lead to `an overload of unequivocal -information' by which I mean the programmer is presented with too many -trivial facts about the program. Too much information can hinder both -readability and writability of programs. For instance, in most -mainstream programming languages with System F-style type polymorphism -programmers normally do not have to annotate type variables with -kinds, unless they happen to be doing something special. Similarly, -programmers do not have to write type variable quantifiers, unless -they do not appear in prenex position. In practice some defaults are -implicitly understood and it is only when programmers deviate from -those defaults that programmers ought to supply the compiler with -explicit information. In Section~\ref{sec:effect-sugar} introduces -some ad-hoc syntactic sugar for effect signature that tames the -verbosity of an effect system based on \citeauthor{Remy93}-style row -polymorphism to the degree that second-order functions like -$\dec{map}$ do not duplicate information. Rather than back-patching -the effect system in hindsight, a possibly better approach is to -design the effect system for practical programming from the ground up -as \citet{LindleyMM17} did for the Frank programming language. +Now, let us implement the environment for $\dec{odd}$. +% +\[ + \bl + \dec{Dialogue} \defas [!:\Int;?:\Record{\Bool,\dec{Dialogue},\dec{Dialogue}}] \smallskip\\ + \dec{env} : ((\Int \to \Bool) \to \Bool \times \UnitType) \to \dec{Dialogue}\\ + \dec{env}~m \defas + \bl + \Case\;\Catchcont~f.m~f\;\{ + \ba[t]{@{}l@{~}c@{~}l} + \Inl~\Record{ans;\Unit} &\mapsto& \dec{!}ans;\\ + \Inr~\Record{q;k} &\mapsto& \dec{?}q\,(\dec{env}~k~\True)\,(\dec{env}~k~\False)\} + \ea + \el + \el +\] +% +Type $\dec{Dialogue}$ represents the dialogue between $\dec{odd}$ and +its parameter. The data structure is a standard binary tree with two +constructors: $!$ constructs a leaf holding a value of type $\Int$ and +$?$ constructs an interior node holding a value of type $\Bool$ and +two subtrees. The function $\dec{env}$ implements the environment that +$\dec{odd}$ will be run in. This function evaluates its parameter $m$ +under $\Catchcont$ which injects the operation $f$. If $m$ returns, +then the left component gets tagged with $!$, otherwise the argument +to the operation $q$ gets tagged with a $?$ along with the subtrees +constructed by the two recursive applications of $\dec{env}$. +% +% The primitive structure of catchcont makes it somewhat fiddly to +% program with it compared to other control operators as we have to +% manually unpack the data. -Nevertheless, the \UNIX{} case study is indicative of the syntactic -sugar being adequate in practice to build larger effect-oriented -applications. The case study demonstrates how effect handlers provide -a high-degree of modularity and flexibility that enable substantial -behavioural changes to be retrofitted onto programs without altering -the existing the code. Thus effect handlers provide a mechanism for -building small task-oriented programs that later can be scaled to -interact with other programs in a larger context. +The following derivation gives the high-level details of how +evaluation proceeds. % -The case study also demonstrates how one might ascribe a handler -semantics to a \UNIX{}-like operating system. The resulting operating -system \OSname{} captures the essential features of a true operating -system including support for managing multiple concurrent user -environments simultaneously, process parallelism, file I/O. The case -study also shows how each feature can be implemented in terms of some -standard effect. +\begin{figure} +\begin{center} + \scalebox{1.3}{\SXORTwoModel} + \end{center} + \caption{Visualisation of the result obtained by + $\dec{env}~\dec{odd}$.}\label{fig:decision-tree-cc} +\end{figure} +% +\begin{derivation} + &\dec{env}~\dec{odd}\\ + \reducesto^+ & \reason{$\beta$-reduction}\\ + &\bl + \Case\;\Catchcont~f.\;\Record{\dec{xor}\,(f~0)\,(f~1);\Unit}\{\cdots\} + % \ba[t]{@{}l@{~}c@{~}l} + % \Inl~\Record{ans;\Unit} &\mapsto& \dec{!}ans;\\ + % \Inr~\Record{q;k} &\mapsto& \dec{?}q\,(\dec{env}~k~\True)\,(\dec{env}~k~\False)\} + % \ea + \el\\ + \reducesto& \reason{\slab{Capture} $\EC = \Record{\dec{xor}\,[\,]\,(f~1),\Unit}$}\\ + &\bl + \Case\;\Inr\,\Record{0;\lambda x.\lambda f.\qq{\cont_{\EC}}\,x}\;\{\cdots\} + % \ba[t]{@{}l@{~}c@{~}l} + % \Inl~\Record{ans;\Unit} &\mapsto& \dec{!}ans;\\ + % \Inr~\Record{q;k} &\mapsto& \dec{?}q\,\bl (\dec{env}~\qq{\cont_{\EC}}~\True)\\(\dec{env}~\qq{\cont_{\EC}}~\False)\}\el + % \ea + \el\\ + \reducesto^+& \reason{\slab{Resume} $\EC$ with $\True$}\\ + &\dec{?}0\,(\Case\;\Catchcont~f.\;\Record{\dec{xor}~\True\,(f~1);\Unit}\{\cdots\})\,(\dec{env}~\qq{\cont_{\EC}}~\False)\\ + \reducesto^+& \reason{\slab{Capture} $\EC' = \Record{\dec{xor}~\True\,[\,], \Unit}$}\\ + &\dec{?}0\,(\dec{?}1\,(\dec{env}~\qq{\cont_{\EC'}}~\True)\,(\dec{env}~\qq{\cont_{\EC'}}~\False))\,(\dec{env}~\qq{\cont_{\EC}}~\False)\\ + \reducesto^+& \reason{\slab{Resume} $\EC'$ with $\True$}\\ + &\dec{?}0\,\bl(\dec{?}1\,(\Case\;\Catchcont~f.\Record{\dec{xor}~\True~\True;\Unit}\;\{\cdots\})\,(\dec{env}~\qq{\cont_{\EC'}}~\False))\\(\dec{env}~\qq{\cont_{\EC}}~\False)\el\\ + \reducesto^+& \reason{\slab{Value}}\\ + &\dec{?}0\,\bl(\dec{?}1\,(\Case\;\Inl~\Record{\False;\Unit}\;\{\cdots\})\,(\dec{env}~\qq{\cont_{\EC'}}~\False))\\(\dec{env}~\qq{\cont_{\EC}}~\False)\el\\ + \reducesto& \reason{$\beta$-reduction}\\ + &\dec{?}0\,\bl(\dec{?}1\,\dec{!}\False\,(\dec{env}~\qq{\cont_{\EC'}}~\False))\,(\dec{env}~\qq{\cont_{\EC}}~\False)\el\\ + \reducesto^+&\reason{Same reasoning}\\ + &?0\,(?1\,!\False\,!\True)\,(?1\,!\True\,!\False) +\end{derivation} +% +Figure~\ref{fig:decision-tree-cc} visualises this result as a binary +tree. The example here does not make use of the `continuation +component', the interested reader may consult \citet{LongleyW08} for +an example usage. +% \subsection{Second-class control operators} +% Coroutines, async/await, generators/iterators, amb. -\subsection{Future work} +% Backtracking: Amb~\cite{McCarthy63}. -\paragraph{Operating systems via effect handlers} -In the \UNIX{} case study we explored the paradigmatic reading of -\emph{effect handlers as composable operating systems} in practice by -composing a \UNIX{}-like operating system out of several effects and -handlers. Obviously, the resulting system \OSname{} has been -implemented in the combined core calculus consisting of $\HCalc$, -$\SCalc$, and $\HPCalc$ calculi. There also exists an actual runnable -implementation of it in Links. It would be interesting to implement -the system in other programming languages with support for effect -handlers as at the time of writing most languages with effect handlers -have some unique trait, e.g. lexical handlers, special effect system, -etc. Ultimately, re-implementing the case study can help collect more -data points about programming with effect handlers, which can -potentially serve to inform the design of future effect -handler-oriented languages. +% Coroutines~\cite{DahlDH72} as introduced by Simula +% 67~\cite{DahlMN68}. The notion of coroutines was coined by Melvin +% Conway, who used coroutines as a code idiom in assembly +% programs~\cite{Knuth97}. Canonical reference for implementing +% coroutines with call/cc~\cite{HaynesFW86}. -I have made no attempts at formally proving the correctness of -\OSname{} with respect to some specification. Although, I have -consciously opted to implement \OSname{} using standard effects with -well-known equations. Furthermore, the effect handlers have been -implemented such that they ought to respect the equations of their -effects. Thus, perhaps it is possible to devise an equational -specification for the operating system and prove the implementation -correct with respect to that specification. +\section{Programming continuations} +\label{sec:programming-continuations} +%Blind vs non-blind backtracking. Engines. Web +% programming. Asynchronous +% programming. Coroutines. -One important feature that is arguably missing from \OSname{} is -external signal handling. Effect handlers as signal handlers is not a -new idea. In a previous paper we have outlined an idea for using -effect handlers to handle POSIX signals~\cite{DolanEHMSW17}. Signal -handling is a delicate matter as signals introduce a form of -preemption, thus some care needs to be taken to ensure that the -interpretation of a signal does not interrupted by another signal -instance. The essence of the idea is to have a \emph{mask} primitive, -which is a form of critical section for signals that permits some -block of code to suppress signal interruptions. A potential starting -point would be to combine \citeauthor{AhmanP21}'s calculus of -asynchronous effects with $\HCalc$ to explore this idea more -formally~\cite{AhmanP21}. +Amongst the first uses of continuations were modelling of unrestricted +jumps, such as \citeauthor{Landin98}'s modelling of \Algol{} labels +and gotos using the J +operator~\cite{Landin65,Landin65a,Landin98,Reynolds93}. -Another interesting thought is to implement an actual operating system -using effect handlers. Although, it might be a daunting task, the idea -is maybe not so far fetched. With the advent of effect handlers in -OCaml~\cite{SivaramakrishnanDWKJM21} it may be possible for MirageOS -project~\cite{MadhavapeddyS14}, which is a unikernel based operating -system written in OCaml, to take advantage of effect handlers to -implement features such as concurrency. +Backtracking is another early and prominent use of continuations. For +example, \citet{Burstall69} used the J operator to implement a +heuristic-driven search procedure with continuation-backed +backtracking for tree-based search. +% +Somewhat related to backtracking, \citet{FriedmanHK84} posed the +\emph{devils and angels problem} as an example that has no direct +solution in a programming language without first-class control. Any +solution to the devils and angels problem involves extensive +manipulation of control to jump both backwards and forwards to resume +computation. -\paragraph{Effect-based optimisations} In this dissertation I have not -considered any effect-based optimisations. However if effect handler -oriented programming is to succeed in practice, then runtime -performance will matter. Optimisation of program structure is one way -to improve runtime performance. At our disposal we have the effect -system and the algebraic structure of effects and handlers. +If the reader ever find themselves in a quiz show asked to single out +a canonical example of continuation use, then implementation of +concurrency would be a qualified guess. Cooperative concurrency in +terms of various forms of coroutines as continuations occur so +frequently in the literature and in the wild that they have become +routine. % -Taking advantage of the information provided by the effect system to -optimise programs is an old idea that has been explored previously in -the literature~\cite{KammarP12,Kammar14,Saleh19}. +\citet{HaynesFW86} published one of the first implementations of +coroutines using first-class control. % -Other work has attempted to exploit the algebraic structure of (deep) -effect handlers to fuse nested handlers~\cite{WuS15}. +Preemptive concurrency in the form of engines were implemented by +\citet{DybvigH89}. An engine is a control abstraction that runs +computations with an allotted time budget~\cite{HaynesF84}. They used +continuations to represent strands of computation and timer interrupts +to suspend continuations. % -An obvious idea is to apply these lines of work to the handler calculi -of Chapter~\ref{ch:unary-handlers}. +\citet{KiselyovS07a} used delimited continuations to explain various +phenomena of operating systems, including multi-tasking and file +systems. % -Moreover, I hypothesise there is untapped potential in the combination -of effect-dependent analysis with respect to equational theories to -optimise effectful programs. A potential starting point for testing -out this hypothesis is to take \citeauthor{LuksicP20}'s a core -calculus where effects are equipped with equations~\cite{LuksicP20} -and combine it with techniques for effect-dependent -optimisations~\cite{KammarP12}. +On the web, \citet{Queinnec04} used continuations to model the +client-server interactions. This model was adapted by +\citet{CooperLWY06} in Links with support for an Erlang-style +concurrency model~\cite{ArmstrongVW93}. +% +\citet{Leijen17a} and \citet{DolanEHMSW17} gave two different ways of +implementing the asynchronous programming operator async/await as a +user-definable library. +% +In the setting of distributed programming, \citet{BracevacASEEM18} +describe a modular event correlation system that makes crucial use of +effect handlers. \citeauthor{Bracevec19}'s PhD dissertation +explicates the theory, design, and implementation of event correlation +by way of effect handlers~\cite{Bracevec19}. -\paragraph{Multi handlers} In this dissertation I have solely focused -on so-called \emph{unary} handlers, which handle a \emph{single} -effectful computation. A natural generalisation is \emph{n-ary} -handlers, which allow $n$ effectful computations to be handled -simultaneously. In the literature n-ary handlers are called -\emph{multi handlers}, and unary handlers are simply called -handlers. The ability to handle two or more computations -simultaneously make for a straightforward way to implement -synchronisation between two or more computations. For example, the -pipes example of Section~\ref{sec:pipes} can be expressed using a -single handler rather than two dual -handlers~\cite{LindleyMM17}. Shallow multi handlers are an ample -feature of the Frank programming language~\cite{LindleyMM17}. The -design space of deep and parameterised notions of multi handlers have -yet to be explored as well as their applications domains. Thus an -interesting future direction of research would be to extend $\HCalc$ -with multi handlers and explore their practical programming -applicability. Retrofitting the effect system of $\HCalc$ to provide a -good programmer experience for programming with multi handlers pose an -interesting design challenge as any quirks that occur with unary -handlers only get amplified in the setting of multi handlers. +Continuations have also been used in meta-programming to speed up +partial evaluation and +multi-staging~\cite{LawallD94,KameyamaKS11,OishiK17,Yallop17,WeiBTR20}. Let +insertion is a canonical example of use of continuations in +multi-staging~\cite{Yallop17}. -\paragraph{Handling linear resources} The implementation of effect -handlers in Links makes the language unsound, because the \naive{} -combination of effect handlers and session typing is unsound. The -combined power of being able to discard some resumptions and resume -others multiple times can make for bad interactions with sessions. For -instance, suppose some channel supplies only one value, then it is -possible to break session fidelity by twice resuming some resumption -that closes over a receive operation. Similarly, it is possible to -break type safety by using a combination of exceptions and multi-shot -resumptions, e.g. suppose some channel first expects an integer -followed by a boolean, then the running the program -$\Do\;\Fork\,\Unit;\keyw{send}~42;\Absurd\;\Do\;\Fail\,\Unit$ under -the composition of the nondeterminism handler and default failure -handler from Chapter~\ref{ch:unary-handlers} will cause the primitive -$\keyw{send}$ operation to supply two integers in succession, thus -breaking the session protocol. Figuring out how to safely combine -linear resources, such as channels, and handlers with multi-shot -resumptions is an interesting unsolved problem. +Probabilistic programming is yet another application domain of +continuations. \citet{KiselyovS09} used delimited continuations to +speed up probabilistic programs. \citet{GorinovaMH20} used +continuations to achieve modularise probabilistic programs and to +provide a simple and efficient mechanism for reparameterisation of +inference algorithms. +% +In the subject of differentiable programming \citet{WangZDWER19} +explained reverse-mode automatic differentiation operators in terms of +delimited continuations. -\section{Canonical implementation strategies for handlers} -Chapter~\ref{ch:cps} carries out a comprehensive study of CPS -translations for deep, shallow, and parameterised notions of effect -handlers. +The aforementioned applications of continuations are by no means +exhaustive, though, the diverse application spectrum underlines the +versatility of continuations. + +\section{Constraining continuations} +\label{sec:constraining-continuations} + +\citet{FriedmanH85} advocated for constraining the power of +(undelimited) continuations~\cite{HaynesF87}. % -We arrive at a higher-order CPS translation through step-wise -refinement of an initial standard first-order fine-grain call-by-value -CPS translation, which we extended to support deep effect -handlers. Firstly, we refined the first-order translation by -uncurrying it in order to yield a properly tail-recursive -translation. Secondly, we adapted it to a higher-order one-pass -translation that statically eliminates administrative -redexes. Thirdly, we solidified the structure of continuations to -arrive at the notion of \emph{generalised continuation}, which -provides the basis for implementing shallow and parameterised -handlers. +Even though, they were concerned with callcc and undelimited +continuations some of their arguments are applicable to other control +operators and delimited continuations. % -The CPS translations have been proven correct with respect to the -contextual small-step semantics of $\HCalc$, $\SCalc$, and $\HPCalc$. +For example, they argued in favour of restricting continuations to be +one-shot, which means continuations may only be invoked once. Firstly, +because one-shot continuations admit particularly efficient +implementations. Secondly, many applications involve only single use +of continuations. Thirdly, one-shot continuations interact more +robustly with resources, such as file handles, than general multi-shot +continuations, because multiple use of a continuation may accidentally +interact with a resource after it has been released. -Generalised continuations are a succinct syntactic framework for -modelling low-level stack manipulations. The structure of generalised -continuations closely mimics the structure of \citeauthor{HiebDB90} -and \citeauthor{BruggemanWD96}'s segmented -stacks~\cite{HiebDB90,BruggemanWD96}, which is a state-of-art -technique for implementing first-class -control~\cite{FlattDDKMSTZ19}. Each generalised continuation frame -consists of a pure continuation and a handler definition. The pure -continuation represents an execution stack delimited by some -handler. Thus chaining together generalised continuation frames yields -a sequence of segmented stacks. +One-shot continuations by themselves are no saving grace for avoiding +resource leakage as they may be dropped or used to perform premature +exits from a block with resources. For example, Racket provides the +programmer with a facility known as \emph{dynamic-wind} to protect a +context with resources such that non-local exits properly release +whatever resources the context has acquired~\cite{Flatt20}. +% +An alternative approach is taken by Multicore OCaml, whose +implementation of effect handlers with one-shot continuations provides +both a \emph{continue} primitive for continuing a given continuation +and a \emph{discontinue} primitive for aborting a given +continuation~\cite{DolanWSYM15,DolanEHMSW17}. The latter throws an +exception at the operation invocation site to which can be caught by +local exception handlers to release resources properly. +% +This approach is also used by \citet{Fowler19}, who uses a +substructural type system to statically enforce the use of +continuations, either by means of a continue or a discontinue. -The versatility of generalised continuations is illustrated in -Chapter~\ref{ch:abstract-machine}, where we plugged the notion of -generalised continuation into \citeauthor{FelleisenF86}'s CEK machine -to obtain an adequate execution runtime with simultaneous support for -deep, shallow, and parameterised effect -handlers~\cite{FelleisenF86}. The resulting abstract machine is proven -correct with respect to the reduction semantics of the combined -calculus of $\HCalc$, $\SCalc$, and $\HPCalc$. The abstract machine -provides a blueprint for both high-level interpreter-based -implementations of effect handlers as well as low-level -implementations based on stack manipulations. The server-side -implementation of effect handlers in the Links programming language is -a testimony to the former~\cite{HillerstromL16}, whilst the Multicore -OCaml implementation of effect handlers is a testimony to the -latter~\cite{SivaramakrishnanDWKJM21}. +% For example callec is a variation of callcc where continuation +% invocation is only defined during the dynamic extent of +% callec~\cite{Flatt20}. -\subsection{Future work} +\section{Implementing continuations} +\label{sec:implementing-continuations} -\paragraph{Functional correspondence} The CPS translations and -abstract machine have been developed separately. Even though, the -abstract machine is presented as an application of generalised -continuations in Chapter~\ref{ch:abstract-machine} it did appear -before the CPS translations. The idea of generalised continuation -first solidified during the design of higher-order CPS translation for -shallow handlers~\cite{HillerstromL18}, where we adapted the -continuation structure of our initial abstract machine -design~\cite{HillerstromL16}. Thus it seems that there ought to be a -formal functional correspondence between higher-order CPS translation -and the abstract machine, however, the existence of such a -correspondence has yet to be established. +There are numerous strategies for implementing continuations. There is +no best implementation strategy. Each strategy has different +trade-offs, and as such, there is no ``best'' strategy. In this +section, I will briefly outline the gist of some implementation +strategies and their trade-offs. For an in depth analysis the +interested reader may consult the respective work of +\citet{ClingerHO88} and \citet{FarvardinR20}, which contain thorough +studies of implementation strategies for first-class continuations. +% +Table~\ref{tbl:ctrl-operators-impls} lists some programming languages +with support for first-class control operators and their +implementation strategies. + +\begin{table} + \centering + \begin{tabular}{| l | >{\raggedright}p{4.3cm} | l |} + \hline + \multicolumn{1}{|c|}{\textbf{Language}} & \multicolumn{1}{c |}{\textbf{Control operators}} & \multicolumn{1}{c|}{\textbf{Implementation strategies}}\\ + \hline + Eff & Effect handlers & Virtual machine, interpreter \\ + \hline + Effekt & Lexical effect handlers & CPS\\ + \hline + Frank & N-ary effect handlers & CEK machine \\ + % \hline + % Gauche & callcc, shift/reset & Virtual machine \\ + \hline + Helium & Effect handlers & CEK machine \\ + \hline + Koka & Effect handlers & Continuation monad\\ + \hline + Links & Effect handlers, escape & CEK machine, CPS\\ + \hline + MLton & callcc & Stack copying\\ + \hline + Multicore OCaml & Affine effect handlers & Segmented stacks\\ + \hline + OchaCaml & shift/reset & Virtual machine\\ + \hline + Racket & callcc, \textCallcomc{}, cupto, fcontrol, control/prompt, shift/reset, splitter, spawn & Segmented stacks\\ + \hline + % Rhino JavaScript & JI & Interpreter \\ + % \hline + Scala & shift/reset & CPS\\ + \hline + SML/NJ & callcc & CPS\\ + \hline + Wasm/k & control/prompt & Virtual machine \\ + \hline + \end{tabular} + \caption{Some languages and their implementation strategies for continuations.}\label{tbl:ctrl-operators-impls} +\end{table} +% +The control stack provides a adequate runtime representation of +continuations as the contiguous sequence of activation records quite +literally represent what to do next. +% +Thus continuation capture can be implemented by making a copy of the +current stack (possibly up to some delimiter), and continuation +invocation as reinstatement of the stack. This implementation strategy +works well if continuations are captured infrequently. The MLton +implementation of Standard ML utilises this strategy~\cite{Fluet20}. +A slight variation is to defer the first copy action until the +continuation is invoked, which requires marking the stack to remember +which sequence of activation records to copy. -\paragraph{Abstracting continuations} It is evident from the step-wise -refinement of the CPS translations in Chapter~\ref{ch:cps} that each -translation has a certain structure to it. +Obviously, frequent continuation use on top of a stack copying +implementation can be expensive time wise as well as space wise, +because with undelimited continuations multiple copies of the stack +may be alive simultaneously. % -In fact, this is how the CPS translation for effect handlers in Links -has been implemented. Concretely, the translation is implemented as a -functor, which is parameterised by a continuation interface. The -continuation interface has monoidal operation for continuation -extension and an application operation for applying the continuation -to a value argument. Theoretically, it would be interesting to pin -down and understand the precise algebraic nature of this nature would -be interesting with respect to abstracting the notion of -continuations. Practically, it would keep the code base modular and -pave the way for rapid compilation of new control structures. Ideally -one would simply have to implement a standard CPS translation, which -keeps the notion of continuation abstract such that any conforming -continuation can be plugged in. - -\paragraph{Generalising generalised continuations} The incarnation of -generalised continuations in this dissertation has been engineered for -unary handlers. An obvious extension to investigate is support for -multi handlers. With multi handlers, handler definitions enter a -one-to-many relationship with pure continuations rather than an -one-to-one relationship with unary handlers. Thus at minimum the -structure of generalised continuation frames needs to be altered such -that each handler definition is paired with a list of pure -continuations, where each pure continuation represents a distinct -computation running under the handler. - -\paragraph{Ad-hoc generalised continuations} -The literature contains plenty of ad-hoc techniques for realising -continuations. For instance, \citeauthor{PettyjohnCMKF05}'s technique -for implementing undelimited continuations via exception handlers and -state~\cite{PettyjohnCMKF05}, and \citeauthor{JamesS11}'s technique -for implementing delimited control via generators and -iterators~\cite{JamesS11}. Such techniques may be used to implement -effect handlers in control hostile environments by simulating the -structure of generalised continuations. By using these techniques to -implement effect handlers we may be able to bring effect handler -oriented programming to programming languages that do not offer -programmers much control. - -\paragraph{Typed CPS for effect handlers} The image of each -translation developed in Chapter~\ref{ch:cps} is untyped. Typing the -translations may provide additional insight into the semantic content -of the translations. Effect forwarding poses a challenge in typing the -image. In order to encode forwarding we need to be able to -parametrically specify what a default case does. +Typically the prefix of copies will be identical, which suggests they +ought to be shared. One way to achieve optimal sharing is to move from +a contiguous stack to a non-contiguous stack representation, +e.g. representing the stack as a heap allocated linked list of +activation records~\cite{Danvy87}. With such a representation copying +is a constant time and space operation, because there is no need to +actually copy anything as the continuation is just a pointer into the +stack. % -The Appendix B of the paper by \citet{HillerstromLAS17} outlines a -possible typing for the CPS translation for deep handlers. The -extension we propose to our row type system is to allow a row type to -be given a \emph{shape} (something akin to -\citeauthor{BerthomieuS95}'s tagged types~\cite{BerthomieuS95}), which -constrains the form of the ordinary types it contains. A full -formalisation of this idea remains to be done. - - -\section{On the expressive power of effect handlers} - -In Chapter~\ref{ch:deep-vs-shallow} we investigated the -interdefinability of deep, shallow, and parameterised handlers through -the lens of typed macro expressiveness. We establish that every kind -of handler is interdefinable. Although, the handlers are -interdefinable it may matter in practice which kind of handler is -being employed. For example, the encoding of shallow handlers using -deep handlers is rather inefficient. The encoding suffers from space -leaks as demonstrated empirically in Appendix B.3 of -\citet{HillerstromL18}. Similarly, the runtime and memory performance -of between native parameterised handlers and encoding parameterised -handlers as ordinary deep handlers may be observable in practice as -the latter introduce a new closure per operation invocation. +The disadvantage of this strategy is that it turns every operation +into an indirection. -Chapter~\ref{ch:handlers-efficiency} explores the relative efficiency -of a base language, $\BPCF$, and its extension with effect handlers, -$\HPCF$, through the lens of type-respecting expressivity. Concretely, -we used the example program of \emph{generic count} to show that -$\HPCF$ admits realisations of this program whose asymptotic -efficiency is better than any possible realisation in -$\BPCF$. Concretely, we established that the lower bound of generic -count on $n$-standard predicates in $\BPCF$ is $\Omega(n2^n)$, whilst -the worst case upper bound in $\HPCF$ is $\BigO(2^n)$. Hence there is -a strict efficiency gap between the two languages. We observed this -efficiency gap in practice on several benchmarks. +Segmented stacks provide a middle ground between contiguous stack and +non-contiguous stack representations. With this representation the +control stack is represented as a linked list of contiguous stacks +which makes it possible to only copy a segment of the stack. The +stacks grown and shrink dynamically as needed. This representation is +due to \citet{HiebDB90}. It is used by Chez Scheme, which is the +runtime that powers Racket~\cite{FlattD20}. % -The lower runtime bound also applies to a language $\BSPCF$ which -extends $\BPCF$ with state. +For undelimited continuations the basic idea is to create a pointer to +the current stack upon continuation capture, and then allocate a new +stack where subsequent computation happens. % -Although, I have not spelled out the details here, in -\citet{HillerstromLL20} we have verified that the lower bound also -applies to a language $\BEPCF$ with \citeauthor{BentonK01}-style -\emph{exceptions} and handlers~\cite{BentonK01}. +For delimited continuations the control delimiter identify when a new +stack should be allocated. % -The lower bound also applies to the combined language $\BSEPCF$ -with both state and exceptions --- this seems to bring us close to -the expressive power of real languages such as Standard ML, Java, and -Python, strongly suggesting that the speedup we have discussed is -unattainable in these languages. +A potential problem with this representation is \emph{stack + thrashing}, which is a phenomenon that occurs when a stack is being +continuously resized. +% +This problem was addressed by \citet{BruggemanWD96}, who designed a +slight variation of segmented stacks optimised for one-shot +continuations, which has been adapted by Multicore +OCaml~\cite{DolanEHMSW17}. -The positive result for $\HPCF$ extends to other control operators by -appeal to existing results on interdefinability of handlers and other -control operators~\cite{ForsterKLP19,PirogPS19}. +Full stack copying and segmented stacks both depend on being able to +manipulate the stack directly. This is seldom possible if the language +implementer do not have control over the target runtime, +e.g. compilation to JavaScript. However, it is possible to emulate +stack copying and segmented stacks in lieu of direct stack access. For +example, \citet{PettyjohnCMKF05} describe a technique that emulates +stack copying by piggybacking on the facile stack inception facility +provided by exception handlers in order to lazily reify the control +stack. % -% The result no longer applies directly if we add an effect type system -% to $\HPCF$, as the implementation of the counting program would -% require a change of type for predicates to reflect the ability to -% perform effectful operations. +\citet{KumarBD98} emulated segmented stacks via threads. Each thread +has its own local stack, and as such, a collection of threads +effectively models segmented stacks. To actually implement +continuations as threads \citeauthor{KumarBD98} also made use of +standard synchronisation primitives. % -% In future we plan to investigate how to account for effect type systems. - -From a practical point of view one might be tempted to label the -efficiency result as merely of theoretical interest, since an -$\Omega(2^n)$ runtime is already infeasible. However, what has been -presented is an example of a much more pervasive phenomenon, and the -generic count example serves merely as a convenient way to bring this -phenomenon into sharp formal focus. For example, suppose that our -programming task was not to count all solutions to $P$, but to find -just one of them. It is informally clear that for many kinds of -predicates this would in practice be a feasible task, and also that we -could still gain our factor $n$ speedup here by working in a language -with first-class control. However, such an observation appears less -amenable to a clean mathematical formulation, as the runtimes in -question are highly sensitive to both the particular choice of -predicate and the search order employed. - -\subsection{Future work} - -\paragraph{Efficiency of handler encodings} Although, I do not give a -formal proof for the efficiency of the shallow as deep encoding in -Chapter~\ref{ch:deep-vs-shallow} it seems intuitively clear that the -encoding is rather inefficient. In fact in Appendix B.2 and B.3 of -\citet{HillerstromL18} we show empirically that the encoding is -inefficient. An interesting question is whether there exists an -efficient encoding of shallow handlers using deep handlers. Formally -proving the absence of an efficient encoding would give a strong -indication of the relative computational expressive power between -shallow and deep handlers. Likewise discovering that an efficient -encoding does exist would tell us that it may not matter -computationally whether a language incorporates shallow or deep -handlers. - -\paragraph{Effect tracking breaks asymptotic improvement} The -result of Chapter~\ref{ch:handlers-efficiency} does not immediately -carry over to a language with an effect system as the implementation -of generic search in \HPCF{} would introduce an effectful operation, -which requires a change of types. In order to state and prove the -result in the presence of an effect system some other refined, -possibly new, notion of expressivity seems necessary. - -\paragraph{Asymptotic improvement with affine handlers} -The result of Chapter~\ref{ch:handlers-efficiency} does not -immediately remain true in the presence of affine effect handlers -(handlers which their resumptions at most once) as they make it -possible to encode coroutines. The present proof method does not -readily adapt to a situation with coroutines, because the proof depend -at various points on an orderly nesting of subcomputations which -corouting would break. +The advantage of these techniques is that they are generally +applicable and portable. The disadvantage is the performance overhead +induced by emulation. -\paragraph{Efficiency hierarchy of control} The definability hierarchy -of various control constructs such as iteration, recursion, recursion -with state, and first class control is fairly -well-understood~\cite{LongleyN15,Longley18a,Longley19}. However, the -relative asymptotic efficiency between them is less -well-understood. It would be interesting to formally establish a -hierarchy of relative asymptotic efficiency between various control -constructs in the style of Chapter~\ref{ch:handlers-efficiency}. +Abstract and virtual machines are a form of full machine emulation. An +abstract machine is an idealised machine. Abstract machines, such as +the CEK machine~\cite{FelleisenF86}, are attractive because they +provide a suitably high-level framework for defining language +semantics in terms of control string manipulations, whilst admitting a +direct implementation. +% +We will discuss abstract machines in more detail in +Chapter~\ref{ch:abstract-machine}. +% +The term virtual machine typically connotes an abstract machine that +works on a byte code representation of programs, whereas the default +connotation of abstract machine is a machine that works on a rich +abstract syntax tree representation of programs. +% \citeauthor{Landin64}'s SECD machine was the +% first abstract machine for evaluating $\lambda$-calculus +% terms~\cite{Landin64,Danvy04}. +% +% Either machine model has an explicit representation of the control +% state in terms of an environment and a control string. Thus either machine can to the +% interpretative overhead. +The disadvantage of abstract machines is their interpretative +overhead, although, techniques such as just-in-time compilation can be +utilised to reduce this overhead. -%% -%% Appendices -%% -\part{Appendices} -\appendix +Continuation passing style (CPS) is a canonical implementation +strategy for continuations --- the word `continuation' even feature in +its name. +% +CPS is a particular idiomatic notation for programs, where every +function takes an additional argument, the current continuation, as +input and every function call appears in tail position. Consequently, +every aspect of control flow is made explicit, which makes CPS a good +fit for implementing control abstraction. In classic CPS the +continuation argument is typically represented as a heap allocated +closure~\cite{Appel92}, however, as we shall see in +Chapter~\ref{ch:cps} richer representations of continuations are +possible. +% +At first thought it may seem that CPS will not work well in +environments that lack proper tail calls such as JavaScript. However, +the contrary is true, because the stackless nature of CPS means it can +readily be implemented with a trampoline~\cite{GanzFW99}. Alas, at the +cost of the indirection induced by the trampoline. \chapter{Get get is redundant} \label{ch:get-get}