My PhD dissertation at the University of Edinburgh, Scotland https://www.dhil.net/research/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

6126 lines
246 KiB

%% 12pt font size, PhD thesis, LFCS, print twosided, new chapters on right page
\documentclass[12pt,phd,lfcs,twoside,openright,logo,leftchapter,normalheadings]{infthesis}
\shieldtype{0}
%% Packages
\usepackage[utf8]{inputenc} % Enable UTF-8 typing
\usepackage[british]{babel} % British English
\usepackage[breaklinks]{hyperref} % Interactive PDF
\usepackage{url}
\usepackage[sort&compress,square,numbers]{natbib} % Bibliography
\usepackage{bibentry} % Print bibliography entries inline.
\nobibliography* % use the bibliographic data from the standard BibTeX setup.
\usepackage{breakurl}
\usepackage{amsmath} % Mathematics library
\usepackage{amssymb} % Provides math fonts
\usepackage{amsthm} % Provides \newtheorem, \theoremstyle, etc.
\usepackage{mathtools}
\usepackage{pkgs/mathpartir} % Inference rules
\usepackage{pkgs/mathwidth}
\usepackage{stmaryrd} % semantic brackets
\usepackage{array}
\usepackage{float} % Float control
\usepackage{caption,subcaption} % Sub figures support
\DeclareCaptionFormat{underlinedfigure}{#1#2#3\hrulefill}
\captionsetup[figure]{format=underlinedfigure}
\usepackage[T1]{fontenc} % Fixes issues with accented characters
%\usepackage{libertine}
%\usepackage{lmodern}
%\usepackage{palatino}
% \usepackage{newpxtext,newpxmath}
\usepackage[scaled=0.80]{beramono}
\usepackage[activate=true,
final,
tracking=true,
kerning=true,
spacing=true,
factor=1100,
stretch=10,
shrink=10]{microtype}
\SetProtrusion{encoding={*},family={bch},series={*},size={6,7}}
{1={ ,750},2={ ,500},3={ ,500},4={ ,500},5={ ,500},
6={ ,500},7={ ,600},8={ ,500},9={ ,500},0={ ,500}}
\SetExtraKerning[unit=space]
{encoding={*}, family={bch}, series={*}, size={footnotesize,small,normalsize}}
{\textendash={400,400}, % en-dash, add more space around it
"28={ ,150}, % left bracket, add space from right
"29={150, }, % right bracket, add space from left
\textquotedblleft={ ,150}, % left quotation mark, space from right
\textquotedblright={150, }} % right quotation mark, space from left
\usepackage{enumerate} % Customise enumerate-environments
\usepackage{xcolor} % Colours
\usepackage{xspace} % Smart spacing in commands.
\usepackage{tikz}
\usetikzlibrary{fit,calc,trees,positioning,arrows,chains,shapes.geometric,%
decorations.pathreplacing,decorations.pathmorphing,shapes,%
matrix,shapes.symbols,intersections,tikzmark}
\usepackage[customcolors,shade]{hf-tikz} % Shaded backgrounds.
\hfsetfillcolor{gray!40}
\hfsetbordercolor{gray!40}
% Cancelling
\newif\ifCancelX
\tikzset{X/.code={\CancelXtrue}}
\newcommand{\Cancel}[2][]{\relax
\ifmmode%
\tikz[baseline=(X.base),inner sep=0pt] {\node (X) {$#2$};
\tikzset{#1}
\draw[#1,overlay,shorten >=-2pt,shorten <=-2pt] (X.south west) -- (X.north east);
\ifCancelX
\draw[#1,overlay,shorten >=-2pt,shorten <=-2pt] (X.north west) -- (X.south east);
\fi}
\else
\tikz[baseline=(X.base),inner sep=0pt] {\node (X) {#2};
\tikzset{#1}
\draw[#1,overlay,shorten >=-2pt,shorten <=-2pt] (X.south west) -- (X.north east);
\ifCancelX
\draw[#1,overlay,shorten >=-2pt,shorten <=-2pt] (X.north west) -- (X.south east);
\fi}%
\fi}
\newcommand{\XCancel}[1]{\Cancel[red,X,line width=1pt]{#1}}
%%
%% Theorem environments
%%
\theoremstyle{plain}
\newtheorem{theorem}{Theorem}[chapter]
\newtheorem{lemma}[theorem]{Lemma}
\newtheorem{proposition}[theorem]{Proposition}
\newtheorem{corollary}[theorem]{Corollary}
\newtheorem{definition}[theorem]{Definition}
% Example environment.
\makeatletter
\def\@endtheorem{\hfill$\blacksquare$\endtrivlist\@endpefalse} % inserts a black square at the end.
\makeatother
\theoremstyle{definition}
\newtheorem{example}{Example}[chapter]
%%
%% Load macros.
%%
\input{macros}
%% Information about the title, etc.
% \title{Higher-Order Theories of Handlers for Algebraic Effects}
% \title{Handlers for Algebraic Effects: Applications, Compilation, and Expressiveness}
% \title{Applications, Compilation, and Expressiveness for Effect Handlers}
% \title{Handling Computational Effects}
% \title{Programming Computable Effectful Functions}
% \title{Handling Effectful Computations}
\title{Foundations for Programming and Implementing Effect Handlers}
\author{Daniel Hillerström}
%% If the year of submission is not the current year, uncomment this line and
%% specify it here:
\submityear{2020}
%% Specify the abstract here.
\abstract{%
An abstract\dots
}
%% Now we start with the actual document.
\begin{document}
\raggedbottom
%% First, the preliminary pages
\begin{preliminary}
%% This creates the title page
\maketitle
%% Acknowledgements
\begin{acknowledgements}
Firstly, I want to thank Sam Lindley for his guidance, advice, and
encouragement throughout my studies. He has been enthusiastic
supervisor, and he has always been generous with his time. I am
fortunate to have been supervised by him.
Throughout my studies I have received funding from the
\href{https://www.ed.ac.uk/informatics}{School of Informatics} at
The University of Edinburgh, as well as an
\href{https://www.epsrc.ac.uk/}{EPSRC} grant
\href{http://pervasiveparallelism.inf.ed.ac.uk}{EP/L01503X/1} (EPSRC
Centre for Doctoral Training in Pervasive Parallelism), and by ERC
Consolidator Grant Skye (grant number 682315).
List of people to thank
\begin{itemize}
\item Sam Lindley
\item John Longley
\item Christophe Dubach
\item KC Sivaramakrishnan
\item Stephen Dolan
\item Anil Madhavapeddy
\item Gemma Gordon
\item Leo White
\item Andreas Rossberg
\item Robert Atkey
\item Jeremy Yallop
\item Simon Fowler
\item Craig McLaughlin
\item Garrett Morris
\item James McKinna
\item Brian Campbell
\item Paul Piho
\item Amna Shahab
\item Gordon Plotkin
\item Ohad Kammar
\item School of Informatics (funding)
\item Google (Kevin Millikin, Dmitry Stefantsov)
\item Microsoft Research (Daan Leijen)
\end{itemize}
\end{acknowledgements}
%% Next we need to have the declaration.
% \standarddeclaration
\begin{declaration}
I declare that this thesis was composed by myself, that the work
contained herein is my own except where explicitly stated otherwise
in the text, and that this work has not been submitted for any other
degree or professional qualification except as specified.
The following previously published work of mine features prominently
within this dissertation. Each chapter details the relevant
relations to my previous work.
%
\begin{itemize}
\item \bibentry{HillerstromL16}
\item \bibentry{HillerstromLAS17}
\item \bibentry{HillerstromL18}
\item \bibentry{HillerstromLA20}
\item \bibentry{HillerstromLL20}
\end{itemize}
%
\end{declaration}
%% Finally, a dedication (this is optional -- uncomment the following line if
%% you want one).
% \dedication{To my mummy.}
% \dedication{\emph{To be or to do}}
\dedication{\emph{Bara du sätter gränserna}}
% \begin{preface}
% A preface will possibly appear here\dots
% \end{preface}
%% Create the table of contents
\setcounter{secnumdepth}{2} % Numbering on sections and subsections
\setcounter{tocdepth}{1} % Show chapters, sections and subsections in TOC
%\singlespace
\tableofcontents
%\doublespace
%% If you want a list of figures or tables, uncomment the appropriate line(s)
% \listoffigures
% \listoftables
\end{preliminary}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Main content %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Introduction
%%
\chapter{Introduction}
\label{ch:introduction}
An enthralling introduction\dots
%
Motivation: 1) compiler perspective: unifying control abstraction,
lean runtime, desugaring of async/await, generators/iterators, 2)
giving control to programmers, safer microkernels, everything as a
library.
\section{Why first-class control matters}
\subsection{Flavours of control}
\paragraph{Undelimited control}
\paragraph{Delimited control}
\paragraph{Composable control}
\subsection{Why effect handlers}
\section{Thesis outline}
Thesis outline\dots
\section{Typographical conventions}
Explain conventions\dots
\part{Background}
\label{p:background}
\chapter{The state of effectful programming}
\label{ch:related-work}
\section{Type and effect systems}
\section{Monadic programming}
\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}
\chapter{Continuations}
\label{ch:continuations}
\section{Zoo of control operators}
Describe how effect handlers fit amongst shift/reset, prompt/control,
callcc, J, catchcont, etc.
\section{Implementation strategies}
\part{Design}
\chapter{An ML-flavoured programming language based on rows}
\label{ch:base-language}
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
\citet{LindleyC12}, which distils the essence of the functional
multi-tier web-programming language
\Links{}~\cite{CooperLWY06}. \Links{} belongs to the
ML-family~\cite{MilnerTHM97} of programming languages as it features
typical characteristics of ML languages such as a static type system
supporting parametric polymorphism with type inference (in fact Links
supports first-class polymorphism), and its evaluation semantics is
strict. However, \Links{} differentiates itself from the rest of the
ML-family by making crucial use of \emph{row polymorphism} to support
extensible records, variants, and tracking of computational
effects. Thus \Links{} has a rather strong emphasis on structural
types rather than nominal types.
\CoreLinks{} captures all of these properties of \Links{}. Our
calculus \BCalc{} differs in several aspects from \CoreLinks{}. For
example, the underlying formalism of \CoreLinks{} is call-by-value,
whilst the formalism of \BCalc{} is \emph{fine-grain
call-by-value}~\cite{LevyPT03}, which shares similarities with
A-normal form (ANF)~\cite{FlanaganSDF93} as it syntactically
distinguishes between value and computation terms by mandating every
intermediate computation being named. However unlike ANF, fine-grain
call-by-value remains closed under $\beta$-reduction. The reason for
choosing fine-grain call-by-value as our formalism is entirely due to
convenience. As we shall see in Chapter~\ref{ch:unary-handlers}
fine-grain call-by-value is a convenient formalism for working with
continuations. Another point of difference between \CoreLinks{} and
\BCalc{} is that the former models the integrated database query
sublanguage of \Links{}. We do not consider the query sublanguage at
all, and instead our focus is entirely on modelling the interaction
and programming with computational effects.
\section{Syntax and static semantics}
\label{sec:syntax-base-language}
As \BCalc{} is intrinsically typed, we begin by presenting the syntax
of kinds and types in
Section~\ref{sec:base-language-types}. Subsequently in
Section~\ref{sec:base-language-terms} we present the term syntax,
before presenting the formation rules for types in
Section~\ref{sec:base-language-type-rules}.
% Typically the presentation of a programming language begins with its
% syntax. If the language is typed there are two possible starting
% points: Either one presents the term syntax first, or alternatively,
% the type syntax first. Although the choice may seem rather benign
% there is, however, a philosophical distinction to be drawn between
% them. Terms are, on their own, entirely meaningless, whilst types
% provide, on their own, an initial approximation of the semantics of
% terms. This is particularly true in an intrinsic typed system perhaps
% less so in an extrinsic typed system. In an intrinsic system types
% must necessarily be precursory to terms, as terms ultimately depend on
% the types. Following this argument leaves us with no choice but to
% first present the type syntax of \BCalc{} and subsequently its term
% syntax.
\subsection{Types and their kinds}
\label{sec:base-language-types}
%
\begin{figure}
\begin{syntax}
% \slab{Value types} &A,B &::= & A \to C
% \mid \alpha
% \mid \forall \alpha^K.C
% \mid \Record{R}
% \mid [R]\\
% \slab{Computation types}
% &C,D &::= & A \eff E \\
% \slab{Effect types} &E &::= & \{R\}\\
% \slab{Row types} &R &::= & \ell : P;R \mid \rho \mid \cdot \\
% \slab{Presence types} &P &::= & \Pre{A} \mid \Abs \mid \theta\\
% %\slab{Labels} &\ell & & \\
% % \slab{Types} &T &::= & A \mid C \mid E \mid R \mid P \\
% \slab{Kinds} &K &::= & \Type \mid \Row_\mathcal{L} \mid \Presence
% \mid \Comp \mid \Effect \\
% \slab{Label sets} &\mathcal{L} &::=& \emptyset \mid \{\ell\} \uplus \mathcal{L}\\
% %\slab{Type variables} &\alpha, \rho, \theta& \\
% \slab{Type environments} &\Gamma &::=& \cdot \mid \Gamma, x:A \\
% \slab{Kind environments} &\Delta &::=& \cdot \mid \Delta, \alpha:K
\slab{Value types} &A,B \in \ValTypeCat &::= & A \to C
\mid \forall \alpha^K.C
\mid \Record{R} \mid [R]
\mid \alpha \\
\slab{Computation types\!\!}
&C,D \in \CompTypeCat &::= & A \eff E \\
\slab{Effect types} &E \in \EffectCat &::= & \{R\}\\
\slab{Row types} &R \in \RowCat &::= & \ell : P;R \mid \rho \mid \cdot \\
\slab{Presence types\!\!\!\!\!} &P \in \PresenceCat &::= & \Pre{A} \mid \Abs \mid \theta\\
\\
\slab{Types} &T \in \TypeCat &::= & A \mid C \mid E \mid R \mid P \\
\slab{Kinds} &K \in \KindCat &::= & \Type \mid \Comp \mid \Effect \mid \Row_\mathcal{L} \mid \Presence \\
\slab{Label sets} &\mathcal{L} \in \LabelCat &::=& \emptyset \mid \{\ell\} \uplus \mathcal{L}\\\\
\slab{Type environments} &\Gamma \in \TyEnvCat &::=& \cdot \mid \Gamma, x:A \\
\slab{Kind environments} &\Delta \in \KindEnvCat &::=& \cdot \mid \Delta, \alpha:K \\
\end{syntax}
\caption{Syntax of types, kinds, and their environments.}
\label{fig:base-language-types}
\end{figure}
%
The types are divided into several distinct syntactic categories which
are given in Figure~\ref{fig:base-language-types} along with the
syntax of kinds and environments.
%
\paragraph{Value types}
We distinguish between values and computations at the level of
types. Value types comprise the function type $A \to C$, which maps
values of type $A$ to computations of type $C$; the polymorphic type
$\forall \alpha^K . C$ is parameterised by a type variable $\alpha$ of
kind $K$; and the record type $\Record{R}$ represents records with
fields constrained by row $R$. Dually, the variant type $[R]$
represents tagged sums constrained by row $R$.
\paragraph{Computation types and effect types}
The computation type $A \eff E$ is given by a value type $A$ and an
effect type $E$, which specifies the effectful operations a
computation inhabiting this type may perform. An effect type
$E = \{R\}$ is constrained by row $R$.
\paragraph{Row types}
Row types play a pivotal role in our type system as effect, record,
and variant types are uniformly given by row types. A \emph{row type}
describes a collection of distinct labels, each annotated by a
presence type. A presence type indicates whether a label is
\emph{present} with type $A$ ($\Pre{A}$), \emph{absent} ($\Abs$) or
\emph{polymorphic} in its presence ($\theta$).
%
For example, the effect row $\{\Read:\Pre{\Int},\Write:\Abs,\cdot\}$
denotes a read-only context in which the operation label $\Read$ may
occur to access some integer value, whilst the operation label
$\Write$ cannot appear.
%
Row types are either \emph{closed} or \emph{open}. A closed row type
ends in~$\cdot$, whilst an open row type ends with a \emph{row
variable} $\rho$ (in an effect row we usually use $\varepsilon$
rather than $\rho$ and refer to it as an \emph{effect variable}).
%
The example effect row above is closed, an open variation of it ends
in an effect variable $\varepsilon$,
i.e. $\{\Read:\Pre{\Int},\Write:\Abs,\varepsilon\}$.
%
The row variable in an open row type can be instantiated with
additional labels subject to the restriction that each label may only
occur at most once (we enforce this restriction at the level of
kinds). We identify rows up to the reordering of labels as follows.
%
\begin{mathpar}
\inferrule*[Lab=\rowlab{Closed}]
{~}
{\cdot \equiv_{\mathrm{row}} \cdot}
\inferrule*[Lab=\rowlab{Open}]
{~}
{\rho \equiv_{\mathrm{row}} \rho'}
\inferrule*[Lab=\rowlab{Head}]
{R \equiv_{\mathrm{row}} R'}
{\ell:P;R \equiv_{\mathrm{row}} \ell:P;R'}
\inferrule*[Lab=\rowlab{Swap}]
{R \equiv_{\mathrm{row}} R'}
{\ell:P;\ell':P';R \equiv_{\mathrm{row}} \ell':P';\ell:P;R'}
\end{mathpar}
%
% The last rule $\rowlab{Swap}$ let us identify rows up to the
% reordering of labels. For instance, the two rows
% $\ell_1 : P_1; \cdots; \ell_n : P_n; \cdot$ and
% $\ell_n : P_n; \cdots ; \ell_1 : P_1; \cdot$ are equivalent.
%
The \rowlab{Closed} rule states that the closed marker $\cdot$ is
equivalent to itself, similarly the \rowlab{Open} rule states that any
two row variables are equivalent if and only if they have the same
syntactic name. The \rowlab{Head} rule compares the head of two given
rows and inductively compares their tails. The \rowlab{Swap} rule
permits reordering of labels. We assume structural equality on
labels. The \rowlab{Head} rule
%
The standard zero and unit types are definable using rows. We define
the zero type as the empty, closed variant $\ZeroType \defas
[\cdot]$. Dually, the unit type is defined as the empty, closed record
type, i.e. $\UnitType \defas \Record{\cdot}$.
% As absent labels in closed rows are redundant we will, for example,
% consider the following two rows equivalent
% $\Read:\Pre{\Int},\Write:\Abs,\cdot \equiv_{\mathrm{row}}
% \Read:\Pre{\Int},\cdot$.
For brevity, we shall often write $\ell : A$
to mean $\ell : \Pre{A}$ and omit $\cdot$ for closed rows.
%
\begin{figure}
\begin{mathpar}
% alpha : K
\inferrule*[Lab=\klab{TyVar}]
{ }
{\Delta, \alpha : K \vdash \alpha : K}
% Computation
\inferrule*[Lab=\klab{Comp}]
{ \Delta \vdash A : \Type \\
\Delta \vdash E : \Effect \\
}
{\Delta \vdash A \eff E : \Comp}
% A -E-> B, A : Type, E : Row, B : Type
\inferrule*[Lab=\klab{Fun}]
{ \Delta \vdash A : \Type \\
\Delta \vdash C : \Comp \\
}
{\Delta \vdash A \to C : \Type}
% forall alpha : K . A : Type
\inferrule*[Lab=\klab{Forall}]
{ \Delta, \alpha : K \vdash C : \Comp}
{\Delta \vdash \forall \alpha^K . \, C : \Type}
% Record
\inferrule*[Lab=\klab{Record}]
{ \Delta \vdash R : \Row_\emptyset}
{\Delta \vdash \Record{R} : \Type}
% Variant
\inferrule*[Lab=\klab{Variant}]
{ \Delta \vdash R : \Row_\emptyset}
{\Delta \vdash [R] : \Type}
% Effect
\inferrule*[Lab=\klab{Effect}]
{ \Delta \vdash R : \Row_\emptyset}
{\Delta \vdash \{R\} : \Effect}
% Present
\inferrule*[Lab=\klab{Present}]
{\Delta \vdash A : \Type}
{\Delta \vdash \Pre{A} : \Presence}
% Absent
\inferrule*[Lab=\klab{Absent}]
{ }
{\Delta \vdash \Abs : \Presence}
% Empty row
\inferrule*[Lab=\klab{EmptyRow}]
{ }
{\Delta \vdash \cdot : \Row_\mathcal{L}}
% Extend row
\inferrule*[Lab=\klab{ExtendRow}]
{ \Delta \vdash P : \Presence \\
\Delta \vdash R : \Row_{\mathcal{L} \uplus \{\ell\}}
}
{\Delta \vdash \ell : P;R : \Row_\mathcal{L}}
\end{mathpar}
\caption{Kinding rules}
\label{fig:base-language-kinding}
\end{figure}
%
\paragraph{Kinds}
The kinds classify the different categories of types. The $\Type$ kind
classifies value types, $\Presence$ classifies presence annotations,
$\Comp$ classifies computation types, $\Effect$ classifies effect
types, and lastly $\Row_{\mathcal{L}}$ classifies rows.
%
The formation rules for kinds are given in
Figure~\ref{fig:base-language-kinding}. The kinding judgement
$\Delta \vdash T : K$ states that type $T$ has kind $K$ in kind
environment $\Delta$.
%
The row kind is annotated by a set of labels $\mathcal{L}$. We use
this set to track the labels of a given row type to ensure uniqueness
amongst labels in each row type. For example, the kinding rule
$\klab{ExtendRow}$ uses this set to constrain which labels may be
mentioned in the tail of $R$. We shall elaborate on this in
Section~\ref{sec:row-polymorphism}.
\paragraph{Environments}
Kind and type environments are right-extended sequences of bindings. A
kind environment binds type variables to their kinds, whilst a type
environment binds term variables to their types.
\paragraph{Type variables} The type structure has three syntactically
distinct type variables (the kinding system gives us five semantically
distinct notions of type variables). As we sometimes wish to refer
collectively to type variables, we define the set of type variables,
$\TyVarCat$, to be generated by:
%
\[
\TyVarCat \defas
\ba[t]{@{~}l@{~}l}
&\{ A \in \ValTypeCat \mid A \text{ has the form } \alpha \}\\
\cup &\{ R \in \RowCat \mid R \text{ has the form } \rho \}\\
\cup &\{ P \in \PresenceCat \mid P \text{ has the form } \theta \}
\ea
\]
% Value types comprise the function type $A \to C$, whose domain
% is a value type and its codomain is a computation type $B \eff E$,
% where $E$ is an effect type detailing which effects the implementing
% function may perform. Value types further comprise type variables
% $\alpha$ and quantification $\forall \alpha^K.C$, where the quantified
% type variable $\alpha$ is annotated with its kind $K$. Finally, the
% value types also contains record types $\Record{R}$ and variant types
% $[R]$, which are built up using row types $R$. An effect type $E$ is
% also built up using a row type. A row type is a sequence of fields of
% labels $\ell$ annotated with their presence information $P$. The
% presence information denotes whether a label is present $\Pre{A}$ with
% some type $A$, absent $\Abs$, or polymorphic in its presence
% $\theta$. A row type may be either \emph{open} or \emph{closed}. An
% open row ends in a row variable $\rho$ which can be instantiated with
% additional fields, effectively growing the row, whilst a closed row
% ends in $\cdot$, meaning the row cannot grow further.
% The kinds comprise $\Type$ for regular type variables, $\Presence$ for
% presence variables, $\Comp$ for computation type variables, $\Effect$
% for effect variables, and lastly $\Row_{\mathcal{L}}$ for row
% variables. The row kind is annotated by a set of labels
% $\mathcal{L}$. We use this set to track the labels of a given row type
% to ensure uniqueness amongst labels in each row type. We shall
% elaborate on this in Section~\ref{sec:row-polymorphism}.
\paragraph{Free type variables} Sometimes we need to compute the free
type variables ($\FTV$) of a given type. To this end we define a
metafunction $\FTV$ by induction on the type structure, $T$, and
point-wise on type environments, $\Gamma$. Note that we always work up
to $\alpha$-conversion~\cite{Church32} of types.
%
\[
\ba[t]{@{~}l@{~~~~~~}c@{~}l}
\multicolumn{3}{c}{\begin{eqs}
\FTV &:& \TypeCat \to \TyVarCat
\end{eqs}}\\
\ba[t]{@{}l}
\begin{eqs}
% \FTV &:& \ValTypeCat \to \TyVarCat\\
\FTV(\alpha) &\defas& \{\alpha\}\\
\FTV(\forall \alpha^K.C) &\defas& \FTV(C) \setminus \{\alpha\}\\
\FTV(A \to C) &\defas& \FTV(A) \cup \FTV(C)\\
\FTV(A \eff E) &\defas& \FTV(A) \cup \FTV(E)\\
\FTV(\{R\}) &\defas& \FTV(R)\\
\FTV(\Record{R}) &\defas& \FTV(R)\\
\FTV([R]) &\defas& \FTV(R)\\
% \FTV(l:P;R) &\defas& \FTV(P) \cup \FTV(R)\\
% \FTV(\Pre{A}) &\defas& \FTV(A)\\
% \FTV(\Abs) &\defas& \emptyset\\
% \FTV(\theta) &\defas& \{\theta\}
\end{eqs}\ea & &
\begin{eqs}
% \FTV([R]) &\defas& \FTV(R)\\
% \FTV(\Record{R}) &\defas& \FTV(R)\\
% \FTV(\{R\}) &\defas& \FTV(R)\\
% \FTV &:& \RowCat \to \TyVarCat\\
\FTV(\cdot) &\defas& \emptyset\\
\FTV(\rho) &\defas& \{\rho\}\\
\FTV(l:P;R) &\defas& \FTV(P) \cup \FTV(R)\\
% \FTV &:& \PresenceCat \to \TyVarCat\\
\FTV(\theta) &\defas& \{\theta\}\\
\FTV(\Abs) &\defas& \emptyset\\
\FTV(\Pre{A}) &\defas& \FTV(A)\\
\end{eqs}\\\\
\multicolumn{3}{c}{\begin{eqs}
\FTV &:& \TyEnvCat \to \TyVarCat\\
\FTV(\cdot) &\defas& \emptyset\\
\FTV(\Gamma,x : A) &\defas& \FTV(\Gamma) \cup \FTV(A)
\end{eqs}}
% \begin{eqs}
% \FTV(\theta) &\defas& \{\theta\}\\
% \FTV(\Abs) &\defas& \emptyset\\
% \FTV(\Pre{A}) &\defas& \FTV(A)
% \end{eqs} & &
% \begin{eqs}
% \FTV(\cdot) &\defas& \emptyset\\
% \FTV(\Gamma,x : A) &\defas& \FTV(\Gamma) \cup \FTV(A)
% \end{eqs}
\ea
\]
%
\paragraph{Type substitution}
We define a type substitution map,
$\sigma : (\TyVarCat \times \TypeCat)^\ast$ as list of pairs mapping a
type variable to its replacement. We denote a single mapping as
$T/\alpha$ meaning substitute $T$ for the variable $\alpha$. We write
multiple mappings using list notation,
i.e. $[T_0/\alpha_0,\dots,T_n/\alpha_n]$. The domain of a substitution
map is set generated by projecting the first component, i.e.
%
\[
\bl
\dom : (\TyVarCat \times \TypeCat)^\ast \to \TyVarCat\\
\dom(\sigma) \defas \{ \alpha \mid (\_/\alpha) \in \sigma \}
\el
\]
%
The application of a type substitution map on a type term, written
$T\sigma$ for some type $T$, is defined inductively on the type
structure as follows.
%
\[
\ba[t]{@{~}l@{~}c@{~}r}
\multicolumn{3}{c}{
\begin{eqs}
(A \eff E)\sigma &\defas& A\sigma \eff E\sigma\\
(A \to C)\sigma &\defas& A\sigma \to C\sigma\\
(\forall \alpha^K.C)\sigma &\simdefas& \forall \alpha^K.C\sigma\\
\alpha\sigma &\defas& \begin{cases}
A & \text{if } (\alpha,A) \in \sigma\\
\alpha & \text{otherwise}
\end{cases}
\end{eqs}}\\
\begin{eqs}
\Record{R}\sigma &\defas& \Record{R[B/\beta]}\\
{[R]}\sigma &\defas& [R\sigma]\\
\{R\}\sigma &\defas& \{R\sigma\}\\
\cdot\sigma &\defas& \cdot\\
\rho\sigma &\defas& \begin{cases}
R & \text{if } (\rho, R) \in \sigma\\
\rho & \text{otherwise}
\end{cases}\\
\end{eqs}
& ~~~~~~~~~~ &
\begin{eqs}
(\ell : P;R)\sigma &\defas& (\ell : P\sigma; R\sigma)\\
\theta\sigma &\defas& \begin{cases}
P & \text{if } (\theta,P) \in \sigma\\
\theta & \text{otherwise}
\end{cases}\\
\Abs\sigma &\defas& \Abs\\
\Pre{A}\sigma &\defas& \Pre{A\sigma}
\end{eqs}
\ea
\]
%
\paragraph{Types and their inhabitants}
We now have the basic vocabulary to construct types in $\BCalc$. For
instance, the signature of the standard polymorphic identity function
is
%
\[
\forall \alpha^\Type. \alpha \to \alpha \eff \emptyset.
\]
%
Modulo the empty effect signature, this type is akin to the type one
would give for the identity function in System
F~\cite{Girard72,Reynolds74}, and thus we can use standard techniques
from parametricity~\cite{Wadler89} to reason about inhabitants of this
signature. However, in our system we can give an even more general
type to the identity function:
%
\[
\forall \alpha^\Type,\varepsilon^{\Row_\emptyset}. \alpha \to \alpha \eff \{\varepsilon\}.
\]
%
This type is polymorphic in its effect signature as signified by the
singleton open effect row $\{\varepsilon\}$, meaning it may be used in
an effectful context. By contrast, the former type may only be used in
a strictly pure context, i.e. the effect-free context.
%
\dhil{Maybe say something about reasoning effect types}
%
We can use the effect system to give precise types to effectful
computations. For example, we can give the signature of some
polymorphic computation that may only be run in a read-only context
%
\[
\forall \alpha^\Type, \varepsilon^{\Row_{\{\Read,\Write\}}}. \alpha \eff \{\Read:\Int,\Write:\Abs,\varepsilon\}.
\]
%
The effect row comprise a nullary $\Read$ operation returning some
integer and an absent operation $\Write$. The absence of $\Write$
means that the computation cannot run in a context that admits a
present $\Write$. It can, however, run in a context that admits a
presence polymorphic $\Write : \theta$ as the presence variable
$\theta$ may instantiated to $\Abs$. An inhabitant of this type may be
run in larger effect contexts, i.e. contexts that admit more
operations, because the row ends in an effect variable.
%
The type and effect system is also precise about how a higher-order
function may use its function arguments. For example consider the
signature of a map-operation over some datatype such as
$\dec{Option}~\alpha^\Type \defas [\dec{None};\dec{Some}:\alpha]$
%
\[
\forall \alpha^\Type,\beta^\Type,\varepsilon^{\Row_\emptyset}. \Record{\alpha \to \beta \eff \{\varepsilon\}, \dec{Option}~\alpha} \to \dec{Option}~\beta \eff \{\varepsilon\}.
\]
%
% The $\dec{map}$ function for
% lists is a canonical example of a higher-order function which is
% parametric in its own effects and the effects of its function
% argument. Supposing $\BCalc$ have some polymorphic list datatype
% $\List$, then we would be able to ascribe the following signature to
% $\dec{map}$
% %
% \[
% \forall \alpha^\Type,\beta^\Type,\varepsilon^{\Row_\emptyset}. \Record{\alpha \to \beta \eff \{\varepsilon\},\List~\alpha} \to \List~\beta \eff \{\varepsilon\}.
% \]
%
The first argument is the function that will be applied to the data
carried by second argument. Note that the two effect rows are
identical and share the same effect variable $\varepsilon$, it is thus
evident that an inhabitant of this type can only perform whatever
effects its first argument is allowed to perform.
Higher-order functions may also transform their function arguments,
e.g. modify their effect rows. The following is the signature of a
higher-order function which restricts its argument's effect context
%
\[
\forall \alpha^\Type, \varepsilon^{\Row_{\{\Read\}}},\varepsilon'^{\Row_\emptyset}. (\UnitType \to \alpha \eff \{\Read:\Int,\varepsilon\}) \to (\UnitType \to \alpha \eff \{\Read:\Abs,\varepsilon\}) \eff \{\varepsilon'\}.
\]
%
The function argument is allowed to perform a $\Read$ operation,
whilst the returned function cannot. Moreover, the two functions share
the same effect variable $\varepsilon$. Like the option-map signature
above, an inhabitant of this type performs no effects of its own as
the (right-most) effect row is a singleton row containing a distinct
effect variable $\varepsilon'$.
\paragraph{Syntactic sugar}
Detail the syntactic sugar\dots
\subsection{Terms}
\label{sec:base-language-terms}
%
\begin{figure}
\begin{syntax}
\slab{Variables} &x \in \VarCat&&\\
\slab{Values} &V,W \in \ValCat &::= & x
\mid \lambda x^A .\, M \mid \Lambda \alpha^K .\, M
\mid \Record{} \mid \Record{\ell = V;W} \mid (\ell~V)^R \\
& & &\\
\slab{Computations} &M,N \in \CompCat &::= & V\,W \mid V\,T\\
& &\mid& \Let\; \Record{\ell=x;y} = V \; \In \; N\\
& &\mid& \Case\; V \{\ell~x \mapsto M; y \mapsto N\} \mid \Absurd^C~V\\
& &\mid& \Return~V \mid \Let \; x \revto M \; \In \; N\\
\slab{Terms} &t \in \TermCat &::= & x \mid V \mid M
\end{syntax}
\caption{Term syntax of \BCalc{}.}
\label{fig:base-language-term-syntax}
\end{figure}
%
The syntax for terms is given in
Figure~\ref{fig:base-language-term-syntax}. We assume a countably
infinite set of names $\VarCat$ from which we draw fresh variable
names. We shall typically denote term variables by $x$, $y$, or $z$.
%
The syntax partitions terms into values and computations.
%
Value terms comprise variables ($x$), lambda abstraction
($\lambda x^A . \, M$), type abstraction ($\Lambda \alpha^K . \, M$),
and the introduction forms for records and variants. Records are
introduced using the empty record $(\Record{})$ and record extension
$(\Record{\ell = V; W})$, whilst variants are introduced using
injection $((\ell~V)^R)$, which injects a field with label $\ell$ and
value $V$ into a row whose type is $R$. We include the row type
annotation in to support bottom-up type reconstruction.
All elimination forms are computation terms. Abstraction and type
abstraction are eliminated using application ($V\,W$) and type
application ($V\,A$) respectively.
%
The record eliminator $(\Let \; \Record{\ell=x;y} = V \; \In \; N)$
splits a record $V$ into $x$, the value associated with $\ell$, and
$y$, the rest of the record. Non-empty variants are eliminated using
the case construct ($\Case\; V\; \{\ell~x \mapsto M; y \mapsto N\}$),
which evaluates the computation $M$ if the tag of $V$ matches
$\ell$. Otherwise it falls through to $y$ and evaluates $N$. The
elimination form for empty variants is ($\Absurd^C~V$).
%
There is one computation introduction form, namely, the trivial
computation $(\Return~V)$ which returns value $V$. Its elimination
form is the expression $(\Let \; x \revto M \; \In \; N)$ which evaluates
$M$ and binds the result value to $x$ in $N$.
%
%
As our calculus is intrinsically typed, we annotate terms with type or
kind information (term abstraction, type abstraction, injection,
operations, and empty cases). However, we shall omit these annotations
whenever they are clear from context.
\paragraph{Free variables} A given term is said to be \emph{closed} if
every applied occurrence of a variable is preceded by some
corresponding binding occurrence. Any applied occurrence of a variable
that is not preceded by a binding occurrence is said be \emph{free
variable}. We define the function $\FV : \TermCat \to \VarCat$
inductively on the term structure to compute the free variables of any
given term.
%
\[
\bl
\ba[t]{@{~}l@{~}c@{~}l}
\begin{eqs}
\FV(x) &\defas& \{x\}\\
\FV(\lambda x^A.M) &\defas& \FV(M) \setminus \{x\}\\
\FV(\Lambda \alpha^K.M) &\defas& \FV(M)\\[1.0ex]
\FV(V\,W) &\defas& \FV(V) \cup \FV(W)\\
\FV(\Return~V) &\defas& \FV(V)\\
\end{eqs}
& \qquad\qquad &
\begin{eqs}
\FV(\Record{}) &\defas& \emptyset\\
\FV(\Record{\ell = V; W}) &\defas& \FV(V) \cup \FV(W)\\
\FV((\ell~V)^R) &\defas& \FV(V)\\[1.0ex]
\FV(V\,T) &\defas& \FV(V)\\
\FV(\Absurd^C~V) &\defas& \FV(V)\\
\end{eqs}
\ea\\
\begin{eqs}
\FV(\Let\;x \revto M \;\In\;N) &\defas& \FV(M) \cup (\FV(N) \setminus \{x\})\\
\FV(\Let\;\Record{\ell=x;y} = V\;\In\;N) &\defas& \FV(V) \cup (\FV(N) \setminus \{x, y\})\\
\FV(\Case~V~\{\ell\,x \mapsto M; y \mapsto N\} &\defas& \FV(V) \cup (\FV(M) \setminus \{x\}) \cup (\FV(N) \setminus \{y\})
\end{eqs}
\el
\]
%
The function computes the set of free variables bottom-up. Most cases
are homomorphic on the syntax constructors. The interesting cases are
those constructs which feature term binders: lambda abstraction, let
bindings, pair deconstructing, and case splitting. In each of those
cases we subtract the relevant binder(s) from the set of free
variables.
\paragraph{Tail recursion}
In practice implementations of functional programming languages tend
to be tail-recursive in order to enable unbounded iteration. Otherwise
nested (repeated) function calls would quickly run out of stack space
on a conventional computer.
%
Intuitively, tail-recursion permits an already allocated stack frame
for some on-going function call to be reused by a nested function
call, provided that this nested call is the last thing to occur before
returning from the on-going function call.
%
A special case is when the nested function call is a fresh invocation
of the on-going function call, i.e. a self-reference. In this case the
nested function call is known as a \emph{tail recursive call},
otherwise it is simply known as a \emph{tail call}.
%
Thus the qualifier ``tail-recursive'' may be somewhat confusing as for
an implementation to be tail-recursive it must support recycling of
stack frames for tail calls; it is not sufficient to support tail
recursive calls.
%
Any decent implementation of Standard ML~\cite{MilnerTHM97},
OCaml~\cite{LeroyDFGRV20}, or Scheme~\cite{SperberDFSFM10} will be
tail-recursive. I deliberately say implementation rather than
specification, because it is often the case that the specification or
the user manual do not explicitly require a suitable implementation to
be tail-recursive; in fact of the three languages just mentioned only
Scheme explicitly mandates an implementation to be
tail-recursive~\cite{SperberDFSFM10}.
%
% The Scheme specification actually goes further and demands that an
% implementation is \emph{properly tail-recursive}, which provides
% strict guarantees on the asymptotic space consumption of tail
% calls~\cite{Clinger98}.
Tail calls will become important in Chapter~\ref{ch:cps} when we will
discuss continuation passing style as an implementation technique for
effect handlers, as tail calls happen to be ubiquitous in continuation
passing style.
%
Therefore let us formally characterise tail calls.
%
For our purposes, the most robust characterisation is a syntactic
characterisation, as opposed to a semantic characterisation, because
in the presence of control effects (which we will add in
Chapter~\ref{ch:unary-handlers}) it surprisingly tricky to describe
tail calls in terms of control flow such as ``the last thing to occur
before returning from the enclosing function'' as a function may
return multiple times. In particular, the effects of a function may be
replayed several times.
%
For this reason we will adapt a syntactic characterisation of tail
calls due to \citet{Clinger98}. First, we define what it means for a
computation to syntactically \emph{appear in tail position}.
%
\begin{definition}[Tail position]\label{def:tail-comp}
Tail position is a transitive notion for computation terms, which is
defined inductively as follows.
%
\begin{itemize}
\item The body $M$ of a $\lambda$-abstraction ($\lambda x. M$) appears in
tail position.
\item The body $M$ of a $\Lambda$-abstraction $(\Lambda \alpha.M)$
appears in tail position.
\item If $\Case\;V\;\{\ell\,x \mapsto M; y \mapsto N\}$ appears in tail
position, then both $M$ and $N$ appear in tail positions.
\item If $\Let\;\Record{\ell = x; y} = V \;\In\;N$ appears in tail
position, then $N$ is in tail position.
\item If $\Let\;x \revto M\;\In\;N$ appears in tail position, then
$N$ appear in tail position.
\item Nothing else appears in tail position.
\end{itemize}
\end{definition}
%
\begin{definition}[Tail call]\label{def:tail-call}
An application term $V\,W$ is said to be a tail call if it appears
in tail position.
\end{definition}
%
% The syntactic position of a tail call is often referred to as the
% \emph{tail-position}.
%
\subsection{Typing rules}
\label{sec:base-language-type-rules}
%
\begin{figure}
Values
\begin{mathpar}
% Variable
\inferrule*[Lab=\tylab{Var}]
{x : A \in \Gamma}
{\typv{\Delta;\Gamma}{x : A}}
% Abstraction
\inferrule*[Lab=\tylab{Lam}]
{\typ{\Delta;\Gamma, x : A}{M : C}}
{\typv{\Delta;\Gamma}{\lambda x^A .\, M : A \to C}}
% Polymorphic abstraction
\inferrule*[Lab=\tylab{PolyLam}]
{\typv{\Delta,\alpha : K;\Gamma}{M : C} \\
\alpha \notin \FTV(\Gamma)
}
{\typv{\Delta;\Gamma}{\Lambda \alpha^K .\, M : \forall \alpha^K . \,C}}
\\
% unit : ()
\inferrule*[Lab=\tylab{Unit}]
{ }
{\typv{\Delta;\Gamma}{\Record{} : \UnitType}}
% Extension
\inferrule*[Lab=\tylab{Extend}]
{ \typv{\Delta;\Gamma}{V : A} \\
\typv{\Delta;\Gamma}{W : \Record{\ell:\Abs;R}}
}
{\typv{\Delta;\Gamma}{\Record{\ell=V;W} : \Record{\ell:\Pre{A};R}}}
% Inject
\inferrule*[Lab=\tylab{Inject}]
{\typv{\Delta;\Gamma}{V : A}}
{\typv{\Delta;\Gamma}{(\ell~V)^R : [\ell : \Pre{A}; R]}}
\end{mathpar}
Computations
\begin{mathpar}
% Application
\inferrule*[Lab=\tylab{App}]
{\typv{\Delta;\Gamma}{V : A \to C} \\
\typv{\Delta;\Gamma}{W : A}
}
{\typ{\Delta;\Gamma}{V\,W : C}}
% Polymorphic application
\inferrule*[Lab=\tylab{PolyApp}]
{\typv{\Delta;\Gamma}{V : \forall \alpha^K . \, C} \\
\Delta \vdash A : K
}
{\typ{\Delta;\Gamma}{V\,A : C[A/\alpha]}}
% Split
\inferrule*[Lab=\tylab{Split}]
{\typv{\Delta;\Gamma}{V : \Record{\ell : \Pre{A};R}} \\\\
\typ{\Delta;\Gamma, x : A, y : \Record{\ell : \Abs; R}}{N : C}
}
{\typ{\Delta;\Gamma}{\Let \; \Record{\ell =x;y} = V\; \In \; N : C}}
% Case
\inferrule*[Lab=\tylab{Case}]
{ \typv{\Delta;\Gamma}{V : [\ell : \Pre{A};R]} \\\\
\typ{\Delta;\Gamma,x:A}{M : C} \\\\
\typ{\Delta;\Gamma,y:[\ell : \Abs;R]}{N : C}
}
{\typ{\Delta;\Gamma}{\Case \; V \{\ell\; x \mapsto M;y \mapsto N \} : C}}
% Absurd
\inferrule*[Lab=\tylab{Absurd}]
{\typv{\Delta;\Gamma}{V : []}}
{\typ{\Delta;\Gamma}{\Absurd^C \; V : C}}
% Return
\inferrule*[Lab=\tylab{Return}]
{\typv{\Delta;\Gamma}{V : A}}
{\typc{\Delta;\Gamma}{\Return \; V : A}{E}}
\\
% Let
\inferrule*[Lab=\tylab{Let}]
{\typc{\Delta;\Gamma}{M : A}{E} \\
\typ{\Delta;\Gamma, x : A}{N : C}
}
{\typ{\Delta;\Gamma}{\Let \; x \revto M\; \In \; N : C}}
\end{mathpar}
\caption{Typing rules}
\label{fig:base-language-type-rules}
\end{figure}
%
Thus the rule states that a type abstraction $(\Lambda \alpha. M)$ has
type $\forall \alpha.C$ if the computation $M$ has type $C$ assuming
$\alpha : K$ and $\alpha$ does not appear in the free type variables
of current type environment $\Gamma$. The \tylab{Unit} rule provides
the basis for all records as it simply states that the empty record
has type unit. The \tylab{Extend} rule handles record
extension. Supposing we wish to extend some record $\Record{W}$ with
$\ell = V$, that is $\Record{\ell = V; W}$. This extension has type
$\Record{\ell : \Pre{A};R}$ if and only if $V$ is well-typed and we
can ascribe $W : \Record{\ell : \Abs; R}$. Since
$\Record{\ell : \Abs; R}$ must be well-kinded with respect to
$\Delta$, the label $\ell$ cannot be mentioned in $W$, thus $\ell$
cannot occur more than once in the record. Similarly, the dual rule
\tylab{Inject} states that the injection $(\ell~V)^R$ has type
$[\ell : \Pre{A}; R]$ if the payload $V$ is well-typed. The implicit
well-kindedness condition on $R$ ensures that $\ell$ cannot be
injected twice. To illustrate how the kinding system prevents
duplicated labels consider the following ill-typed example
%
\[
(\dec{S}~\Unit)^{\dec{S}:\UnitType} : [\dec{S}:\UnitType;\dec{S}:\UnitType].
\]
%
Typing fails because the resulting row type is ill-kinded by the
\klab{ExtendRow}-rule:
\begin{mathpar}
\inferrule*[leftskip=6.5em,Right={\klab{Variant}}]
{\inferrule*[Right={\klab{ExtendRow}}]
{\vdash \Pre{\UnitType} : \Presence \\
\inferrule*[Right={\klab{ExtendRow}}]
{\vdash \Pre{\UnitType} : \Presence \\ \vdash \cdot : \Row_{\color{red}{\{\dec{S}\} \uplus \{\dec{S}\}}}}
{\vdash \dec{S}:\Pre{\UnitType};\cdot : \Row_{\emptyset \uplus \{\dec{S}\}}}}
{\vdash \dec{S}:\Pre{\UnitType};\dec{S}:\Pre{\UnitType};\cdot : \Row_{\emptyset}}
}
{\vdash [\dec{S}:\Pre{\UnitType};\dec{S}:\Pre{\UnitType};\cdot] : \Type}
\end{mathpar}
%
The two sets $\{\dec{S}\}$ and $\{\dec{S}\}$ are clearly not disjoint,
thus the second premise of the last application of \klab{ExtendRow}
cannot be satisfied.
\paragraph{Typing computations}
The \tylab{App} rule states that an application $V\,W$ has computation
type $C$ if the function-term $V$ has type $A \to C$ and the
argument term $W$ has type $A$, that is both the argument type and the
domain type of the abstractor agree.
%
The type application rule \tylab{PolyApp} tells us that a type
application $V\,A$ is well-typed whenever the abstractor term $V$ has
the polymorphic type $\forall \alpha^K.C$ and the type $A$ has kind
$K$. This rule makes use of type substitution.
%
The \tylab{Split} rule handles typing of record deconstructing. When
splitting a record term $V$ on some label $\ell$ binding it to $x$ and
the remainder to $y$. The label we wish to split on must be present
with some type $A$, hence we require that
$V : \Record{\ell : \Pre{A}; R}$. This restriction prohibits us for
splitting on an absent or presence polymorphic label. The
continuation of the splitting, $N$, must then have some computation
type $C$ subject to the following restriction: $N : C$ must be
well-typed under the additional assumptions $x : A$ and
$y : \Record{\ell : \Abs; R}$, statically ensuring that it is not
possible to split on $\ell$ again in the continuation $N$.
%
The \tylab{Case} rule is similar, but has two possible continuations:
the success continuation, $M$, and the fall-through continuation, $N$.
The label being matched must be present with some type $A$ in the type
of the scrutinee, thus we require $V : [\ell : \Pre{A};R]$. The
success continuation has some computation $C$ under the assumption
that the binder $x : A$, whilst the fall-through continuation also has
type $C$ it is subject to the restriction that the binder
$y : [\ell : \Abs;R]$ which statically enforces that no subsequent
case split in $N$ can match on $\ell$.
%
The \tylab{Absurd} states that we can ascribe any computation type to
the term $\Absurd~V$ if $V$ has the empty type $[]$.
%
The trivial computation term is typed by the \tylab{Return} rule,
which says that $\Return\;V$ has computation type $A \eff E$ if the
value $V$ has type $A$.
%
The \tylab{Let} rule types let bindings. The computation being bound,
$M$, must have computation type $A \eff E$, whilst the continuation,
$N$, must have computation $C$ subject to the additional assumption
that the binder $x : A$.
\section{Dynamic semantics}
\label{sec:base-language-dynamic-semantics}
%
\begin{figure}
\begin{reductions}
\semlab{App} & (\lambda x^A . \, M) V &\reducesto& M[V/x] \\
\semlab{TyApp} & (\Lambda \alpha^K . \, M) A &\reducesto& M[A/\alpha] \\
\semlab{Split} & \Let \; \Record{\ell = x;y} = \Record{\ell = V;W} \; \In \; N &\reducesto& N[V/x,W/y] \\
\semlab{Case_1} &
\Case \; (\ell~V)^R \{ \ell \; x \mapsto M; y \mapsto N\} &\reducesto& M[V/x] \\
\semlab{Case_2} &
\Case \; (\ell~V)^R \{ \ell' \; x \mapsto M; y \mapsto N\} &\reducesto& N[(\ell~V)^R/y], \hfill\quad \text{if } \ell \neq \ell' \\
\semlab{Let} &
\Let \; x \revto \Return \; V \; \In \; N &\reducesto& N[V/x] \\
\semlab{Lift} &
\EC[M] &\reducesto& \EC[N], \hfill\quad \text{if } M \reducesto N \\
\end{reductions}
\begin{syntax}
\slab{Evaluation contexts} & \mathcal{E} \in \EvalCat &::=& [~] \mid \Let \; x \revto \mathcal{E} \; \In \; N
\end{syntax}
%
\dhil{Describe evaluation contexts as functions: decompose and plug.}
%
%%\[
% Evaluation context lift
%% \inferrule*[Lab=\semlab{Lift}]
%% { M \reducesto N }
%% { \mathcal{E}[M] \reducesto \mathcal{E}[N]}
%% \]
\caption{Contextual small-step semantics}
\label{fig:base-language-small-step}
\end{figure}
%
In this section I will present the dynamic semantics of \BCalc{}. I
have chosen opt to use a \citet{Felleisen87}-style contextual
small-step semantics, since in conjunction with fine-grain
call-by-value (FGCBV), it yields a considerably simpler semantics than
the traditional structural operational semantics
(SOS)~\cite{Plotkin04a}, because only the rule for let bindings admits
a continuation wheres in ordinary call-by-value SOS each congruence
rule admits a continuation.
%
The simpler semantics comes at the expense of a more verbose syntax,
which is not a concern as one can readily convert between fine-grain
call-by-value and ordinary call-by-value.
The reduction semantics are based on a substitution model of
computation. Thus, before presenting the reduction rules, we define an
adequate substitution function. As usual we work up to
$\alpha$-conversion~\cite{Church32} of terms in $\BCalc{}$.
%
\paragraph{Term substitution}
We define a term substitution map,
$\sigma : (\VarCat \times \ValCat)^\ast$ as list of pairs mapping a
variable to its value replacement. We denote a single mapping as $V/x$
meaning substitute $V$ for the variable $x$. We write multiple
mappings using list notation, i.e. $[V_0/x_0,\dots,V_n/x_n]$. The
domain of a substitution map is set generated by projecting the first
component, i.e.
%
\[
\bl
\dom : (\VarCat \times \ValCat)^\ast \to \ValCat\\
\dom(\sigma) \defas \{ x \mid (\_/x) \in \sigma \}
\el
\]
%
The application of a type substitution map on a term $t \in \TermCat$,
written $t\sigma$, is defined inductively on the term structure as
follows.
%
\[
\ba[t]{@{~}l@{~}c@{~}r}
\begin{eqs}
x\sigma &\defas& \begin{cases}
V & \text{if } (x, V) \in \sigma\\
x & \text{otherwise}
\end{cases}\\
(\lambda x^A.M)\sigma &\simdefas& \lambda x^A.M\sigma\\
(\Lambda \alpha^K.M)\sigma &\defas& \Lambda \alpha^K.M\sigma\\
(V~T)\sigma &\defas& V\sigma~T
\end{eqs}
&~~&
\begin{eqs}
(V~W)\sigma &\defas& V\sigma~W\sigma\\
\Unit\sigma &\defas& \Unit\\
\Record{\ell = V; W}\sigma &\defas& \Record{\ell = V\sigma;W\sigma}\\
(\ell~V)^R\sigma &\defas& (\ell~V\sigma)^R\\
\end{eqs}\bigskip\\
\multicolumn{3}{c}{
\begin{eqs}
(\Let\;\Record{\ell = x; y} = V\;\In\;N)\sigma &\simdefas& \Let\;\Record{\ell = x; y} = V\sigma\;\In\;N\sigma\\
(\Case\;(\ell~V)^R\{
\ell~x \mapsto M
; y \mapsto N \})\sigma
&\simdefas&
\Case\;(\ell~V\sigma)^R\{
\ell~x \mapsto M\sigma
; y \mapsto N\sigma \}\\
(\Let\;x \revto M \;\In\;N)\sigma &\simdefas& \Let\;x \revto M[V/y] \;\In\;N\sigma
\end{eqs}}
\ea
\]
%
The attentive reader will notice that I am using the same notation for
type and term substitutions. In fact, we shall go further and unify
the two notions of substitution by combining them. As such we may
think of a combined substitution map as pair of a term substitution
map and a type substitution map, i.e.
$\sigma : (\VarCat \times \ValCat)^\ast \times (\TyVarCat \times
\TypeCat)^\ast$. The application of a combined substitution mostly the
same as the application of a term substitution map save for a couple
equations in which we need to apply the type substitution map
component to a type annotation and type abstraction which now might
require a change of name of the bound type variable
%
\[
\bl
(\lambda x^A.M)\sigma \defas \lambda x^{A\sigma.2}.M\sigma, \qquad
(V~T)\sigma \defas V\sigma~T\sigma.2, \qquad
(\ell~V)^R\sigma \defas (\ell~V\sigma)^{R\sigma.2}\medskip\\
\begin{eqs}
(\Lambda \alpha^K.M)\sigma &\simdefas& \Lambda \alpha^K.M\sigma\\
(\Case\;(\ell~V)^R\{
\ell~x \mapsto M
; y \mapsto N \})\sigma
&\simdefas&
\Case\;(\ell~V\sigma)^{R\sigma.2}\{
\ell~x \mapsto M\sigma
; y \mapsto N\sigma \}.
\end{eqs}
\el
\]
%
% We shall go further and use the
% notation to mean simultaneous substitution of types and terms, that is
% we
% %
% We justify this choice by the fact that we can lift type substitution
% pointwise on the term syntax constructors, enabling us to use one
% uniform notation for substitution.
% %
% Thus we shall generally allow a mix
% of pairs of variables and values and pairs of type variables and types
% to occur in the same substitution map.
\paragraph{Reduction semantics}
The reduction relation $\reducesto \subseteq \CompCat \times \CompCat$
relates a computation term to another if the former can reduce to the
latter in a single step. Figure~\ref{fig:base-language-small-step}
depicts the reduction rules. The application rules \semlab{App} and
\semlab{TyApp} eliminates a lambda and type abstraction, respectively,
by substituting the argument for the parameter in their body
computation $M$.
%
Record splitting is handled by the \semlab{Split} rule: splitting on
some label $\ell$ binds the payload $V$ to $x$ and the remainder $W$
to $y$ in the continuation $N$.
%
Disjunctive case splitting is handled by the two rules
\semlab{Case_1} and \semlab{Case_2}. The former rule handles the
success case, when the scrutinee's tag $\ell$ matches the tag of the
success clause, thus binds the payload $V$ to $x$ and proceeds to
evaluate the continuation $M$. The latter rule handles the
fall-through case, here the scrutinee gets bounds to $y$ and
evaluation proceeds with the continuation $N$.
%
The \semlab{Let} rule eliminates a trivial computation term
$\Return\;V$ by substituting $V$ for $x$ in the continuation $N$.
%
\paragraph{Evaluation contexts}
Recall from Section~\ref{sec:base-language-terms},
Figure~\ref{fig:base-language-term-syntax} that the syntax of let
bindings allows a general computation term $M$ to occur on the right
hand side of the binding, i.e. $\Let\;x \revto M \;\In\;N$. Thus we
are seemingly stuck in the general case, as the \semlab{Let} rule only
applies if the right hand side is a trivial computation.
%
However, it is at this stage we make use of the notion of
\emph{evaluation contexts} due to \citet{Felleisen87}. An evaluation
context is syntactic construction which decompose the dynamic
semantics into a set of base rules (i.e. the rules presented thus far)
and an inductive rule, which enables us to focus on a particular
computation term, $M$, in some larger context, $\EC$, and reduce it in
the said context to another computation $N$ if $M$ reduces outside out
the context to that particular $N$. In our formalism, we call this
rule \semlab{Lift}. Evaluation contexts are generated from the empty
context ($[~]$) and let expressions ($\Let\;x \revto \EC \;\In\;N$).
The choices of using fine-grain call-by-value and evaluation contexts
may seem odd, if not arbitrary at this point; the reader may wonder
with good reason why we elect to use fine-grain call-by-value over
ordinary call-by-value. In Chapter~\ref{ch:unary-handlers} we will
reap the benefits from our design choices, as we shall see that the
combination of fine-grain call-by-value and evaluation contexts
provide the basis for a convenient, simple semantic framework for
working with continuations.
\section{Metatheoretic properties of \BCalc{}}
\label{sec:base-language-metatheory}
Thus far we have defined the syntax, static semantics, and dynamic
semantics of \BCalc{}. In this section, we state and prove some
customary metatheoretic properties about \BCalc{}.
%
We begin by showing that substitutions preserve typability.
%
\begin{lemma}[Preservation of typing under substitution]\label{lem:base-language-subst}
Let $\sigma$ be any type substitution and $V \in \ValCat$ be any
value and $M \in \CompCat$ a computation such that
$\typ{\Delta;\Gamma}{V : A}$ and $\typ{\Delta;\Gamma}{M : C}$, then
$\typ{\Delta;\Gamma\sigma}{V\sigma : A\sigma}$ and
$\typ{\Delta;\Gamma\sigma}{M\sigma : C\sigma}$.
\end{lemma}
%
\begin{proof}
By induction on the typing derivations.
\end{proof}
%
\dhil{It is clear to me at this point, that I want to coalesce the
substitution functions. Possibly define them as maps rather than ordinary functions.}
The reduction semantics satisfy a \emph{unique decomposition}
property, which guarantees the existence and uniqueness of complete
decomposition for arbitrary computation terms into evaluation
contexts.
%
\begin{lemma}[Unique decomposition]\label{lem:base-language-uniq-decomp}
For any computation $M \in \CompCat$ it holds that $M$ is either
stuck or there exists a unique evaluation context $\EC \in \EvalCat$
and a redex $N \in \CompCat$ such that $M = \EC[N]$.
\end{lemma}
%
\begin{proof}
By structural induction on $M$.
\begin{description}
\item[Base step] $M = N$ where $N$ is either $\Return\;V$,
$\Absurd^C\;V$, $V\,W$, or $V\,T$. In each case take $\EC = [\,]$
such that $M = \EC[N]$.
\item[Inductive step]
%
There are several cases to consider. In each case we must find an
evaluation context $\EC$ and a computation term $M'$ such that
$M = \EC[M']$.
\begin{itemize}
\item[Case] $M = \Let\;\Record{\ell = x; y} = V\;\In\;N$: Take $\EC = [\,]$ such that $M = \EC[\Let\;\Record{\ell = x; y} = V\;\In\;N]$.
\item[Case] $M = \Case\;V\,\{\ell\,x \mapsto M'; y \mapsto N\}$:
Take $\EC = [\,]$ such that
$M = \EC[\Case\;V\,\{\ell\,x \mapsto M'; y \mapsto N\}]$.
\item[Case] $M = \Let\;x \revto M' \;\In\;N$: By the induction
hypothesis it follows that $M'$ is either stuck or it
decomposes (uniquely) into an evaluation context $\EC'$ and a
redex $N'$. If $M'$ is stuck, then so is $M$. Otherwise take
$\EC = \Let\;x \revto \EC'\;\In\;N$ such that $M = \EC[N']$.
\end{itemize}
\end{description}
\end{proof}
%
The calculus enjoys a rather strong \emph{progress} property, which
states that \emph{every} closed computation term reduces to a trivial
computation term $\Return\;V$ for some value $V$. In other words, any
realisable function in \BCalc{} is effect-free and total.
%
\begin{definition}[Computation normal form]\label{def:base-language-comp-normal}
A computation $M \in \CompCat$ is said to be \emph{normal} if it is
of the form $\Return\; V$ for some value $V \in \ValCat$.
\end{definition}
%
\begin{theorem}[Progress]\label{thm:base-language-progress}
Suppose $\typ{}{M : C}$, then $M$ is normal or there exists
$\typ{}{N : C}$ such that $M \reducesto^\ast N$.
\end{theorem}
%
\begin{proof}
By induction on typing derivations.
\end{proof}
%
% \begin{corollary}
% Every closed computation term in \BCalc{} is terminating.
% \end{corollary}
%
The calculus also satisfies the \emph{subject reduction} property,
which states that if some computation $M$ is well typed and reduces to
some other computation $M'$, then $M'$ is also well typed.
%
\begin{theorem}[Subject reduction]\label{thm:base-language-preservation}
Suppose $\typ{\Gamma}{M : C}$ and $M \reducesto M'$, then
$\typ{\Gamma}{M' : C}$.
\end{theorem}
%
\begin{proof}
By induction on typing derivations.
\end{proof}
\section{A primitive effect: recursion}
\label{sec:base-language-recursion}
%
As evident from Theorem~\ref{thm:base-language-progress} \BCalc{}
admit no computational effects. As a consequence every realisable
program is terminating. Thus the calculus provide a solid and minimal
basis for studying the expressiveness of any extension, and in
particular, which primitive effects any such extension may sneak into
the calculus.
However, we cannot write many (if any) interesting programs in
\BCalc{}. The calculus is simply not expressive enough. In order to
bring it closer to the ML-family of languages we endow the calculus
with a fixpoint operator which introduces recursion as a primitive
effect. We dub the resulting calculus \BCalcRec{}.
%
First we augment the syntactic category of values with a new
abstraction form for recursive functions.
%
\begin{syntax}
& V,W \in \ValCat &::=& \cdots \mid~ \tikzmarkin{rec1} \Rec \; f^{A \to C} \, x.M \tikzmarkend{rec1}
\end{syntax}
%
The $\Rec$ construct binds the function name $f$ and its argument $x$
in the body $M$. Typing of recursive functions is standard.
%
\begin{mathpar}
\inferrule*[Lab=\tylab{Rec}]
{\typ{\Delta;\Gamma,f : A \to C, x : A}{M : C}}
{\typ{\Delta;\Gamma}{(\Rec \; f^{A \to C} \, x . M) : A \to C}}
\end{mathpar}
%
The reduction semantics are also standard.
%
\begin{reductions}
\semlab{Rec} &
(\Rec \; f^{A \to C} \, x.M)\,V &\reducesto& M[(\Rec \; f^{A \to C}\,x.M)/f, V/x]
\end{reductions}
%
Every occurrence of $f$ in $M$ is replaced by the recursive abstractor
term, while every $x$ in $M$ is replaced by the value argument $V$.
The introduction of recursion means we obtain a slightly weaker
progress theorem as some programs may now diverge.
%
\begin{theorem}[Progress]
\label{thm:base-rec-language-progress}
Suppose $\typ{}{M : C}$, then $M$ is normal or there exists
$\typ{}{N : C}$ such that $M \reducesto^\ast N$.
\end{theorem}
%
\begin{proof}
Similar to the proof of Theorem~\ref{thm:base-language-progress}.
\end{proof}
\subsection{Tracking divergence via the effect system}
\label{sec:tracking-div}
%
With the $\Rec$-operator in \BCalcRec{} we can now implement the
factorial function.
%
\[
\bl
\dec{fac} : \Int \to \Int \eff \emptyset\\
\dec{fac} \defas \Rec\;f~n.
\ba[t]{@{}l}
\Let\;is\_zero \revto n = 0\;\In\\
\If\;is\_zero\;\Then\; \Return\;1\\
\Else\;\ba[t]{@{~}l}
\Let\; n' \revto n - 1 \;\In\\
\Let\; m \revto f~n' \;\In\\
n * m
\ea
\ea
\el
\]
%
The $\dec{fac}$ function computes $n!$ for any non-negative integer
$n$. If $n$ is negative then $\dec{fac}$ diverges as the function will
repeatedly select the $\Else$-branch in the conditional
expression. Thus this function is not total on its domain. Yet the
effect signature does not alert us about the potential divergence. In
fact, in this particular instance the effect row on the computation
type is empty, which might deceive the doctrinaire to think that this
function is `pure'. Whether this function is pure or impure depend on
the precise notion of purity -- which we have yet to choose. In
Section~\ref{sec:notions-of-purity} we shall make clear the notion of
purity that we have mind, however, first let us briefly illustrate how
we might utilise the effect system to track divergence.
The key to tracking divergence is to modify the \tylab{Rec} to inject
some primitive operation into the effect row.
%
\begin{mathpar}
\inferrule*[Lab=$\tylab{Rec}^\ast$]
{\typ{\Delta;\Gamma,f : A \to B\eff\{\dec{Div}:\Zero\}, x : A}{M : B\eff\{\dec{Div}:\Zero\}}}
{\typ{\Delta;\Gamma}{(\Rec \; f^{A \to B\eff\{\dec{Div}:\Zero\}} \, x .M) : A \to B\eff\{\dec{Div}:\Zero\}}}
\end{mathpar}
%
In this typing rule we have chosen to inject an operation named
$\dec{Div}$ into the effect row of the computation type on the
recursive binder $f$. The operation is primitive, because it can never
be directly invoked, rather, it occurs as a side-effect of applying a
$\Rec$-definition.
%
Using this typing rule we get that
$\dec{fac} : \Int \to \Int \eff \{\dec{Div}:\Zero\}$. Consequently,
every application-site of $\dec{fac}$ must now permit the $\dec{Div}$
operation in order to type check.
%
\begin{example}
We will use the following suspended computation to demonstrate
effect tracking in action.
%
\[
\bl
\lambda\Unit. \dec{fac}~3
\el
\]
%
The computation calculates $3!$ when forced.
%
We will now give a typing derivation for this computation to
illustrate how the application of $\dec{fac}$ causes its effect row
to be propagated outwards. Let
$\Gamma = \{\dec{fac} : \Int \to \Int \eff \{\dec{Div}:\Zero\}\}$.
%
\begin{mathpar}
\inferrule*[Right={\tylab{Lam}}]
{\inferrule*[Right={\tylab{App}}]
{\typ{\emptyset;\Gamma,\Unit:\Record{}}{\dec{fac} : \Int \to \Int \eff \{\dec{Div}:\Zero\}}\\
\typ{\emptyset;\Gamma,\Unit:\Record{}}{3 : \Int}
}
{\typc{\emptyset;\Gamma,\Unit:\Record{}}{\dec{fac}~3 : \Int}{\{\dec{Div}:\Zero\}}}
}
{\typ{\emptyset;\Gamma}{\lambda\Unit.\dec{fac}~3} : \Unit \to \Int \eff \{\dec{Div}:\Zero\}}
\end{mathpar}
%
The information that the computation applies a possibly divergent
function internally gets reflected externally in its effect
signature.
\end{example}
%
A possible inconvenience of the current formulation of
$\tylab{Rec}^\ast$ is that it recursion cannot be mixed with other
computational effects. The reason being that the effect row on
$A \to B\eff \{\dec{Div}:\Zero\}$ is closed. Thus in a practical
general-purpose programming language implementation it is likely be
more convenient to leave the tail of the effect row open as to allow
recursion to be used in larger effect contexts. The rule formulation
is also rather coarse as it renders every $\Rec$-definition as
possibly divergent -- even definitions that are obviously
non-divergent such as the $\Rec$-variation of the identity function:
$\Rec\;f\,x.x$. A practical implementation could utilise a static
termination checker~\cite{Walther94} to obtain more fine-grained
tracking of divergence.
% By fairly lightweight means we can obtain a finer analysis of
% $\Rec$-definitions by simply having an additional typing rule for
% the application of $\Rec$.
% %
% \begin{mathpar}
% \inferrule*[lab=$\tylab{AppRec}^\ast$]
% { E' = \{\dec{Div}:\Zero\} \uplus E\\
% \typ{\Delta}{E'}\\\\
% \typ{\Delta;\Gamma}{\Rec\;f^{A \to B \eff E}\,x.M : A \to B \eff E}\\
% \typ{\Delta;\Gamma}{W : A}
% }
% {\typ{\Delta;\Gamma}{(\Rec\;f^{A \to B \eff E}\,x.M)\,W : B \eff E'}}
% \end{mathpar}
% %
\subsection{Notions of purity}
\label{sec:notions-of-purity}
The term `pure' is heavily overloaded in the programming literature.
%
\dhil{In this thesis we use the Haskell notion of purity.}
\section{Row polymorphism}
\label{sec:row-polymorphism}
\dhil{A discussion of alternative row systems}
% \section{Type and effect inference}
% \dhil{While I would like to detail the type and effect inference, it
% may not be worth the effort. The reason I would like to do this goes
% back to 2016 when Richard Eisenberg asked me about how we do effect
% inference in Links.}
\chapter{Programming with control via effect handlers}
\label{ch:unary-handlers}
%
In this chapter we study various flavours of unary effect
handlers~\cite{PlotkinP13}, that is handlers of a single
computation. Concretely, we shall study four variations of effect
handlers: in Section~\ref{sec:unary-deep-handlers} we augment the base
calculus \BCalc{} with \emph{deep} effect handlers yielding the
calculus \HCalc{}; subsequently in
Sections~\ref{sec:unary-parameterised-handlers} and
\ref{sec:unary-default-handlers} we refine \HCalc{} with two practical
relevant kinds of handlers, namely, \emph{parameterised} and
\emph{default} handlers. The former is a specialisation of a
particular class of deep handlers, whilst the latter is important for
programming at large. Finally in
Section~\ref{sec:unary-shallow-handlers} we study \emph{shallow}
effect handlers which are an alternative to deep effect handlers.
% , First we endow \BCalc{}
% with a syntax for performing effectful operations, yielding the
% calculus \EffCalc{}. On its own the calculus is not very interesting,
% however, as the sole addition of the ability to perform effectful
% operations does not provide any practical note-worthy
% expressiveness. However, as we augment the calculus with different
% forms of effect handlers, we begin be able to implement interesting
% that are either difficult or impossible to realise in \BCalc{} in
% direct-style. Concretely, we shall study four variations of effect
% handlers, each as a separate extension to \EffCalc{}: deep, default,
% parameterised, and shallow handlers.
\section{Deep handlers}
\label{sec:unary-deep-handlers}
%
Programming with effect handlers is a dichotomy of \emph{performing}
and \emph{handling} of effectful operations -- or alternatively a
dichotomy of \emph{constructing} and \emph{deconstructing}. An operation
is a constructor of an effect without a predefined semantics. A
handler deconstructs effects by pattern-matching on their operations. By
matching on a particular operation, a handler instantiates the said
operation with a particular semantics of its own choosing. The key
ingredient to make this work in practice is \emph{delimited
control}~\cite{Landin65,Landin65a,Landin98,FelleisenF86,DanvyF90}. Performing
an operation reifies the remainder of the computation up to the
nearest enclosing handler of the said operation.
As our starting point we take the regular base calculus, \BCalc{},
without the recursion operator. We elect to do so to understand
exactly which primitive effects deep handlers bring into our resulting
calculus.
%
Deep handlers are defined as folds (catamorphisms) over computation
trees, meaning they provide a uniform semantics to the handled
operations of a given computation. In contrast, shallow handlers are
defined as case-splits over computation trees, and thus, allow a
nonuniform semantics to be given to operations. We will discuss this
last point in greater detail in
Section~\ref{sec:unary-shallow-handlers}.
\subsection{Performing effectful operations}
\label{sec:eff-language-perform}
An effectful operation is a purely syntactic construction, which has
no predefined dynamic semantics. The introduction form for effectful
operations is a computation term.
%
\begin{syntax}
\slab{Value types} &A,B \in \ValTypeCat &::=& \cdots \mid~ \tikzmarkin{optype} A \opto B \tikzmarkend{optype}\\
\slab{Computations} &M,N \in \CompCat &::=& \cdots \mid~ \tikzmarkin{do1} (\Do \; \ell~V)^E \tikzmarkend{do1}
\end{syntax}
%
\dhil{Describe the operation arrow.}
%
Informally, the intended behaviour of the new computation term
$(\Do\; \ell~V)^E$ is that it performs some operation $\ell$ with
value argument $V$. Thus the $\Do$-construct is similar to the typical
exception-signalling $\keyw{throw}$ or $\keyw{raise}$ constructs found
in programming languages with support for exceptions. The term is
annotated with an effect row $E$, providing a handle to obtain the
current effect context. We make use of this effect row during typing.
%
\begin{mathpar}
\inferrule*[Lab=\tylab{Do}]
{ E = \{\ell : A \opto B; R\} \\
\typ{\Delta}{E} \\
\typ{\Delta;\Gamma}{V : A}
}
{\typc{\Delta;\Gamma}{(\Do \; \ell \; V)^E : B}{E}}
\end{mathpar}
%
An operation invocation is only well-typed if the effect row $E$ is
well-kinded and contains the said operation with a present type; in
other words, the current effect context permits the operation. The
argument type $A$ must be the same as the domain of the operation.
%
We slightly abuse notation by using the function space arrow, $\to$,
to also denote the operation space. Although, the function and
operation spaces are separate entities, we may think of the operation
space as a subspace of the function space in which every effect row is
empty, that is every operation has a type on the form
$A \to B \eff \emptyset$. The reason that the effect row is always
empty is that any effects an operation might have are ultimately
conferred by a handler.
\dhil{Introduce notation for computation trees.}
\subsection{Handling of effectful operations}
%
We now present the elimination form for effectful operations, namely,
handlers.
%
First we define notation for handler kinds and types.
%
\begin{syntax}
\slab{Kinds} &K \in \KindCat &::=& \cdots \mid~ \tikzmarkin{handlerkinds1} \Handler \tikzmarkend{handlerkinds1}\\
\slab{Handler types} &F \in \HandlerTypeCat &::=& C \Harrow D\\
\slab{Types} &T \in \TypeCat &::=& \cdots \mid~ \tikzmarkin{typeswithhandler} F \tikzmarkend{typeswithhandler}
\end{syntax}
%
The syntactic category of kinds is augmented with the kind $\Handler$
which we will ascribe to handler types $F$. The arrow, $\Harrow$,
denotes the handler space. Type structure suggests that a handler is a
transformer of computations, as by the types it takes a computation of
type $C$ and returns another computation of type $D$. We use the
following kinding rule to check that a handler type is well-kinded.
%
\begin{mathpar}
\inferrule*[Lab=\klab{Handler}]
{ \Delta \vdash C : \Comp \\
\Delta \vdash D : \Comp
}
{\Delta \vdash C \Harrow D : \Handler}
\end{mathpar}
%
With the type structure in place, we present the term syntax for
handlers. The addition of handlers augments the syntactic category of
computations with a new computation form as well as introducing a new
syntactic category of handler definitions.
%
\begin{syntax}
\slab{Computations} &M,N \in \CompCat &::=& \cdots \mid~ \tikzmarkin{deephandlers1} \Handle \; M \; \With \; H\tikzmarkend{deephandlers1}\\[1ex]
\slab{Handlers} &H \in \HandlerCat &::=& \{ \Return \; x \mapsto M \}
\mid \{ \OpCase{\ell}{p}{r} \mapsto N \} \uplus H\\
\slab{Terms} &t \in \TermCat &::=& \cdots \mid~ \tikzmarkin{handlerdefs} H \tikzmarkend{handlerdefs}
\end{syntax}
%
The handle construct $(\Handle \; M \; \With \; H)$ is the counterpart
to $\Do$. It runs computation $M$ using handler $H$. A handler $H$
consists of a return clause $\{\Return \; x \mapsto M\}$ and a
possibly empty set of operation clauses
$\{\OpCase{\ell}{p_\ell}{r_\ell} \mapsto N_\ell\}_{\ell \in \mathcal{L}}$.
%
The return clause $\{\Return \; x \mapsto M\}$ defines how to
interpret the final return value of a handled computation. The
variable $x$ is bound to the final return value in the body $M$.
%
Each operation clause
$\{\OpCase{\ell}{p_\ell}{r_\ell} \mapsto N_\ell\}_{\ell \in \mathcal{L}}$
defines how to interpret an invocation of a particular operation
$\ell$. The variables $p_\ell$ and $r_\ell$ are bound in the body
$N_\ell$: $p_\ell$ binds the argument carried by the operation and
$r_\ell$ binds the continuation of the invocation site of $\ell$.
Given a handler $H$, we often wish to refer to the clause for a
particular operation or the return clause; for these purposes we
define two convenient projections on handlers in the metalanguage.
\[
\ba{@{~}r@{~}c@{~}l@{~}l}
\hell &\defas& \{\OpCase{\ell}{p}{r} \mapsto N \}, &\quad \text{where } \{\OpCase{\ell}{p}{r} \mapsto N \} \in H\\
\hret &\defas& \{\Return\; x \mapsto N \}, &\quad \text{where } \{\Return\; x \mapsto N \} \in H\\
\ea
\]
%
The $\hell$ projection yields the singleton set consisting of the
operation clause in $H$ that handles the operation $\ell$, whilst
$\hret$ yields the singleton set containing the return clause of $H$.
%
We define the \emph{domain} of an handler as the set of operation
labels it handles, i.e.
%
\begin{equations}
\dom &:& \HandlerCat \to \LabelCat\\
\dom(\{\Return\;x \mapsto M\}) &\defas& \emptyset\\
\dom(\{\OpCase{\ell}{p}{r} \mapsto M\} \uplus H) &\defas& \{\ell\} \cup \dom(H)
\end{equations}
\subsection{Static semantics}
The typing of effect handlers is slightly more involved than the
typing of the $\Do$-construct.
%
\begin{mathpar}
\inferrule*[Lab=\tylab{Handle}]
{
\typ{\Gamma}{M : C} \\
\typ{\Gamma}{H : C \Harrow D}
}
{\Gamma \vdash \Handle \; M \; \With\; H : D}
%\mprset{flushleft}
\inferrule*[Lab=\tylab{Handler}]
{{\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 D}{N_i : D}]_i
}
{\typ{\Delta;\Gamma}{H : C \Harrow D}}
\end{mathpar}
%
%
The \tylab{Handler} rule is where most of the work happens. The effect
rows on the input computation type $C$ and the output computation type
$D$ must mention every operation in the domain of the handler. In the
output row those operations may be either present ($\Pre{A}$), absent
($\Abs$), or polymorphic in their presence ($\theta$), whilst in the
input row they must be mentioned with a present type as those types
are used to type operation clauses.
%
In each operation clause the resumption $r_i$ must have the same
return type, $D$, as its handler. In the return clause the binder $x$
has the same type, $C$, as the result of the input computation.
\subsection{Dynamic semantics}
We augment the operational semantics with two new reduction rules: one
for handling return values and another for handling operations.
%{\small{
\begin{reductions}
\semlab{Ret} &
\Handle \; (\Return \; V) \; \With \; H &\reducesto& N[V/x], \hfill\text{where } \hret = \{ \Return \; x \mapsto N \} \\
\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\ba[t]{@{~}r@{~}l}
\text{where}& \hell = \{ \OpCase{\ell}{p}{r} \mapsto N \}\\
\text{and} & \ell \notin \BL(\EC)
\ea
}
\end{reductions}%}}%
%
The rule \semlab{Ret} invokes the return clause of the current handler
$H$ and substitutes $V$ for $x$ in the body $N$.
%
The rule \semlab{Op} handles an operation $\ell$, provided that the
handler definition $H$ contains a corresponding operation clause for
$\ell$ and that $\ell$ does not appear in the \emph{bound labels}
($\BL$) of the inner context $\EC$. The bound label condition enforces
that an operation is always handled by the nearest enclosing suitable
handler.
%
Formally, we define the notion of bound labels,
$\BL : \EvalCat \to \LabelCat$, inductively over the structure of
evaluation contexts.
%
\begin{equations}
\BL([~]) &=& \emptyset \\
\BL(\Let\;x \revto \EC\;\In\;N) &=& \BL(\EC) \\
\BL(\Handle\;\EC\;\With\;H) &=& \BL(\EC) \cup \dom(H) \\
\end{equations}
%
To illustrate the necessity of this condition consider the following
example with two nested handlers which both handle the same operation
$\ell$.
%
\[
\bl
\ba{@{~}r@{~}c@{~}l}
H_{\mathsf{inner}} &\defas& \{\OpCase{\ell}{p}{r} \mapsto r~42; \Return\;x \mapsto \Return~x\}\\
H_{\mathsf{outer}} &\defas& \{\OpCase{\ell}{p}{r} \mapsto r~0;\Return\;x \mapsto \Return~x \}
\ea\medskip\\
\Handle \;
\left(\Handle\; \Do\;\ell~\Record{}\;\With\; H_{\mathsf{inner}}\right)\;
\With\; H_{\mathsf{outer}}
\reducesto^+ \begin{cases}
\Return\;42 & \text{Innermost}\\
\Return\;0 & \text{Outermost}
\end{cases}
\el
\]
%
Without the bound label condition there are two possible results as
the choice of which handler to pick for $\ell$ is ambiguous, meaning
reduction would be nondeterministic. Conversely, with the bound label
condition we obtain that the above term reduces to $\Return\;42$,
because $\ell$ is bound in the computation term of the outermost
$\Handle$.
%
We have made a conscious design decision by always selecting nearest
enclosing suitable handler for any operation. In fact, we have made
the \emph{only} natural and sensible choice as picking any other
handler than the nearest enclosing renders programming with effect
handlers anti-modular. Consider always selecting the outermost
suitable handler, then the meaning of closed program composition
cannot be derived from its immediate constituents. For example,
consider using integer addition as the composition operator to compose
the inner handle expression from above with a copy of itself.
%
\[
\bl
\dec{fortytwo} \defas \Handle\;\Do\;\ell~\Unit\;\With\;H_{\mathsf{inner}} \medskip\\
\EC[\dec{fortytwo} + \dec{fortytwo}] \reducesto^+ \begin{cases}
\Return\; 84 & \text{when $\EC$ is empty}\\
?? & \text{otherwise}
\end{cases}
\el
\]
%
Clearly, if the ambient context $\EC$ is empty, then we can derive the
result by reasoning locally about each constituent separately and
subsequently add their results together to obtain the computation term
$\Return\;84$. However, if the ambient context is nonempty, then we
need to account for the possibility that some handler for $\ell$ could
occur in the context. For instance if
$\EC = \Handle\;[~]\;\With\;H_{\mathsf{outer}}$ then the result would
be $\Return\;0$, which we cannot derive locally from looking at the
constituents. Thus we argue that if we want programming to remain
modular and compositional, then we must necessarily always select the
nearest enclosing suitable handler to handle an operation invocation.
%
\dhil{Effect forwarding (the first condition)}
The metatheoretic properties of $\BCalc$ transfer to $\HCalc$ with
little extra effort, although we must amend the definition of
computation normal forms as there are now two ways in which a
computation term can terminate: successfully returning a value or
getting stuck on an unhandled operation.
%
\begin{definition}[Computation normal forms]
We say that a computation term $N$ is normal with respect to an
effect signature $E$, if $N$ is either of the form $\Return\;V$, or
$\EC[\Do\;\ell\,W]$ where $\ell \in E$ and $\ell \notin \BL(\EC)$.
\end{definition}
%
\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{theorem}[Preservation]
Suppose $\typ{\Gamma}{M : C}$ and $M \reducesto M'$, then
$\typ{\Gamma}{M' : C}$.
\end{theorem}
\section{\OSname{} in 50 lines of code or less}
\label{sec:deep-handlers-in-action}
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.
%
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}
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.
%
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}.
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}$.
%
The signature of the basic file system will consist of a single
operation $\Write$ for writing a list of characters to the file.
%
\[
\BIO \defas \{\Write : \Record{\UFD;\String} \opto \UnitType\}
\]
%
The operation is parameterised by an $\UFD$ and a character
sequence. We will leave the $\UFD$ type abstract until
Section~\ref{sec:tiny-unix-io}, however, we will assume the existence
of a term $\stdout : \UFD$ such that we perform invocations of
$\Write$.
%
Let us define a suitable handler for this operation.
%
\[
\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
\]
%
The handler takes as input a computation that produces some value
$\alpha$, and in doing so may perform the $\BIO$ effect.
%
The handler ultimately returns a pair consisting of the return value
$\alpha$ and the final state of the file.
%
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$.% The file is
% effectively built bottom up starting with the last character.
Let us define an auxiliary function that writes a string to the $\stdout$ file.
%
\[
\bl
\echo : \String \to \UnitType \eff\, \BIO\\%\{\Write : \Record{\UFD;\String} \opto \UnitType\}\\
\echo~cs \defas \Do\;\Write\,\Record{\stdout;cs}
\el
\]
%
The function $\echo$ is a simple wrapper around an invocation of
$\Write$.
%
We can now write some contents to the file and observe the effects.
%
\[
\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}
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.
%
We can model the exit system call by way of a single operation
$\Exit$.
%
\[
\Status \defas \{\Exit : \Int \opto \Zero\}
\]
%
The operation is parameterised by an integer value, however, an
invocation of $\Exit$ can never return, because the type $\Zero$ 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.
%
\[
\bl
\exit : \Int \to \alpha \eff \Status\\
\exit~n \defas \Absurd\;(\Do\;\Exit~n)
\el
\]
%
The $\Absurd$ computation term is used to coerce the return type
$\Zero$ of $\Fail$ into $\alpha$. This coercion is safe, because
$\Zero$ is an uninhabited type.
%
An interpretation of $\Fail$ amounts to implementing an exception
handler.
%
\[
\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
\]
%
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
\]
%
The (delimited) continuation of $\exit~1$ is effectively dead code.
\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}.
%
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 inquiring user.
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.
%
\[
\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$.
%
% \[
% \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.
%
\[
\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
\]
%
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{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.
%
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.
The session signature consists of two operations, $\Ask$, which we
used above, and $\Su$, for switching user.
%
\[
\EnvE \defas \{\Ask : \UnitType \opto \String;\Su : \User \opto \UnitType\}
\]
%
As usual, we define a small wrapper around invocations of $\Su$.
%
\[
\bl
\su : \User \to \UnitType \eff \{\Su : \User \opto \UnitType\}\\
\su~user \defas \Do\;\Su~user
\el
\]
%
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.
%
\[
\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.
%
To make this concrete, let us plug together the all components of our
system we have defined thus far.
%
\[
\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
\]
%
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.
%
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.}
\subsection{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.
%
Thus far in our system there is exactly one process.
%
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 process duplication primitive in \UNIX{} is called
\emph{fork}~\cite{RitchieT74}.
%
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.
%
\[
\bl
\Let\;i\revto fork~\Unit\;\In\\
\If\;i = 0\;\Then\;
~\textit{child's code}\\
\Else\;~\textit{parent's code}
\el
\]
%
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.
%
\[
\bl
\fork : \UnitType \to \Bool \eff \{\Fork : \UnitType \opto \Bool\}\\
\fork~\Unit \defas \Do\;\Fork~\Unit
\el
\]
%
In \UNIX{} the parent process \emph{continues} execution after the
fork point, and the child process \emph{begins} its execution after
the fork point.
%
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 following handler implements this behaviour.
%
\[
\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 $\Return$-case returns a singleton list containing a result of
running $m$.
%
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$).
%
Thus the handler returns a list of all the possible results of $m$.
%
In fact, this handler is exactly the handler for nondeterministic
choice, and it satisfies the standard semi-lattice
equations~\cite{PlotkinP09,PlotkinP13}.
% \dhil{This is an instance of non-blind backtracking~\cite{FriedmanHK84}}
Let us consider $\nondet$ together with the previously defined
handlers. But first, let us define two computations.
%
\[
\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
\]
%
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.
%
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.
%
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}
%
We need an operation for interruptions and corresponding handler to
handle interrupts in order for the system to support interleaving of
processes.
%
\[
\bl
\interrupt : \UnitType \to \UnitType \eff \{\Interrupt : \UnitType \opto \UnitType\}\\
\interrupt~\Unit \defas \Do\;\Interrupt~\Unit
\el
\]
%
The intended behaviour of an invocation of $\Interrupt$ is to suspend
the invoking computation in order to yield time for another
computation to run.
%
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.
%
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.
%
\[
\Pstate~\alpha~\varepsilon \defas \forall \theta.
\ba[t]{@{}l@{}l}
[&\Done:\alpha;\\
&\Suspended:\UnitType \to \Pstate~\alpha~\varepsilon \eff \{\Interrupt:\theta;\varepsilon\} ]
\ea
\]
%
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$. Note that, the presence variable $\theta$
in the effect row is universally quantified in the type alias
definition. The reason for this particular definition is that the type
of a value carried by $\Suspended$ is precisely the type of a
resumption originating from a handler that handles only the operation
$\Interrupt$ such as the following handler.
%
\[
\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
\]
%
This handler tags and returns values with $\Done$. It also tags and
returns the resumption provided by the $\Interrupt$-case with
$\Suspended$. If we compose this handler with the nondeterminism
handler, then we obtain a term with the following type.
%
\[
\nondet\,(\lambda\Unit.\reifyP~m) : \List~(\Pstate~\alpha~\{\Fork: \UnitType \opto \Bool;\varepsilon\})
\]
%
for some $m : \UnitType \to \{\Proc;\varepsilon\}$ where
$\Proc \defas \{\Fork: \UnitType \opto \Bool;\Interrupt: \UnitType
\opto \UnitType\}$.
%
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\}) \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 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.
%
Using the above machinery, we can define a function which adds
time-sharing capabilities to the system.
%
\[
\bl
\timeshare : (\UnitType \to \alpha \eff \Proc) \to \List~\alpha\\
\timeshare~m \defas \schedule\,[\Suspended\,(\lambda\Unit.\reifyP~m)]
\el
\]
%
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.
%
The question remains how to inject invocations of $\Interrupt$ such
that computation gets interleaved.
\paragraph{Interruption via interception}
%
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{AhmanP20}'s core calculus
$\lambda_{\text{\ae}}$~\cite{AhmanP20}, 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.
%
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$.
%
\[
\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
\]
%
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.
%
\[
\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
\]
%
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}
&\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
\]
%
Evidently, each write operation has been interleaved, resulting in a
mishmash poetry of Shakespeare and \UNIX{}.
%
To some this may be a shambolic treatment of classical arts, whilst to
others this may be a modern contemporaneous treatment. As the saying
goes: 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.
%
In this section we will implement a \UNIX{}-like file system that
supports file creation, opening, truncation, read and write
operations, and file linking.
%
To implement a file system we will need to use state. State can
readily be implemented with an effect handler~\cite{KammarLO13}.
%
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 platitudinous.
%
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.
%
\[
\State~\beta \defas \{\Get:\UnitType \opto \beta;\Put:\beta \opto \UnitType\}
\]
%
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.
%
\[
\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 following handler interprets the operations.
%
\[
\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
\]
%
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}.
%
Each case returns a state-accepting function.
%
The $\Return$-case returns a function that produces a pair consisting
of return value of $m$ and the final state $st$.
%
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.
%
\dhil{Discuss briefly local vs global state~\cite{PauwelsSM19}}
\subsubsection{Basic sequential file system}
%
\begin{figure}
\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 |}{$\cdots$}\\
\hline
\strlit{stdout}\tikzmark{stdout}\\
\hline
\multicolumn{1}{| c |}{$\cdots$}\\
\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 |}{$\cdots$}\\
\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 |}{$\cdots$}\\
\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}
\begin{figure}
\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}
% The existing literature already contain an extensive amount of
% introductory examples of programming with (deep) effect
% handlers~\cite{KammarLO13,Pretnar15,Leijen17}.
% \subsubsection{Exception handling}
% \label{sec:exn-handling-in-action}
% Effect handlers subsume a variety of control abstractions, and
% arguably the simplest such abstraction is exception handling.
% \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$.
% %
% 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.
% 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$.
% 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{Parameterised handlers}
\label{sec:unary-parameterised-handlers}
\subsection{Syntax and static semantics}
\subsection{Dynamic semantics}
\subsection{Lightweight concurrency}
\begin{example}[Green threads]
\dhil{Example: Lightweight threads}
\end{example}
% \section{Default handlers}
% \label{sec:unary-default-handlers}
\section{Shallow handlers}
\label{sec:unary-shallow-handlers}
\subsection{Syntax and static semantics}
\subsection{Dynamic semantics}
\subsection{\UNIX{}-style pipes}
\begin{example}[Inversion of control]
foo
\end{example}
\begin{example}[Communicating pipes]
bar
\end{example}
% \chapter{N-ary handlers}
% \label{ch:multi-handlers}
% \section{Syntax and Static Semantics}
% \section{Dynamic Semantics}
% \section{Unifying deep and shallow handlers}
\part{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
rudimentary factorial function from Section~\ref{sec:tracking-div}.
%
\[
\bl
\dec{fac} : \Int \to \Int\\
\dec{fac} \defas \lambda n.
\ba[t]{@{}l}
\Let\;is\_zero \revto n = 0\;\In\\
\If\;is\_zero\;\Then\; \Return\;1\\
\Else\;\ba[t]{@{~}l}
\Let\; n' \revto n - 1 \;\In\\
\Let\; m \revto f~n' \;\In\\
n * m
\ea
\ea
\el
\]
%
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
\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 is\_zero.
\ba[t]{@{~}l}
\If\;is\_zero\;\Then\; k~1\\
\Else\;
(-_{\dec{cps}})~n~1~
(\lambda n'.
\dec{fac}_{\dec{cps}}~n'~
(\lambda m. (*_{\dec{cps}})~n~m~k)))
\ea
\el
\]
%
There are several worthwhile observations to make about the
differences between the two implementations $\dec{fac}$ and
$\dec{fac}_{\dec{cps}}$.
%
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. The continuation captures the remainder of
computation that ultimately produces a result of type $\alpha$, or put
differently: it determines what to do with the result returned by an
invocation of $\dec{fac}_{\dec{cps}}$. Semantically the continuation corresponds
to the surrounding evaluation
context.
%
Secondly note that every $\Let$-binding in $\dec{fac}$ has become a
function application in $\dec{fac}_{\dec{cps}}$. The functions
$=_{\dec{cps}}$, $-_{\dec{cps}}$, and $*_{\dec{cps}}$ denote the CPS
versions of equality testing, subtraction, and multiplication
respectively. Moreover, the explicit $\Return~1$ in the true branch
has been turned into an application of continuation $k$, and the
implicit return $n*m$ in the $\Else$-branch has been turned into an
explicit application of the continuation.
%
Thirdly note every function application occurs in tail position. This
is a characteristic property of CPS that makes CPS feasible as a
practical implementation strategy since programs in CPS notation do
not consume stack space.
%
\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}
\[
\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
\]
\section{Initial target calculus}
\label{sec:target-cps}
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 $\BCalcRec$ 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.
We give a standard small-step evaluation 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.
\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}
\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}
\[
\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}
\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{CPS transform for 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
\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}
\[
\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{CPS 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$).
%
As handlers can be nested, each computation executes in the context of
a \emph{stack} of pairs of pure and effect continuations.
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 when the resumption is invoked.
%
\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.
%
\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 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 translation of a return clause is a term which garbage collects
the current effect continuation $h$.
%
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.
%
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.
%
\begin{equations}
\pcps{-} &:& \CompCat \to \UCompCat\\
\pcps{M} &\defas& \cps{M}~(\lambda x.\lambda h.x)~(\lambda \Record{z,\_}.\Absurd~z) \\
\end{equations}
%
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{DanvyF92,Steele78}, and as a result the
image is not stackless, meaning it 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.
%
\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*}
%
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.
%
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.
%
In Section~\ref{sec:first-order-uncurried-cps} we will refine this
translation to be properly tail-recursive.
%
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.
%
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.
%
\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}
%
%
\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}
%
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.
%
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. Thus this
restriction suffices to ensure that every function application will be
in tail position.
%
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, hence we remove them altogether. 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$.
%
These changes to $\UCalc$ immediately invalidate the curried
translation from the previous section as the image of the translation
is no longer well-formed.
%
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.
%
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 of continuations.
%
\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}
%
The other cases are as in the original CPS translation in
Figure~\ref{fig:cps-cbv}.
%
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.
%
% 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.
%
\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}
%
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.
%
\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.
%
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).
%
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}
%
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.
%
We can avoid generating these administrative redexes by applying a
variation of the technique that we used in the previous section to
uncurry the curried CPS translation.
%
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{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}
%
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.
%
The translations of $\Do$, handling, and forwarding need to be
modified to account for the change in representation of
resumptions.
%
\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\\
%
\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.
%
\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}
\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.
\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.
%
\begin{syntax}
\slab{Static patterns} &\sP \in \SPatCat &::=& \sks \mid \sk \scons \sP\\
\slab{Static values} & \sV, \sW \in \SValCat &::=& \reflect V \mid \sV \scons \sW \mid \slam \sP. \sM\\
\slab{Static computations} & \sM \in \SCompCat &::=& \sV \mid \sV \sapp \sW \mid \sV \dapp V \dapp W
\end{syntax}
%
The patterns comprise only static list deconstructing. We let $\sP$
range over static patterns.
%
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.
%
\[
\ba{@{}l@{\qquad}c}
\reify \reflect V \defas V
&\reify (\sV \scons \sW) \defas \reify \sV \dcons \reify \sW
\ea
\]
%
\paragraph{Higher-order translation}
%
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:
%
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 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.
%
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$.
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.
%
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 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).
%
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$.
%
To provide a flavour of how this continuation manipulation functions
in practice, consider the following example term.
%
\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.
%
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$.
%
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.
%
\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.
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}
%
An alternative to the explicit deconstruction of continuations is to
implicitly deconstruct continuations on demand when static pattern
matching fails. This was the approach taken by
\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.
%
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.
%
Furthermore, 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.
%
\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}
%
\begin{proof}
By mutual induction on the structure of $W$, $M$, $\hret$, and
$\hops$.
\end{proof}
%
It follows as a corollary that top-level substitution is well-behaved.
%
\begin{corollary}[Top-level substitution]
\[
\pcps{M}[\cps{V}/x] = \pcps{M[V/x]}.
\]
\end{corollary}
%
\begin{proof}
Follows immediately by the definitions of $\pcps{-}$ and
Lemma~\ref{lem:ho-cps-subst}.
\end{proof}
%
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.
%
\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}
%
\begin{proof}
By structural induction on the evaluation context $\EC$.
\end{proof}
%
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$.
%
\begin{reductions}
\usemlab{SplitList} & \Let\; (k \dcons ks) = V \dcons W \;\In\; M &\reducesto& M[V/k, W/ks] \\
\end{reductions}
%
Note this rule is isomorphic to the \usemlab{Split} rule with lists
encoded as right nested pairs using unit to denote nil.
%
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.
%
\begin{lemma}[Reflect after reify]
\label{lem:reflect-after-reify}
%
Reflection after reification may give rise to dynamic administrative
reductions, i.e.
%
\[
\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}
%
\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
%
\[
\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
\]
%
\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
\]
%
\end{lemma}
%
\begin{proof}
Follows from Lemmas~\ref{lem:decomposition},
\ref{lem:reflect-after-reify}, and \ref{lem:forwarding}.
\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}$.
\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}.
\end{proof}
%
% 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}
%
\section{CPS 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.
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.
\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.
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.
%
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.
%
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.
%
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.
%
\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}
%
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.
%
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.
%
\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}
%
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}
%
The main difference between this translation rule and the translation
rule for deep handler operation clauses is the realisation of
resumptions.
%
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.
%
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, inserting such dummy handlers lead to memory
leaks.
%
\dhil{Give the counting example}
%
The use of dummy handlers is symptomatic for the need of a more
general notion of resumptions. Upon 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$ that behaves along the
following lines.
%
\[
\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.
%
\[
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
\]
%
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.
%
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.
%
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.
%
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.
%
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.
%
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.
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.
\dhil{Remark that a `generalised continuation' is a defunctionalised continuation.}
\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
\]
%
\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.
%
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.
\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, \sRecord{\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{\svhret, \svhops}} \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.
%
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 is
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.
%
\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],
\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}
%
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, \sRecord{\svhret, \svhops}} \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\\
\sRecord{\svhret,\svhops}} \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))
\]
\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).
\]
\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:
%
\[
\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.
%
\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
\]
%
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
\]
%
\end{lemma}
\medskip
Now 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}
\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}
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^* 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{Related work}
\label{sec:cps-related-work}
\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$.
%
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}$.
\dhil{Check whether the first pass marks administrative redexes}
% CPS The colon translation captures the
% intuition tThe colon translation is itself a CPS translation which
% yields
% 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.
% 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.
\paragraph{Iterated CPS transform}
\paragraph{Partial evaluation}
\chapter{Abstract machine semantics}
\dhil{The text is this chapter needs to be reworked}
In this chapter we develop an abstract machine that supports deep and
shallow handlers \emph{simultaneously}, using the generalised
continuation structure we identified in the previous section for the
CPS translation. We also build upon prior work~\citep{HillerstromL16}
that developed an abstract machine for deep handlers by generalising
the continuation structure of a CEK machine (Control, Environment,
Kontinuation)~\citep{FelleisenF86}.
%
% \citet{HillerstromL16} sketched an adaptation for shallow handlers. It
% turns out that this adaptation had a subtle flaw, similar to the flaw
% in the sketched implementation of a CPS translation for shallow
% handlers given by \citet{HillerstromLAS17}. We fix the flaw here with
% a full development of shallow handlers along with a statement of the
% correctness property.
\section{Syntax and semantics}
The abstract machine syntax is given in
Figure~\ref{fig:abstract-machine-syntax}.
% A machine continuation is a list of handler frames. A handler frame is
% a pair of a \emph{handler closure} (handler definition) and a
% \emph{pure continuation} (a sequence of let bindings), analogous to
% the structured frames used in the CPS translation in
% \Sec\ref{sec:higher-order-uncurried-cps}.
% %
% Handling an operation amounts to searching through the continuation
% for a matching handler.
% %
% The resumption is constructed during the search by reifying each
% handler frame. As in the CPS translation, the resumption is assembled
% in one of two ways depending on whether the matching handler is deep
% or shallow.
% %
% For a deep handler, the current handler closure is included, and a deep
% resumption is a reified continuation.
% %
% An invocation of a deep resumption amounts to concatenating it with
% the current machine continuation.
% %
% For a shallow handler, the current handler closure must be discarded
% leaving behind a dangling pure continuation, and a shallow resumption
% is a pair of this pure continuation and the remaining reified
% continuation.
% %
% (By contrast, the prior flawed adaptation prematurely precomposed the
% pure continuation with the outer handler in the current resumption.)
% %
% An invocation of a shallow resumption again amounts to concatenating
% it with the current machine continuation, but taking care to
% concatenate the dangling pure continuation with that of the next
% frame.
%
\begin{figure}[t]
\flushleft
\begin{syntax}
\slab{Configurations} & \conf &::= & \cek{M \mid \env \mid \shk \circ \shk'} \\
\slab{Value environments} &\env &::= & \emptyset \mid \env[x \mapsto v] \\
\slab{Values} &v, w &::= & (\env, \lambda x^A . 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\\
% \end{syntax}
% \begin{displaymath}
% \ba{@{}l@{~~}r@{~}c@{~}l@{\quad}l@{~~}r@{~}c@{~}l@{}}
% \slab{Continuations} &\shk &::= & \nil \mid \shf \cons \shk & \slab{Continuation frames} &\shf &::= & (\slk, \chi) \\
% & & & & \slab{Handler closures} &\chi &::= & (\env, H)^\depth \smallskip \\
% \slab{Pure continuations} &\slk &::= & \nil \mid \slf \cons \slk & \slab{Pure continuation frames} &\slf &::= & (\env, x, N) \\
% \ea
% \end{displaymath}
% \begin{syntax}
\slab{Continuations} &\shk &::= & \nil \mid \shf \cons \shk \\
\slab{Continuation frames} &\shf &::= & (\slk, \chi) \\
\slab{Pure continuations} &\slk &::= & \nil \mid \slf \cons \slk \\
\slab{Pure continuation frames} &\slf &::= & (\env, x, N) \\
\slab{Handler closures} &\chi &::= & (\env, H) \mid (\env, H)^\dagger \medskip \\
\end{syntax}
\caption{Abstract machine syntax.}
\label{fig:abstract-machine-syntax}
\end{figure}
%% A CEK machine~\citep{FelleisenF86} operates on configurations, which
%% are (Control, Environment, Continuation) triples.
%% %
% Our machine, like Hillerström and Lindley's, generalises the usual
% notion of continuation to accommodate handlers.
%
%
\begin{figure}[p]
\dhil{Fix figure formatting}
\begin{minipage}{0.90\textheight}%
%% Identity continuation
%% \[
%% \shk_0 = [(\nil, (\emptyset, \{\Return\;x \mapsto x\}))]
%% \]
\textbf{Transition function}
\begin{displaymath}
\bl
\ba{@{}l@{~}r@{~}c@{~}l@{\quad}l@{}}
\mlab{Init} & \multicolumn{4}{@{}c@{}}{M \stepsto \cek{M \mid \emptyset \mid [(\nil, (\emptyset, \{\Return\;x \mapsto \Return\;x\}))]}} \\[1ex]
% App
\mlab{AppClosure} & \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) \\
\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) \\
% App - continuation
\mlab{AppCont} & \cek{ V\;W \mid \env \mid \shk}
&\stepsto& \cek{ \Return \; W \mid \env \mid \shk' \concat \shk},
&\text{if }\val{V}{\env} = (\shk')^A \\
\mlab{AppCont^\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 \\
% 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) \\[1ex]
\ea \\
\ba{@{}l@{}r@{~}c@{~}l@{\quad}l@{}}
\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} \\
% 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 \\[2ex]
% 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} & \cek{ \Handle^\depth \, M \; \With \; H \mid \env \mid \shk}
&\stepsto& \cek{ M \mid \env \mid (\nil, (\env, H)^\depth) \cons \shk} \\[1ex]
% Return - let binding
\mlab{RetCont} &\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{RetHandler} & \cek{ \Return \; V \mid \env \mid (\nil, (\env',H)) \cons \shk}
&\stepsto& \cek{ M \mid \env'[x \mapsto \val{V}{\env}] \mid \shk},
& \text{if } \hret = \{\Return\; x \mapsto M\} \\
\mlab{RetTop} & \cek{\Return\;V \mid \env \mid \nil} &\stepsto& \val{V}{\env} \\[1ex]
% Deep
\mlab{Do} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid ((\slk, (\env', H)) \cons \shk) \circ \shk'}
&\stepsto& \multicolumn{2}{@{}l@{}}{\cek{M \mid \env'[x \mapsto \val{V}{\env},
r \mapsto (\shk' \concat [(\slk, (\env', H))])^B] \mid \shk},} \\
&&& \multicolumn{2}{@{}r@{}}{\text{if } \ell : A \to B \in E \text{ and } \hell = \{\ell\; x \; r \mapsto M\}} \\
% Shallow
\mlab{Do^\dagger} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid ((\slk, (\gamma', H)^\dagger) \cons \shk) \circ \shk'} &\stepsto&
\multicolumn{2}{@{}l@{}}{\cek{M \mid \env'[x \mapsto \val{V}{\env}, r \mapsto (\shk', \slk)^B] \mid \shk},}\\
&&&\multicolumn{2}{@{}r@{}}{\text{if } \ell : A \to B \in E \text{ and } \hell = \{\ell\; x \; r \mapsto M\}} \\
% Forward
\mlab{Forward} & \cek{ (\Do \; \ell \; V)^E \mid \env \mid ((\slk, (\env', H)^\depth) \cons \shk) \circ \shk'}
&\stepsto& \cek{ (\Do \; \ell \; V)^E \mid \env \mid \shk \circ (\shk' \concat [(\slk, (\env', H)^\depth)])},
& \text{if } \hell = \emptyset \\
\ea \\
\el
\end{displaymath}
\textbf{Value interpretation}
\begin{displaymath}
\ba{@{}r@{~}c@{~}l@{}}
\val{x}{\env} &=& \env(x) \\
\val{\Record{}}{\env} &=& \Record{} \\
\ea
\qquad
\ba{@{}r@{~}c@{~}l@{}}
\val{\lambda x^A.M}{\env} &=& (\env, \lambda x^A.M) \\
\val{\Record{\ell = V; W}}{\env} &=& \Record{\ell = \val{V}{\env}; \val{W}{\env}} \\
\ea
\qquad
\ba{@{}r@{~}c@{~}l@{}}
\val{\Lambda \alpha^K.M}{\env} &=& (\env, \Lambda \alpha^K.M) \\
\val{(\ell\, V)^R}{\env} &=& (\ell\; \val{V}{\env})^R \\
\ea
\qquad
\val{\Rec\,g^{A \to C}\,x.M}{\env} = (\env, \Rec\,g^{A \to C}\,x.M) \\
\end{displaymath}
\caption{Abstract machine semantics.}
\label{fig:abstract-machine-semantics}
\end{minipage}
\end{figure}
%
%
A configuration $\conf = \cek{M \mid \env \mid \shk \circ \shk'}$
of our abstract machine is a quadruple of a computation term ($M$), an
environment ($\env$) mapping free variables to values, and two
continuations ($\shk$) and ($\shk'$).
%
The latter continuation is always the identity, except when forwarding
an operation, in which case it is used to keep track of the extent to
which the operation has been forwarded.
%
We write $\cek{M \mid \env \mid \shk}$ as syntactic sugar for $\cek{M
\mid \env \mid \shk \circ []}$ where $[]$ is the identity
continuation.
%
%% Our continuations differ from the standard machine. On the one hand,
%% they are somewhat simplified, due to our strict separation between
%% computations and values. On the other hand, they have considerably
%% more structure in order to accommodate effects and handlers.
Values consist of function closures, type function closures, records,
variants, and captured continuations.
%
A continuation $\shk$ is a stack of frames $[\shf_1, \dots,
\shf_n]$. We annotate captured continuations with input types in order
to make the results of Section~\ref{subsec:machine-correctness} easier
to state. Each frame $\shf = (\slk, \chi)$ represents pure
continuation $\slk$, corresponding to a sequence of let bindings,
inside 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$. A handler closure $(\env, H)$ closes a handler definition $H$
over environment $\env$.
%
We 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 abstract machine semantics defining the transition function $\stepsto$ is given in
Fig.~\ref{fig:abstract-machine-semantics}.
%
It depends on an interpretation function $\val{-}$ for values.
%
The machine is initialised (\mlab{Init}) by placing a term in a
configuration alongside the empty environment and identity
continuation.
%
The rules (\mlab{AppClosure}), (\mlab{AppRec}), (\mlab{AppCont}),
(\mlab{AppCont^\dagger}), (\mlab{AppType}), (\mlab{Split}), and
(\mlab{Case}) enact the elimination of values.
%
The rules (\mlab{Let}) and (\mlab{Handle}) extend the current
continuation with let bindings and handlers respectively.
%
The rule (\mlab{RetCont}) binds a returned value if there is a pure
continuation in the current continuation frame;
%
(\mlab{RetHandler}) invokes the return clause of a handler if the pure
continuation is empty; and (\mlab{RetTop}) returns a final value if
the continuation is empty.
%
%% The rule (\mlab{Op}) switches to a special four place configuration in
%% order to handle an operation. The fourth component of the
%% configuration is an auxiliary forwarding continuation, which keeps
%% track of the continuation frames through which the operation has been
%% forwarded. It is initialised to be empty.
%% %
The rule (\mlab{Do}) applies the current handler to an operation if
the label matches one of the operation clauses. The captured
continuation is assigned the forwarding continuation with the current
frame appended to the end of it.
%
The rule (\mlab{Do^\dagger}) is much like (\mlab{Do}), except it
constructs a shallow resumption, discarding the current handler but
keeping the current pure continuation.
%
The rule (\mlab{Forward}) appends the current continuation
frame onto the end of the forwarding continuation.
%
The (\mlab{Init}) rule provides a canonical way to map a computation
term onto a configuration.
\newcommand{\Chiid}{\ensuremath{\Chi_{\text{id}}}}
\newcommand{\kappaid}{\ensuremath{\kappa_{\text{id}}}}
\paragraph{Example} To make the transition rules in
Figure~\ref{fig:abstract-machine-semantics} concrete we give an
example of the abstract machine in action. We reuse the small producer
and consumer from Section~\ref{sec:shallow-handlers-tutorial}. We
reproduce their definitions here in ANF.
%
\[
\bl
\Ones \defas \Rec\;ones~\Unit. \Do\; \Yield~1; ones~\Unit \\
\AddTwo \defas
\lambda \Unit.
\Let\; x \revto \Do\;\Await~\Unit \;\In\;
\Let\; y \revto \Do\;\Await~\Unit \;\In\;
x + y \\
\el
\]%
%
Let $N_x$ denote the term $\Let\;x \revto \Do\;\Await~\Unit\;\In\;N_y$
and $N_y$ the term $\Let\;y \revto \Do\;\Await~\Unit\;\In\;x+y$.
%
%% For clarity, we annotate each bound variable $resume$ with a subscript
%% $\Await$ or $\Yield$ according to whether it was captured by a
%% consumer or a producer.
%
Suppose $\Ones$, $\AddTwo$, $\Pipe$, and $\Copipe$ are bound in
$\env_\top$. Furthermore, let $H_\Pipe$ and $H_\Copipe$ denote the
pipe and copipe handler definitions. The machine begins by applying
$\Pipe$.
%
\begin{derivation}
&\cek{\Pipe~\Record{\Ones, \AddTwo} \mid \env_\top \mid \kappaid}\\
\stepsto& \reason{apply $\Pipe$}\\
&\cek{\ShallowHandle\;c~\Unit\;\With\;H_\Pipe \mid \env_\top[c \mapsto (\emptyset, \AddTwo), p \mapsto (\emptyset, \Ones)] \mid \kappaid}\\
\stepsto& \reason{install $H_\Pipe$ with $\env_\Pipe = \env_\top[c \mapsto (\emptyset, \AddTwo), p \mapsto (\emptyset, \Ones)]$}\\
&\cek{c~\Unit \mid \env_\Pipe \mid (\nil, (\env_\Pipe, H_\Pipe)) \cons \kappaid}\\
\stepsto& \reason{apply $c$ and $\val{c}\env_\Pipe = (\emptyset, \AddTwo)$}\\
&\cek{N_x \mid \emptyset \mid (\nil, (\env_\Pipe, H_\Pipe)) \cons \kappaid}\\
\stepsto& \reason{focus on left operand}\\
&\cek{\Do\;\Await~\Unit \mid \emptyset \mid ([(\emptyset, x, N_y)], (\env_\Pipe, H_\Pipe)) \cons \kappaid}\\
\stepsto& \reason{shallow continuation capture $v_\Await = (\nil, [(\emptyset, x, N_y)])$}\\
&\cek{\Copipe~\Record{resume, p} \mid \env_\Pipe[resume \mapsto v_\Await] \mid \kappaid}\\
\end{derivation}
%
The invocation of $\Await$ begins a search through the machine
continuation to locate a matching handler. In this instance the
top-most handler $H_\Pipe$ handles $\Await$. The complete shallow
resumption consists of an empty continuation and a singleton pure
continuation. The former is empty as $H_\Pipe$ is a shallow handler,
meaning that it is discarded.
Evaluation continues by applying $\Copipe$.
%
\begin{derivation}
\stepsto& \reason{apply $\Copipe$}\\
&\cek{\ShallowHandle\; p~\Unit \;\With\;H_\Copipe \mid \env_\top[c \mapsto v_\Await, p \mapsto (\emptyset, \Ones)] \mid \kappaid}\\
\stepsto& \reason{install $H_\Copipe$ with $\env_\Copipe = \env_\top[c \mapsto v_\Await, p \mapsto (\emptyset, \Ones)]$}\\
&\cek{p~\Unit \mid \env_\Copipe \mid (\emptyset, (\env_\Copipe, H_\Copipe)) \cons \kappaid}\\
\stepsto^2& \reason{apply $p$, $\val{p}\env_\Copipe = (\emptyset, \Ones)$, and $\env_{\Ones} = \emptyset[ones \mapsto (\emptyset, \Ones)]$}\\
% &\cek{\Do\;\Yield~1;~ones~\Unit \mid \env_{ones} \mid (\nil, (\env_\Copipe, H_\Copipe)) \cons \kappaid}\\
% \stepsto^2& \reason{focus on $\Yield$}\\
&\cek{\Do\;\Yield~1 \mid \env_{\Ones} \mid ([(\env_{\Ones},\_,ones~\Unit)], (\env_\Copipe, H_\Copipe)) \cons \kappaid}\\
\stepsto& \reason{shallow continuation capture $v_\Yield = (\nil, [(\env_{\Ones}, \_, ones~\Unit)])$}\\
&\cek{\Pipe~\Record{resume, \lambda\Unit.c~s} \mid \env_\Copipe[s \mapsto 1,resume \mapsto v_\Yield] \mid \kappaid}
\end{derivation}
%
At this point the situation is similar as before: the invocation of
$\Yield$ causes the continuation to be unwound in order to find an
appropriate handler, which happens to be $H_\Copipe$. Next $\Pipe$ is
applied.
%
\begin{derivation}
\stepsto& \reason{apply $\Pipe$ and $\env_\Pipe' = \env_\top[c \mapsto (\env_\Copipe[c \mapsto v_\Await, s\mapsto 1]), p \mapsto v_\Yield])]$}\\
&\cek{\ShallowHandle\;c~\Unit\;\With\; H_\Pipe \mid \env_\Pipe' \mid \kappaid}\\
\stepsto& \reason{install $H_\Pipe$}\\
&\cek{c~\Unit \mid \env_\Pipe' \mid (\nil, (\env_\Pipe', H_\Pipe)) \cons \kappaid}\\
\stepsto& \reason{apply $c$ and $\val{c}\env_\Pipe' = (\env_\Copipe[c \mapsto v_\Await, s\mapsto 1])$}\\
&\cek{c~s \mid \env_\Copipe[c \mapsto v_\Await, s\mapsto 1] \mid (\nil, (\env_\Pipe', H_\Pipe)) \cons \kappaid}\\
\stepsto& \reason{shallow resume with $v_\Await = (\nil, [(\emptyset,x,N_y)])$}\\
&\cek{\Return\;1 \mid \env_\Pipe' \mid ([(\emptyset,x,N_y)], (\env_\Pipe', H_\Pipe)) \cons \kappaid}
\end{derivation}
%
Applying the resumption concatenates the first component (the
continuation) with the machine continuation. The second component
(pure continuation) gets concatenated with the pure continuation of
the top-most frame of the machine continuation. Thus in this
particular instance, the machine continuation is manipulated as
follows.
%
\[
\ba{@{~}l@{~}l}
&\nil \concat ([(\emptyset,x,N_y)] \concat \nil, (\env_\Pipe', H_\Pipe)) \cons \kappaid\\
=& ([(\emptyset,x,N_y)], (\env_\Pipe', H_\Pipe)) \cons \kappaid
\ea
\]
%
Because the control component contains the expression $\Return\;1$ and
the pure continuation is nonempty, the machine applies the pure
continuation.
\begin{derivation}
\stepsto& \reason{apply pure continuation, $\env_{\AddTwo} = \emptyset[x \mapsto 1]$}\\
&\cek{N_y \mid \env_{\AddTwo} \mid (\nil, (\env_\Pipe', H_\Pipe)) \cons \kappaid}\\
\stepsto& \reason{focus on right operand}\\
&\cek{\Do\;\Await~\Unit \mid \env_{\AddTwo} \mid ([(\env_{\AddTwo}, y, x + y)], (\env_\Pipe', H_\Pipe)) \cons \kappaid}\\
\stepsto^2& \reason{shallow continuation capture $w_\Await = (\nil, [(\env_{\AddTwo}, y, x + y)])$, apply $\Copipe$}\\
&\cek{\ShallowHandle\;p~\Unit \;\With\; \env_\Copipe \mid \env_\top[c \mapsto w_\Await, p \mapsto v_\Yield] \mid \kappaid}\\
\reducesto& \reason{install $H_\Copipe$ with $\env_\Copipe = \env_\top[c \mapsto w_\Await, p \mapsto v_\Yield]$}\\
&\cek{p~\Unit \mid \env_\Copipe \mid (\nil, (\env_\Copipe, H_\Copipe)) \cons \kappaid}
\end{derivation}
%
The variable $p$ is bound to the shallow resumption $v_\Yield$, thus
invoking it will transfer control back to the $\Ones$ computation.
%
\begin{derivation}
\stepsto & \reason{shallow resume with $v_\Yield = (\nil, [(\env_{\Ones}, \_, ones~\Unit)])$}\\
&\cek{\Return\;\Unit \mid \env_\Copipe \mid ([(\env_{\Ones}, \_, ones~\Unit)], (\env_\Copipe, H_\Copipe)) \cons \kappaid}\\
\stepsto^3& \reason{apply pure continuation, apply $ones$, focus on $\Yield$}\\
&\cek{\Do\;\Yield~1 \mid \env_{\Ones} \mid ([(\env_{\Ones}, \_, ones~\Unit)], (\env_\Copipe, H_\Copipe)) \cons \kappaid}\\
\end{derivation}
%
At this stage the machine repeats the transitions from before: the
shallow continuation of $\Do\;\Yield~1$ is captured, control passes to
the $\Yield$ clause in $H_\Copipe$, which again invokes $\Pipe$ and
subsequently installs the $H_\Pipe$ handler with an environment
$\env_\Pipe''$. The handler runs the computation $c~\Unit$, where $c$
is an abstraction over the resumption $w_\Await$ applied to the
yielded value $1$.
%
\begin{derivation}
\stepsto^6 & \reason{by the above reasoning, shallow resume with $w_\Await = (\nil, [(\env_{\AddTwo}, y, x + y)])$}\\
&\cek{x + y \mid \env_{\AddTwo}[y \mapsto 1] \mid (\nil, (\env_\Pipe'', H_\Pipe)) \cons \kappaid}\\
\stepsto& \reason{$\val{x}\env_{\AddTwo}[y\mapsto 1] = 1$ and $\val{y}\env_{\AddTwo}[y\mapsto 1] = 1$}\\
&\cek{\Return\;2 \mid \env_{\AddTwo}[y \mapsto 1] \mid (\nil, (\env_\Pipe'', H_\Pipe)) \cons \kappaid}
\end{derivation}
%
Since the pure continuation is empty the $\Return$ clause of $H_\Pipe$
gets invoked with the value $2$. Afterwards the $\Return$ clause of the
identity continuation in $\kappaid$ is invoked, ultimately
transitioning to the following final configuration.
%
\begin{derivation}
\stepsto^2& \reason{by the above reasoning}\\
&\cek{\Return\;2 \mid \emptyset \mid \nil}
\end{derivation}
%
%\env_\top[c \mapsto (\env_\Copipe, \lambda\Unit.w_\Await~1),p \mapsto (\nil, [(\env_{\Ones}, \_, ones~\Unit)])]
\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)}
%
Configurations
\begin{displaymath}
\inv{\cek{M \mid \env \mid \shk \circ \shk'}} = \contappp{\inv{\shk' \concat \shk}}{\inv{M}\env}
= \contappp{\inv{\shk'}}{\contapp{\inv{\shk}}{\inv{M}\env}}
\end{displaymath}
Pure continuations
\begin{displaymath}
\contapp{\inv{[]}}{M} = M \qquad \contapp{\inv{((\env, x, N) \cons \slk)}}{M}
= \contappp{\inv{\slk}}{\Let\; x \revto M \;\In\; \inv{N}(\env \res \{x\})}
\end{displaymath}
%% \begin{equations}
%% \contapp{\inv{[]}}{M}
%% &=& M \\
%% \contapp{\inv{((\env, x, N) \cons \slk)}}{M}
%% &=& \contappp{\inv{\slk}}{\Let\; x \revto M \;\In\; \inv{N}(\env \res \{x\})} \\
%% \end{equations}
Continuations
\begin{displaymath}
\contapp{\inv{[]}}{M}
= M \qquad
\contapp{\inv{(\slk, \chi) \cons \shk}}{M}
= \contapp{\inv{\shk}}{(\contappp{\inv{\chi}}{\contappp{\inv{\slk}}{M}})}
\end{displaymath}
%% \begin{equations}
%% \contapp{\inv{[]}}{M}
%% &=& M \\
%% \contapp{\inv{(\slk, \chi) \cons \shk}}{M}
%% &=& \contapp{\inv{\shk}}{(\contappp{\inv{\chi}}{\contappp{\inv{\slk}}{M}})} \\
%% \end{equations}
Handler closures
\begin{displaymath}
\contapp{\inv{(\env, H)}^\depth}{M}
= \Handle^\depth\;M\;\With\;\inv{H}\env
\end{displaymath}
%% \begin{equations}
%% \contapp{\inv{(\env, H)}}{M}
%% &=& \Handle\;M\;\With\;\inv{H}\env \\
%% \contapp{\inv{(\env, H)^\dagger}}{M}
%% &=& \ShallowHandle\;M\;\With\;\inv{H}\env \\
%% \end{equations}
Computation terms
\begin{equations}
\inv{V\,W}\env &=& \inv{V}\env\,\inv{W}{\env} \\
\inv{V\,T}\env &=& \inv{V}\env\,T \\
\inv{\Let\;\Record{\ell = x; y} = V \;\In\;N}\env
&=& \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
&=& \Case\;\inv{V}\env \,\{\ell\;x \mapsto \inv{M}(\env \res \{x\}); y \mapsto \inv{N}(\env \res \{y\})\} \\
\inv{\Return\;V}\env &=& \Return\;\inv{V}\env \\
\inv{\Let\;x \revto M \;\In\;N}\env
&=& \Let\;x \revto\inv{M}\env \;\In\; \inv{N}(\env \res \{x\}) \\
\inv{\Do\;\ell\;V}\env
&=& \Do\;\ell\;\inv{V}\env \\
\inv{\Handle^\depth\;M\;\With\;H}\env
&=& \Handle^\depth\;\inv{M}\env\;\With\;\inv{H}\env \\
\end{equations}
Handler definitions
\begin{equations}
\inv{\{\Return\;x \mapsto M\}}\env
&=& \{\Return\;x \mapsto \inv{M}(\env \res \{x\})\} \\
\inv{\{\ell\;x\;k \mapsto M\} \uplus H}\env
&=& \{\ell\;x\;k \mapsto \inv{M}(\env \res \{x, k\}\} \uplus \inv{H}\env \\
\end{equations}
Value terms and values
\begin{displaymath}
\ba{@{}c@{}}
\begin{eqs}
\inv{x}\env &=& \inv{v}, \quad \text{ if }\env(x) = v \\
\inv{x}\env &=& x, \quad \text{ if }x \notin dom(\env) \\
\inv{\lambda x^A.M}\env &=& \lambda x^A.\inv{M}(\env \res \{x\}) \\
\inv{\Lambda \alpha^K.M}\env &=& \Lambda \alpha^K.\inv{M}\env \\
\inv{\Record{}}\env &=& \Record{} \\
\inv{\Record{\ell=V; W}}\env &=& \Record{\ell=\inv{V}\env; \inv{W}\env} \\
\inv{(\ell\;V)^R}\env &=& (\ell\;\inv{V}\env)^R \\
\end{eqs}
\quad
\begin{eqs}
\inv{\shk^A} &=& \lambda x^A.\inv{\shk}(\Return\;x) \\
\inv{(\shk, \slk)^A} &=& \lambda x^A.\inv{\slk}(\inv{\shk}(\Return\;x)) \\
\inv{(\env, \lambda x^A.M)} &=& \lambda x^A.\inv{M}(\env \res \{x\}) \\
\inv{(\env, \Lambda \alpha^K.M)} &=& \Lambda \alpha^K.\inv{M}\env \\
\inv{\Record{}} &=& \Record{} \\
\inv{\Record{\ell=v; w}} &=& \Record{\ell=\inv{v}; \inv{w}} \\
\inv{(\ell\;v)^R} &=& (\ell\;\inv{v})^R \\
\end{eqs} \smallskip\\
\inv{\Rec\,g^{A \to C}\,x.M}\env = \Rec\,g^{A \to C}\,x.\inv{M}(\env \res \{g, x\})
= \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}
\paragraph{Remark} If the main continuation is empty then the machine gets
stuck. This occurs when an operation is unhandled, and the forwarding
continuation describes the succession of handlers that have failed to
handle the operation along with any pure continuations that were
encountered along the way.
%
Assuming the input is a well-typed closed computation term $\typc{}{M
: A}{E}$, the machine will either not terminate, return a value of
type $A$, or get stuck failing to handle an operation appearing in
$E$. We now make the correspondence between the operational semantics
and the abstract machine more precise.
\section{Correctness}
\label{subsec:machine-correctness}
Fig.~\ref{fig:config-to-term} defines an inverse mapping $\inv{-}$
from configurations to computation terms via a collection of mutually
recursive functions defined on configurations, continuations,
computation terms, handler definitions, value terms, and values.
%
We write $dom(\gamma)$ for the domain of $\gamma$ and $\gamma \res
\{x_1, \dots, x_n\}$ for the restriction of environment $\gamma$ to
$dom(\gamma) \res \{x_1, \dots, x_n\}$.
%
The $\inv{-}$ function enables us to classify the abstract machine
reduction rules by how they relate to the operational
semantics.
%
The rules (\mlab{Init}) and (\mlab{RetTop}) concern only initial
input and final output, neither a feature of the operational
semantics.
%
The rules (\mlab{AppCont^\depth}), (\mlab{Let}), (\mlab{Handle}),
and (\mlab{Forward}) are administrative in that $\inv{-}$ is invariant
under them.
%
This leaves $\beta$-rules (\mlab{AppClosure}), (\mlab{AppRec}),
(\mlab{AppType}), (\mlab{Split}), (\mlab{Case}), (\mlab{RetCont}),
(\mlab{RetHandler}), (\mlab{Do}), and (\mlab{Do^\dagger}),
each of which corresponds directly to performing a reduction in the
operational semantics.
%
We write $\stepsto_a$ for administrative steps, $\stepsto_\beta$ for
$\beta$-steps, and $\Stepsto$ for a sequence of steps of the form
$\stepsto_a^* \stepsto_\beta$.
Each reduction in the operational semantics is simulated by a sequence
of administrative steps followed by a single $\beta$-step in the
abstract machine. The $Id$ handler (\S\ref{subsec:terms})
implements the top-level identity continuation.
\\
\begin{theorem}[Simulation]
\label{lem:simulation}
If $M \reducesto N$, then for any $\conf$ such that $\inv{\conf} =
Id(M)$ there exists $\conf'$ such that $\conf \Stepsto \conf'$ and
$\inv{\conf'} = Id(N)$.
%% If $M \reducesto N$, then for any $\conf$ such that $\inv{\conf} = M$
%% there exists $\conf'$ such that $\conf \Stepsto \conf'$ and
%% $\inv{\conf'} = N$.
\end{theorem}
\begin{proof}
By induction on the derivation of $M \reducesto N$.
\end{proof}
\begin{corollary}
If $\typc{}{M : A}{E}$ and $M \reducesto^+ N \not\reducesto$, then $M
\stepsto^+ \conf$ with $\inv{\conf} = N$.
\end{corollary}
\part{Expressiveness}
\chapter{Computability, complexity, and expressivness}
\label{ch:expressiveness}
\section{Notions of expressiveness}
Felleisen's macro-expressiveness, Longley's type-respecting
expressiveness, Kammar's typability-preserving expressiveness.
\section{Interdefinability of deep and shallow Handlers}
\section{Encoding parameterised handlers}
\chapter{The asymptotic power of control}
\label{ch:handlers-efficiency}
Describe the methodology\dots
\section{Generic search}
\section{Calculi}
\subsection{Base calculus}
\subsection{Handler calculus}
\section{A practical model of computation}
\subsection{Syntax}
\subsection{Semantics}
\subsection{Realisability}
\section{Points, predicates, and their models}
\section{Efficient generic search with effect handlers}
\subsection{Space complexity}
\section{Best-case complexity of generic search without control}
\subsection{No shortcuts}
\subsection{No sharing}
\chapter{Robustness of the asymptotic power of control}
\section{Mutable state}
\section{Exception handling}
\section{Effect system}
\part{Conclusions}
\chapter{Conclusions}
\label{ch:conclusions}
Some profound conclusions\dots
\chapter{Future Work}
\label{ch:future-work}
%%
%% Appendices
%%
% \appendix
%% If you want the bibliography single-spaced (which is allowed), uncomment
%% the next line.
%\nocite{*}
\singlespace
%\nocite{*}
%\printbibliography[heading=bibintoc]
\bibliographystyle{plainnat}
\bibliography{\jobname}
%% ... that's all, folks!
\end{document}