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.
552 lines
22 KiB
552 lines
22 KiB
\documentclass{article}
|
|
|
|
\usepackage{mygeometry}
|
|
\usepackage{mylistings}
|
|
\usepackage{myhyperref}
|
|
\usepackage{mybibliography}
|
|
|
|
\usepackage{notations}
|
|
|
|
\title{Quantified Applicatives -- API design for type-inference constraints}
|
|
\author{Olivier Martinot, Gabriel Scherer} % ordre alphabétique
|
|
|
|
\begin{document}
|
|
|
|
\maketitle
|
|
|
|
% TODO use Software Heritage for software and code source citation
|
|
|
|
\begin{abstract}
|
|
The \Inferno{} library~\citep*{inferno,inferno-code} uses an
|
|
applicative functor \lstinline{'a co} to represent constraint-based
|
|
inference problems that elaborate into explicitly-typed term
|
|
representations. A central operation of the \Inferno{} API is the
|
|
\lstinline{exist} quantifier, which generates a fresh inference
|
|
variable scoped in an existential constraint. \lstinline{exist}
|
|
returns the result of solving the constraint, as well as the
|
|
elaborated type inferred for the inference variable:
|
|
%
|
|
\lstinline{val exist : (variable -> 'a co) -> ('a * ty) co}
|
|
|
|
We found that programming with this interface is difficult and leads
|
|
to program that are hard to read. The difficulty is specific to
|
|
applicative functors, there would be standard solutions with a monad.
|
|
|
|
We report on our API design work to solve this issue. We start by
|
|
explaining the programming difficulties and why the \Inferno{}
|
|
functor is not a monad, then a first experiment with a codensity
|
|
monad, and finally a simpler solution that avoids mixing several
|
|
functors in the same codebase. Our approach makes good use of
|
|
the ``binding operators'' recently introduced in OCaml to provide
|
|
flexible syntactic sugar for monad or applicative-like structures.
|
|
|
|
We believe that the API design pattern we propose, ``turning direct
|
|
outputs into modal inputs'', may apply to any applicative functors
|
|
with quantifiers. It may already be in use, and its discussion may
|
|
benefit other projects. Experience sharing with the ML Workshop
|
|
audience could be very valuable.
|
|
\end{abstract}
|
|
|
|
\section{Inferno}
|
|
|
|
François Pottier's \Inferno{}~\citep{inferno-code} is a software
|
|
library for constraint-based type inference. It exposes low-level data
|
|
structures and algorithms for efficient inference (union-find graphs
|
|
for unification, level-based generalization), a term representation
|
|
for inference constraints
|
|
(\sourcelink{src/SolverLo.mli}{SolverLo.mli})\footnote{We give links
|
|
to source code for the interested reader, which are versioned to
|
|
point at the versions of the software at the time of writing
|
|
(git revision
|
|
\texttt{d02e4c23430df0b8cd15a99adfa41fd00086b925}). }, and
|
|
a higher-level API (\sourcelink{src/SolverHi.mli}{SolverHi.mli})
|
|
exposing an applicative functor \lstinline{'a co} designed to write
|
|
constraint-based type inference engines in a declarative
|
|
style~\citep{inferno}.
|
|
|
|
A value of type \lstinline{'a co} pairs together an inference
|
|
constraint with a function that will be called with the solution of
|
|
the constraint to construct an \lstinline{'a} ``witness'', that
|
|
inference has suceeded. For example, a type-inference procedure for ML
|
|
programs could have type
|
|
%
|
|
\lstinline{ML.term -> F.term co}: it takes an ML term and generates
|
|
a constraint that, once solved, will produce an explicity-typed
|
|
representation of the input program as a System F term. The
|
|
implementation of Inferno comes with exactly such an example inference
|
|
program, for a core fragment of ML:
|
|
\sourcelink{client/Client.ml}{Client.ml}.
|
|
|
|
In this document we will use the following running example, adapted
|
|
from a fragment of the \Inferno{} inference example. On the right, we
|
|
show the types of the combinators we use, provided by \Inferno{}'s
|
|
high-level layer:
|
|
|
|
\begin{minipage}{0.45\linewidth}
|
|
\begin{lstlisting}
|
|
let rec hastype (t : ML.term) (w : Infer.variable) : F.term co =
|
|
match t with
|
|
| [...]
|
|
| ML.Pair (u1, u2) ->
|
|
exist (fun v1 ->
|
|
exist (fun v2 ->
|
|
w --- Infer.Prod(v1, v2) ^^
|
|
hastype u1 v1 ^&
|
|
hastype u2 v2
|
|
)) <$$> fun (ty1, (ty2, (u'1, u'2))) ->
|
|
F.Pair ((u'1, ty1), (u'2, ty2))
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\hfill
|
|
\begin{minipage}{0.55\linewidth}
|
|
\begin{lstlisting}
|
|
val exist : (Infer.variable -> 'a co) -> (F.ty * 'a) co
|
|
val (---) : Infer.variable -> Infer.ty -> unit co
|
|
val (^^) : unit co -> 'b co -> 'b co
|
|
val (^&) : 'a co -> 'b co -> ('a * 'b) co
|
|
val (<$$>) : 'a co -> ('a -> 'b) -> 'b co
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
|
|
We define a function \lstinline{hastype : ML.term -> Infer.variable -> F.term co}
|
|
which takes an ML term \lstinline{t} and a "destination inference variable"
|
|
\lstinline{w}; the returned constraint will unify the inferred type of
|
|
\lstinline{t} with \lstinline{w}, and return the elaborated System
|
|
F term. We show the inference code for the \lstinline{Pair(u1,u2)}
|
|
construct, representing pairs in ML; for illustration purposes we use
|
|
a "very explicit" syntax for pairs in the System F side, which expects
|
|
the two terms but also their types:~%
|
|
\lstinline{F.Pair : (F.term * F.ty) * (F.term * F.ty) -> F.term}.
|
|
(In practice our pair syntax does not require explicit types,
|
|
but the Church-style syntax for $\lambda$-abstractions does.)
|
|
|
|
The infix operation \lstinline{(---)} inserts a unification constraint
|
|
(\lstinline{Infer.ty} is not the source representation of ML or
|
|
F types, but an inference-specific representation of types, which the
|
|
engine knows how to traverse for unification, etc.). The infix
|
|
combinators \lstinline{(^^), (^&)} pair constraints together. Finally,
|
|
\lstinline{<$$>} is a flipped \lstinline{map} function for constraints.
|
|
|
|
All type-inference cases have roughly this structure: using
|
|
\lstinline{exist} to create inference variables for subterms,
|
|
combining type-checking constraints to follow our type-checking rule,
|
|
concluded by a final ``map'' that takes all the witnesses of the
|
|
constraint-solving pieces, and combines them into a final witness for
|
|
the inferred term.
|
|
|
|
\subsection{The problem} Consider extending this code to handle not
|
|
just binary pairs, but n-ary tuples \lstinline{Tuple us}. Instead of
|
|
a statically-known number of inference variables (2), we want to
|
|
generate one for each subterm of the list, to build the result
|
|
type. How would you program this with the proposed API?
|
|
|
|
If \lstinline{'a co} was an inference/elaboration \emph{monad}, it
|
|
would be natural to decompose the problem with a function of type
|
|
\lstinline{'a list -> Infer.variable list co}, or possibly
|
|
\lstinline{mapM : ('a -> 'b co) -> 'a list -> 'b list co}, and then
|
|
\lstinline{bind} the result to continue. We write this hypothetical
|
|
program below on the left column. But \lstinline{'a co} is \emph{not}
|
|
a monad for a good reason -- we explain this in the next
|
|
section. Instead, we have to write code in continuation-passing style,
|
|
on the right column below.
|
|
|
|
\hspace{-3em}
|
|
\begin{minipage}{0.5\linewidth}
|
|
\begin{lstlisting}
|
|
| ML.Tuple us ->
|
|
let on_each u =
|
|
exist (fun v -> v ^& hastype u v)
|
|
<$$> fun (ty, (v, u')) -> (v, (u', ty))
|
|
in
|
|
mapM on_each us >>= fun vutys ->
|
|
let vs, utys = List.split vutys in
|
|
w --- Infer.product vs ^^
|
|
return (ML.tuple utys)
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\hfill
|
|
\begin{minipage}{0.5\linewidth}
|
|
\begin{lstlisting}
|
|
| ML.Tuple us ->
|
|
let rec traverse us (k : variable list -> 'r co)
|
|
: ((F.term * F.ty) list * 'r) co
|
|
= match us with
|
|
| [] -> k [] <$$> fun r -> ([], r)
|
|
| u::us ->
|
|
exist (fun v ->
|
|
traverse us (fun vs ->
|
|
hastype u v ^&
|
|
k (v :: vs)
|
|
)) <$$> fun (ty, (u', (utys, rs))) ->
|
|
((u', ty) :: utys, r)
|
|
in
|
|
traverse us (fun vs ->
|
|
w --- Infer.product vs
|
|
) <$$> fun (utys, ()) ->
|
|
F.Tuple utys
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
|
|
Note: \lstinline{traverse} is called with a single continuation; it is
|
|
possible to inline this continuation in the empty-list case, and get
|
|
a simpler, less generic function, that could not be reused
|
|
elsewhere. We wish to discuss the reusable presentation in this
|
|
document, but the API design improvements also help in the
|
|
non-reusable case.
|
|
|
|
There are two difficulties with the code we had to write:
|
|
\begin{enumerate}
|
|
\item It is much harder to figure out how to write this code than in the monadic
|
|
version, which allows a natural problem decomposition. Programmers may not know
|
|
how to generate dynamically many inference variables. If they know that
|
|
some things can be done with CPS, they may not know exactly which,
|
|
or which signatures to use for CPS functions.
|
|
\item The code, in either versions, is hard to read. In particular,
|
|
values are \emph{named} far from where they are \emph{expressed} in
|
|
the program; notice for example how \lstinline{u'} binds the witness
|
|
of \lstinline{hastype u v}, but is placed farther away in the
|
|
program. This problem gets worse when writing inference code for
|
|
more complex constructs.
|
|
|
|
When we explain this code, we say that the \lstinline{Infer.ty}
|
|
result of \lstinline{exist} or the \lstinline{(F.term * F.ty) list}
|
|
result of \lstinline{traverse} is ``pushed on the stack'' -- which
|
|
is then later shuffled around by the \lstinline{(<$$>)} operator.
|
|
We would rather avoid this particular style of point-free programming.
|
|
\end{enumerate}
|
|
|
|
Our contribution is to identify, explain this problem, and propose
|
|
a partial solution: a systematic approach to programming with
|
|
``binders'' that can be explained and documented, and a different API
|
|
that lets us name terms in a more natural, local way -- at the cost of
|
|
``unpacking'' operations later on, as we will explain.
|
|
|
|
While our work is specific to Inferno (and OCaml), we believe that
|
|
this API problem arises in the more general case of an applicative
|
|
functor with quantifiers, as \lstinline{exist} in our example.
|
|
|
|
\subsection{Aside: binding operators} OCaml recently adopted a flexible
|
|
syntactic sugar for composing computations in monadic- and
|
|
applicative-like structures called ``binding operators''. Just like we
|
|
allow user-defined ``infix operators'' and ``prefix operators'' in
|
|
a limited set of symbols (\lstinline{(^^)}, \lstinline{(^&)}
|
|
were examples), we allow ``binding operators'' of the form
|
|
\lstinline{let<op>} and \lstinline{and<op>}, where \lstinline{<op>} is
|
|
a user-chosen sequence of symbols, with the following desugaring rules:
|
|
\begin{lstlisting}
|
|
let<op1> p1 = e1 and<op2> p2 = e2 in body
|
|
==>
|
|
(let<op1>) ((and<op2>) e1 e2) (fun (p1, p2) -> body)
|
|
\end{lstlisting}
|
|
The current convention is to use \lstinline{(let* )} for monadic
|
|
binding, and \lstinline{(let+), (and+)} for applicative map and
|
|
product. For example, with suitable user definitions one can write %
|
|
\lstinline{(let* x = m in e)} for %
|
|
\lstinline{(m >>= fun x -> e)}, and %
|
|
\lstinline{(let+ x = foo and+ y = bar in e)} for %
|
|
\lstinline{(pure (fun x y -> e) <$> foo <$> bar)}. Other symbols are
|
|
up for grabs.
|
|
|
|
We will use binding operators for convenience, but our solution does not
|
|
rely on them in an essential way.
|
|
|
|
\subsection{Not a monad} Giving up on our page limit, let us explain
|
|
why the \lstinline{'a co} type of Inferno cannot be given the
|
|
structure of a monad.
|
|
|
|
The witness \lstinline{'a} is built with knowledge of the
|
|
\emph{global} solution to the inference constraints
|
|
generated. Consider the applicative combinator %
|
|
\lstinline{pair : 'a co -> 'b co -> ('a * 'b) co}; both arguments
|
|
generate a part of the constraint, but the witnesses of \lstinline{'a}
|
|
and \lstinline{'b} can only be computed once the constraints on
|
|
\emph{both} sides are solved. For example, when inferring the ML term
|
|
\lstinline{(x, x + 1)}, the type inferred for the first component of
|
|
the pair is determined by unifications coming from the constraint of
|
|
the second.
|
|
|
|
Implementing \lstinline{bind : 'a co -> ('a -> 'b co) -> 'b co}
|
|
would require building the witness for \lstinline{'a} before the
|
|
constraint arising from \lstinline{'b co} is known; this cannot be
|
|
done if \lstinline{'a} requires the final solution.
|
|
|
|
For the abstractly inclined: internally \lstinline{'a co} is
|
|
defined as \lstinline{raw_constraint * (solution -> 'a)} it is the
|
|
composition of a ``writer'' monad that generate constraints and
|
|
a ``reader'' monad consuming the solution. For the composition
|
|
$W \circ R$ of two arbitrary monads to itself be a monad, a sufficient
|
|
condition~\citep{composing-monads} is that they commute:
|
|
$(R \circ W) \to (W \circ R)$ -- this suffices to define
|
|
$\mathtt{join} : (W \circ R \circ W \circ R) \to W \circ R$ from the
|
|
$\mathtt{join}$ operation of $W$ and $R$. Pushing the reader below the
|
|
writer would require reading the solution before writing the
|
|
constraint; this is not possible if we want the final solution.
|
|
|
|
\section{Failed attempt: codensity} If we have to write in CPS style, a natural
|
|
idea is to program in the corresponding CPS \emph{monad}. We can define
|
|
\lstinline{('a, 'i) co_cps} which does have an (indexed) monadic
|
|
structure:
|
|
\begin{lstlisting}
|
|
type ('a, 'i) co_cps = { run : 'r . ('a -> 'r co) -> ('i * 'r) co }
|
|
val bind : ('a, 'i1) co_cps -> ('a -> ('b, 'i2) co_cps) -> ('b, 'i1 * 'i2) co_cps
|
|
val existM : (Infer.variable, F.ty) co_cps
|
|
\end{lstlisting}
|
|
|
|
This is an indexed version of the \emph{codensity} construction: for
|
|
any functor \lstinline{'a t}, the type\\ %
|
|
\lstinline{type 'a m = 'r. ('a -> 'r t) -> 'r t} has a monadic
|
|
structure. This lets us write \lstinline{exist} and our
|
|
\lstinline{traverse} above in monadic style, and then rely on our
|
|
usual monadic programming habits. Unfortunately, we found this too hard
|
|
to use in practice for the following reasons:
|
|
\begin{itemize}
|
|
|
|
\item \lstinline{exist} is a naturally \emph{scoped} construction:
|
|
besides generating a free variables, it builds a raw constraint term
|
|
of the form \lstinline{CExist(x,rc)}, where the inference variable
|
|
scopes over the constraint sub-term \lstinline{rc}.
|
|
|
|
A \lstinline{co_cps} computation builds a single ever-growing scope
|
|
(by composing continuations in scope of all quantifiers effect
|
|
performed so var). The scope is only ``closed'' when the computation
|
|
is ``run'' by invoking it with the identity continuation -- we can
|
|
define a \lstinline{close} combinator doing this.
|
|
|
|
Note that lifting all existential constraints to a toplevel, global
|
|
scope would be incorrect, due to the interplay with polymorphism /
|
|
generalization. We need to close the scope in a more local way.
|
|
|
|
In practice we found scoped programming with \lstinline{co_cps} less
|
|
natural than the syntactic/lexical scoping of the non-monadic
|
|
style. It makes it harder to reason about scopes, and it forces us
|
|
to write programs with hybrid usage of both \lstinline{co_cps}
|
|
(for quantifiers) and \lstinline{co} (for scope control).
|
|
|
|
\item As we have to use a mix of the two structures \lstinline{co} and
|
|
\lstinline{co_cps} in our programs, we have to duplicate all
|
|
operations to work in both of them, or to use lifting combinators
|
|
from one layer to the other. This makes program more difficult to
|
|
write and read.
|
|
\end{itemize}
|
|
|
|
\section{Our proposal: turning direct outputs into modal inputs}
|
|
|
|
We propose to define a type \lstinline{'a binder} for
|
|
quantifier-like definitions that resembles the \lstinline{co_cps}
|
|
construct, with two key differences in API design:
|
|
\begin{enumerate}
|
|
\item Instead of using a \lstinline{bind} operator whose return type
|
|
is in the \lstinline{co_cps} monad, use a composition combinator
|
|
whose return type is in the \lstinline{co} applicative. As long as
|
|
our return type remains a \lstinline{co}, we only need one set of
|
|
operations.
|
|
|
|
\item When quantifiers produce witnesses, instead of adding the
|
|
witness type \lstinline{w} to the \emph{output} type of the
|
|
continuation, have the continuation take a \emph{modal input} of
|
|
type \lstinline{w co}.
|
|
|
|
For example, we change \lstinline{exist} from the first to
|
|
the second type:
|
|
\begin{lstlisting}
|
|
val exist : (Infer.variable -> 'r co) -> (F.ty * 'r) co
|
|
val exist : (Infer.variable * F.ty co -> 'r co) -> 'r co
|
|
\end{lstlisting}
|
|
\end{enumerate}
|
|
|
|
Here \lstinline{co} acts as a modality indicating that the value will
|
|
only be available at solution time. (In particular, no constraint is
|
|
produced. We could use the writer/reader decomposition of
|
|
\lstinline{co} to take only the reader part, but that would force us
|
|
to work with two different layers again.)
|
|
|
|
More concretely, we use the following definitions:
|
|
\begin{lstlisting}
|
|
type ('a, 'r) binder = ('a -> 'r co) -> 'r co
|
|
val exist : (Infer.variable * F.ty co, 'r) binder
|
|
\end{lstlisting}
|
|
(Note: we would like to define \lstinline{'a binder} with a universal
|
|
quantification on \lstinline{'r}, but OCaml makes it painful due its
|
|
limited support for universal inference; in practice binders only
|
|
occur in positive positions in our APIs, so we can keep their second
|
|
parameter polymorphic in each combinator, as for \lstinline{exist}.)
|
|
|
|
This change in type only requires a simple change in the
|
|
implementation of the quantifier.
|
|
|
|
\subsection{Final API and example}
|
|
Our final API looks as follows (other combinators are unchanged from
|
|
our initial examples):
|
|
\begin{lstlisting}[columns=flexible]
|
|
type ('a, 'r) binder = ('a -> 'r co) -> 'r co
|
|
val exist : (Infer.variable * F.ty co, 'r) binder
|
|
|
|
val (let@) : ('a, 'r) binder -> ('a -> 'r co) -> 'r co (* identity *)
|
|
val (let+) : 'a co -> ('a -> 'b) -> 'b co (* map *)
|
|
val (and+) : 'a co -> 'b co -> ('a * 'b) co (* pair *)
|
|
\end{lstlisting}
|
|
|
|
\lstinline{('a, 'r) binder} is a type of ``general quantifiers'',
|
|
which introduce arguments of type \lstinline{'a} to build a constraint
|
|
in a delimited scope returning a \lstinline{'r co}. Only terms of this
|
|
type are written in continuation-passing style. They are used through
|
|
\lstinline{let@}, that opens a quantifier which naturally scopes until
|
|
the end of the lexical block. The binder syntax is a reference to
|
|
OCaml's infix application operator \lstinline{(@@)}: %
|
|
\lstinline{(let@ p = a in b)} is equivalent to %
|
|
\lstinline{(b @@ fun p -> a)}.
|
|
|
|
As a final example, let us revisit our ``tuple'' case with this new API.
|
|
(Left: before, Right: after)
|
|
|
|
\hspace{-3em}
|
|
\begin{minipage}{0.45\linewidth}
|
|
\begin{lstlisting}
|
|
| ML.Tuple us ->
|
|
let rec traverse
|
|
us (k : variable list -> 'r co)
|
|
: ((F.term * F.ty) list * 'r) co
|
|
= match us with
|
|
| [] ->
|
|
k [] <$$> fun r -> ([], r)
|
|
| u::us ->
|
|
exist (fun v ->
|
|
traverse us (fun vs ->
|
|
hastype u v ^&
|
|
k (v :: vs)
|
|
)) <$$> fun (ty, (u', (utys, rs))) ->
|
|
((u', ty) :: utys, r)
|
|
in
|
|
traverse us (fun vs ->
|
|
w --- Infer.product vs
|
|
) <$$> fun (utys, ()) ->
|
|
F.Tuple utys
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\hfill
|
|
\begin{minipage}{0.55\linewidth}
|
|
\begin{lstlisting}
|
|
| ML.Tuple us ->
|
|
let rec traverse (us : ML.term list)
|
|
: (variable list * (F.term * F.ty) list co,
|
|
'r) binder
|
|
= fun k -> match us with
|
|
| [] ->
|
|
k ([], pure [])
|
|
| u::us ->
|
|
let@ v, ty = exist in
|
|
let@ (vs, us') = traverse us in
|
|
k (v :: vs,
|
|
let+ u' = hastype u v
|
|
and+ ty = ty
|
|
and+ us' = us'
|
|
in (u', ty) :: us')
|
|
in
|
|
let@ (vs, utys) = traverse us in
|
|
let+ () = w --- Infer.product vs
|
|
and+ utys = utys
|
|
in F.Tuple utys
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
|
|
The CPS is now guided/documented by our types: to define
|
|
a \lstinline{(foo, 'r) binder} value we use CPS, and to use these binders
|
|
we use the \lstinline{let@} operators.
|
|
|
|
Modal inputs improve code readability by giving a name to an
|
|
expression at the logical place in the code (\lstinline{ty} and
|
|
\lstinline{us'} for example). Being in the applicative, a modal input
|
|
variable \lstinline{x} cannot be used as-is to build the witness, it
|
|
has to be ``unpacked'' in the final%
|
|
\lstinline{let+ .. and+ .. in <witness>} block by the odd construction
|
|
\lstinline{and+ x = x}.
|
|
%
|
|
This unpacking construction is surprising, and the redudancy is
|
|
frustrating (one could think of a proper modal-typing rule in the
|
|
compiler that would implicitly unpack modal inputs); but note that the
|
|
unpacking place corresponds to the place, in the previous style, where
|
|
the variable would have been first named. Unpacking is a fair price to
|
|
pay to recover readable code.
|
|
|
|
\subsection{Another example} To conclude, we would like to demonstrate
|
|
the generality of this approach to a wider family of applicative
|
|
functions with ``quantifier''-like combinators, by briefly showcasing
|
|
the same refactoring in a different applicative functor -- an
|
|
artificial example.
|
|
|
|
Consider an applicative \lstinline{'a cloud} for ``computations in the
|
|
cloud'', where operating on a resources (here a remote file) comes
|
|
with a ``cost'' (API usage, time, money, or something else) that is
|
|
returned to the programmer when they conclude using the resource. In
|
|
this model, opening a (remote) file for reading could be exposed by
|
|
the following quantifier-like operation:
|
|
|
|
\begin{lstlisting}
|
|
val with_input : path -> (in_channel -> 'r cloud) -> (cost * 'r) cloud
|
|
\end{lstlisting}
|
|
|
|
and now the user desires to generalize this into a function that opens
|
|
a list of paths, operates on the correspond list of input channels,
|
|
and returns the sum of all costs (we will assume that
|
|
\lstinline{type cost = int} for simplicity). They want to implement:
|
|
|
|
\begin{lstlisting}
|
|
val with_inputs : path list -> (in_channel list -> 'r cloud) -> (cost * 'r) cloud
|
|
\end{lstlisting}
|
|
|
|
and this is tricky. Our proposal, ``turning direct outputs into modal
|
|
inputs'', suggests the following change to the primitive operation,
|
|
introducing a \lstinline{binder} type that marks the place where
|
|
continuation-passing-style should be used:
|
|
|
|
\begin{lstlisting}
|
|
type ('a, 'r) binder = ('a -> 'r cloud) -> 'r cloud
|
|
val with_input : (in_channel * cost cloud, 'r) binder
|
|
\end{lstlisting}
|
|
|
|
An implementation of \lstinline{with_inputs} using the original API is on the left,
|
|
and our proposed approach is on the right.
|
|
|
|
\hspace{-3em}
|
|
\begin{minipage}{0.45\linewidth}
|
|
\begin{lstlisting}
|
|
let rec with_inputs paths k =
|
|
match paths with
|
|
| [] ->
|
|
let+ r = k [] in (0, r)
|
|
| p :: ps ->
|
|
let+ (cost_p, (cost_ps, r)) =
|
|
let@ chan = with_input p in
|
|
let@ chans = with_inputs pts in
|
|
k (chan :: chans)
|
|
in (cost_p + cost_ps, r)
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\hfill
|
|
\begin{minipage}{0.55\linewidth}
|
|
\begin{lstlisting}
|
|
let rec with_inputs paths =
|
|
fun k -> match paths with
|
|
| [] -> k ([], pure 0)
|
|
| p :: ps ->
|
|
let@ chan, cost_p = with_input p in
|
|
let@ chans, cost_ps = with_input ps in
|
|
let+ res = k (p :: ps)
|
|
and+ cost_p = cost_p
|
|
and+ cost_ps = cost_ps
|
|
in (cost_p + cost_ps, res)
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
|
|
Notice, again, that the original API pushes the cost ``on the stack'',
|
|
and that the variables \lstinline{cost_p}, \lstinline{cost_ps} are
|
|
bound far from the place that logically introduces them in the left
|
|
solution. The issue is solved on the right, at the cost of explicit
|
|
unpacking of the modal inputs.
|
|
|
|
\subsection*{Acknowledgments}
|
|
We wish to thank François Pottier and our anonymous reviewers for their feedback.
|
|
|
|
\bibliography{biblio}
|
|
\end{document}
|