{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import HSXML_ext
import HSXML (h1)
main = runHTMLIO . run_root $ content
content =
standard_page
HeadAttrs {
ha_title = "Undelimited continuations are not functions",
ha_longtitle = "Undelimited continuations are co-values rather \
\than functions",
ha_description = "Undelimited continuations are often mis-represented \
\as functions, e.g., in Scheme. We explain the distinction \
\and describe the proper \
\undelimited continuation monad, which differs from \
\the one in Haskell's monad transformer libraries.",
ha_DateRevision = (24,October,2014),
ha_DateCreation = (14,February,2011),
ha_keywords = ["CPS", "undelimited continuation", "co-value",
"lambda-mu", "duality of computation"],
ha_links = HLinks
{
links_prev = FileURL "zipper.html",
links_next = FileURL "against-callcc.html",
links_up = FileURL "index.html",
links_other = []
}
}
(tdiv
(ul
(li (item'ref introduction))
(li (item'ref delim'undelim))
(li (item'ref contM))
(li (item'ref recursion'iteration))
(li (item'ref fix'callcc)
br nbsp)
(li (aref (FileURL "against-callcc.html")
"An argument against" (code "call/cc")))
)
hr
(describe introduction)
(describe delim'undelim)
(describe contM)
(describe recursion'iteration)
(describe fix'callcc)
)
introduction = DescriptionUnit "introduction"
(title "Undelimited continuations are not functions") $ tdu
(dbody
(tp[mup_text|
When one part of a program is being evaluated, producing the result,
the rest of the program lies ready to consume it. For example, in the program
|print 1 + 2*3|, the context |print []| is the consumer of the
result of the addition, and |print 1 + []| is the consumer for
the result of the multiplication. Whereas value is the meaning
of an expression, (undelimited) continuation is the meaning of a context.
Ever-present and implicit, continuations become explicit
when the program is converted to the
continuation-passing style (CPS) or when they are captured by a control
operator such as |call/cc|. Explicit continuations are often
represented as functions, which can be applied. One must keep
in mind however that an undelimited continuation is _not_ a genuine
function. It _does not return_, and hence cannot be meaningfully composed.|])
(p[mup_text|
We alert to the misconception of explicit, or first-class,
undelimited continuations as ordinary functions. The confusion is
wide-spread, for example, in the description and the code for the
|Cont| monad in Haskell's monad transformer libraries.
We argue for a better |call/cc| interface, to hopefully reduce
the bewilderment commonly associated with that control operator. We
describe the proper undelimited continuation monad, pointing out that
Haskell's |Cont| is actually the monad for _delimited_
continuations. Delimited and undelimited continuations are vastly
different. Delimited continuations are isomorphic to functions, they
do return the result and can be composed. We emphasize the great
help of types in understanding continuations and avoiding their surprises.|])
(p[mup_text|
Scheme's |call/cc| might be responsible for mistaking first-class
undelimited continuations as functions: the captured undelimited
continuation |k| in |(call/cc (lambda (k) ...))| indeed looks like
a function and can be applied to a value. It is not a genuine function
however. The expression |(k (k 1))|, albeit well-formed, is meaningless.
It makes as much sense to compose captured undelimited continuations
as to compose |abort| or |exit| `functions.' The |call/cc| interface
in SML is quite more helpful:|])
([mup_code|
type 'a cont
val callcc: ('a cont -> 'a) -> 'a
val throw: 'a cont -> 'a -> 'b|])
(tp[mup_text|
A captured continuation is represented as the value of an abstract type
|'a cont|. Since it is not a function, we need an operator, |throw|, to
pass the continuation a value to consume and so let it `continue.' The
type of |throw| is similar to the type of the exception-raising |raise|,
indicating that neither `function' returns.|])
(p[mup_text|
The untyped CPS transform might also make undelimited continuations
appear as functions. After all, they are represented as
functions -- in the metalanguage. One must clearly distinguish the
metalanguage, _in_ which the CPS code is written, from the object
language, _of_ which the CPS transform is taken.
Types make the distinction clear. An object-language expression
of the type |Int| has the CPS transform of the metalanguage
type |(Int->w)->w|, where |w| is the `answer-type'. An object-language
function, say, |Int->Bool|, is represented in CPS as the metalanguage
value of the type |(Bool->w) -> (Int->w)|. An object-level continuation
receiving the value |Int| has the metalanguage type |Int->w|.
The object-language continuation is represented as the metalanguage
function, whose type sets it apart from representations of
object-language functions.|])
(p[mup_text|
Distinguishing undelimited continuations and functions not only reduces
confusion but also gives a deep insight into the duality of
computation. A continuation is a co-value. Whereas a term, a producer,
has the type |t|, the context of the term -- the consumer, or the co-term --
can be given the type |NOT t|, isomorphic to |t -> Falsum|.
The answer-type |Falsum| has no constructors: Informally, it describes
the result of a crashed computer. The type of the
undelimited continuation |t -> Falsum|
clarifies that although the continuation may look like a function,
it never returns because there is no way to create a |Falsum| value.
In other words, the `result' of an undelimited continuation may never be
used by the program itself; it is `beyond the grave'.
Therefore, the value of the type |t -> Falsum| witnesses the
impossibility to produce a value of the type |t|. The type
|t -> Falsum| represents the proposition that is the negation
of that of |t|.|])
)
(references
(tp
"Pierre-Louis Curien and Hugo Herbelin: The duality of computation."
"ICFP 2000" br
(urlref (URL
"http://pauillac.inria.fr/~herbelin/publis/icfp-CuHer00-duality+errata.pdf"))
)
)
delim'undelim = DescriptionUnit "delim-vs-undelim"
(title "Vast difference between delimited and undelimited continuations")$ tdu
(dbody
(tp[mup_text|
Whereas an undelimited continuation is the meaning of the whole
context, a delimited continuation is the meaning of context's prefix,
mapping a context to a wider one. Despite sounding pretty much alike,
undelimited and delimited continuations are _qualitatively_
different. Since a delimited continuation is a mapping between
contexts, it may truly be a function, which returns and can be
composed. Undelimited continuations are not true
functions. Undelimited and delimited continuations also differ in
expressiveness, and this difference has been proven. First-class
_delimited_ continuations can express _any_ expressible computational
effect, including exceptions and mutable state. To be precise, any
effect that can be emulated by transforming the whole program (into
the state-passing--style, into an |Either|-returning style, etc.) can be
expressed with first-class delimited continuations without
the global transformation, see Filinski's ``Representing
Monads.'' In contrast, first-class undelimited continuations _cannot_
express raising and catching of exceptions. Furthermore, first-class
undelimited continuations, even when aided by exceptions, _cannot_
express mutable state. The limited expressiveness of undelimited
continuations has been proven by Hayo Thielecke, see the reference and
the abstract below.|])
(p[mup_text|
One sometimes hears a misleading statement that ``it is
possible to implement Scheme's |set!| in terms of Scheme's |call/cc|
and |letrec|.'' The statement mis-represents
Alan Bawden's observation that according to Scheme standards and common
practice, |letrec| is implemented in terms of |set!|; that hidden |set!|
can be `pried open' with |call/cc|. Of course |letrec| can be
implemented without |set!|, for example, via the fix-point combinator.
The inability of |call/cc| to express mutable state becomes clear
then. Section 4.4 of Friedman and Sabry's
``Recursion is a Computational Effect'' has the detailed explanation.|])
(p[mup_text|
One may wonder what is the use of first-class undelimited
continuations then. That is a good question. Many applications that
use |call/cc| also contain mutable cells holding continuations,
betraying the well-known Filinski's emulation of delimited control via
|call/cc| and a mutable cell. Undelimited continuations by themselves
are hardly practically useful. The best non-trivial application of
bare |call/cc| is along the lines of Nick Benton's ``Undoing Dynamic
Typing.'' The paper mentions at the end that in practice `undoing'
should be done within transaction boundaries. The latter calls for
delimited continuations. Given that first-class delimited
continuations -- often emulated via |call/cc| together with |set!| --
are strictly more expressive than undelimited ones, and most often used
in practice, one would think that |call/cc| should have been replaced
by a delimited control operator.|])
)
(references
(tp
"Andrzej Filinski: Representing Monads. POPL 1994." br
(urlref (URL
"http://www.diku.dk/~andrzej/papers/RM-abstract.html")))
(p
"Hayo Thielecke: Contrasting Exceptions and Continuations. 2001" br
(urlref (URL "http://www.cs.bham.ac.uk/~hxt/research/exncontjournal.pdf"))
br
[mup_text|
Abstract: ``Exceptions and first-class continuations are the most
powerful forms of control in programming languages. While both are a
form of non-local jumping, there is a fundamental difference between
them. We formalize this difference by proving a contextual equivalence
that holds in the presence of exceptions, but not continuations; and
conversely, we prove an equivalence that holds in the presence of
continuations, but not exceptions. By the same technique, we show that
exceptions and continuations together do not give rise to state, and
that exceptions and state do not give rise to continuations.''|])
(p
"Daniel P. Friedman and Amr Sabry: Recursion is a Computational Effect."
br
"Tech. Rep. TR546, Computer Science Department, Indiana University,"
"December 2000." br
(urlref (URL
"http://www.cs.indiana.edu/~sabry/papers/recursion-monads-tr.pdf"))
)
(p
"Nick Benton: Undoing Dynamic Typing. FLOPS 2008." br
(urlref (URL "http://research.microsoft.com/~nick/newtrans.pdf"))
)
)
{-
refer to Hayo's work: answer-type polymorphism and purity.
> The reason is indeed because of "effect polymorphic", i.e.
> there has to be an underlying parametricity theorem which tells you that
> the only truly effect polymorphic functions are the pure ones, for
> basically the same reason that the only function of type forall a.a->a
> is the identity. Do you know if such parametricity theorems have been
> proved?
I think it might be this work
@InProceedings{ thielecke-control,
author = "Hayo Thielecke",
title = "From Control Effects to Typed Continuation Passing",
pages = "139--149",
crossref = "popl2003",
url = "http://www.cs.bham.ac.uk/~hxt/research/effects.pdf",
abstract = "First-class continuations are a powerful computational
effect, allowing the programmer to express any form of jumping.
Types and effect systems can be used to reason about continuations,
both in the source language and in the target language of the
continuation-passing transform. In this paper, we establish the
connection between an effect system for first-class continuations and
typed versions of continuation-passing style. A region in the
effect system determines a local answer type for continuations,
such that the continuation transforms of pure expressions
are parametrically polymorphic in their answer types.
We use this polymorphism to derive transforms that make use of effect
information, in particular, a mixed linear\non-linear continuation-passing
transform, in which expressions without control effects are passed
their continuations linearly."
}
I have just noticed a recent paper
author = "Hayo Thielecke",
title = "Control Effects as a Modality",
journal = j_jfp,
year = 2009,
volume = 19,
number = 1,
pages = "17--26",
which I haven't even skimmed.
-}
contM = DescriptionUnit "proper-contM"
(title "Monads for undelimited continuations") $ tdu
(dbody
(tp[mup_text|
The proper implementation of _undelimited_ continuations is
surprisingly tricky. In particular, the |Cont| monad in Haskell's
monad transformer library has a couple of imperfections.
Our ideal monad for undelimited continuations: (i) spells out
in syntax and types of its operations that undelimited continuations
are not to be treated as functions; (ii) statically prevents
building or executing computations that break the undelimited continuation
abstraction and depend on the answer-type. We eventually develop
two implementations satisfying both requirements; the second one
is almost the same as |Cont| but with a crucial distinction of the
higher-rank type for |runCont|.|])
(p[mup_text|
The |Cont| monad in the popular Haskell monad transformer library
does not satisfy the requirements. The captured continuation has the
type of a monadic function. The |Cont| documentation (through the
current version 2.0.1.0) confirms that view, saying ``Computations are
built up from sequences of nested continuations, terminated by a
final continuation (often id) which produces the final result.
Since continuations are functions which represent the future of
a computation...'' We have already argued that
undelimited continuations are not functions and that one should
forget about the ``final result.'' It behoves for a typed language
to statically enforce the forgetting. Incidentally, in the presence
of first-class undelimited continuations, computations can no longer be
viewed as ``sequences of nested continuations.'' As Peter Landin
said, ``You can enter a room once, and yet leave it twice.''
Our code will show a simple example.|])
(p[mup_text|
Recall that monad is a technique of embedding an impure, object language,
into a pure functional (meta)language. For an object language
with control effects, the well-known embedding technique is
continuation-passing style (CPS) --
which is what all our monads implement. In CPS, an object
language expression of the base object type |a| is represented
as a Haskell value of the type |(a -> w) -> w| where |w|
is a so-called `answer-type', to be discussed shortly.
An object-language |a->b| function is represented as Haskell's
|a -> ((b->w) -> w)| function. An object-language co-value of
the type |a|, that is, the continuation consuming |a| value
is represented in Haskell
as the value of the type |a->w|. We stress
that although the representation of an object language
continuation is a Haskell function, it differs, in type, from
the representation of any object language function. CPS operations
for building primitive computations, extending them, capturing an
undelimited continuation and invoking it have the following form:|])
([mup_code|
return x = \k -> k x
m >>= f = \k -> m (\v -> f v k)
callCC f = \k -> f k k
throw k x = \_ -> k x|])
(p[mup_text|
All our monads share the above code, modulo |newtype|-induced
conversions. The monads differ in their treatment of the answer-type |w|.
An object-language computation cannot know what |w| is; to it, |w|
is arbitrary. This fact gives us the first implementation, with the following
types for monadic values and continuations:|])
([mup_code|
newtype CPS1 a = CPS1{unCPS1:: forall w. (a -> w) -> w}
type K1 a = forall w. a -> w|])
(tp[mup_text|
Alas, we cannot write |callCC|:|])
([mup_code|
callCC1 :: (K1 a -> CPS1 a) -> CPS1 a
callCC1 f = CPS1 $ \k -> unCPS1 (f k) k|])
(tp[mup_text|
The type-checker keeps complaining that
the code for |callCC| identifies |w| quantified in the type of |CPS1|
with |w| quantified in the type of |K1|. The type-checker has the point.
The answer-type is the type of the value of the crashed computer; the program
cannot make any use of that value, or care of that type. Ostensibly that
is what the types |CPS1| and |K1| say, that the answer-type
is arbitrary. It cannot be totally arbitrary however: the continuation
captured by |callCC| leads to the same crashed computer value as the
computation whose continuation has been captured. The code for |callCC1|
says that the answer-type
of the captured |K1| must be the same as that of |CPS1|. The signature of
|callCC1| has no way to express that constraint.|])
(p[mup_text|
A different way to introduce a type whose values cannot be used
is to define an algebraic data type with no constructors -- an empty
type (disregarding bottom). Monadic values and continuations get the
following types:|])
([mup_code|
data Falsum -- no constructors
newtype CPS2 a = CPS2{unCPS2:: (a -> Falsum) -> Falsum}
type K2 a = a -> Falsum|])
(tp[mup_text|
with indeed the same answer-type, |Falsum|.
Even the program knows that, but it cannot get its values since none exist.
The type-checker now accepts the definitions of all four operations; we can
write any computation with undelimited control effects. Alas, we cannot
execute these computations. To execute a |CPS2 a| computation,
we need the initial continuation |a -> Falsum|.
We cannot construct values of that type: |Falsum| really has no constructors.
The initial continuation (whose invocation leads to the crashed computer,
eventually) should be supplied by the operating or the run-time system.
GHC RTS does supply it.|])
(p[mup_text|
Cheating is always an option. We can run the computation and obtain
its result through a side-channel. Since the side-channel is an effect,
we need another monad:|])
([mup_code|
newtype CPS3 m a = CPS3{unCPS3:: (a -> m Falsum) -> m Falsum}
type K3 m a = a -> m Falsum|])
(tp[mup_text|
The following code of the |run| operation instantiates |m|
to |ST s|, using a mutable cell to stash away the result of the computation.
The code exhibits lots of partiality, which is inherent, betraying the
essential cheating. Instead of |ST s|, we could have used the Error
monad |Either a|.|])
([mup_code|
runCPS3 :: (forall m. CPS3 m a) -> a
runCPS3 m = runST (do
res <- newSTRef (error "Continuation has escaped!")
unCPS3 m (\v -> writeSTRef res v >> return undefined)
readSTRef res)|])
(tp[mup_text|
We stress the higher-rank type of |runCPS3|: we introduced the monad
|m| only for the sake of |runCPS3|. The rest of the monadic computation
should stay away from it. The quantification over |m| keeps the program
away from using |m| in any way whatsoever.|])
(p[mup_text|
The implementation |CPS3| looks suspiciously like |Cont| in the
monad transformer library MTL, with the side-channel monad |m| playing
the role of the answer-type |w|. Neither should be looked at
by computations. We obtain the final implementation|])
([mup_code|
newtype Cont w a = Cont{unCont:: (a -> w) -> w}
type K w a = a -> w
callCC :: (K w a -> Cont w a) -> Cont w a
callCC f = Cont $ \k -> unCont (f k) k
throw :: K w a -> a -> Cont w b
throw k x = Cont $ \ k_ignored -> k x
runCont :: (forall w. Cont w a) -> a
runCont m = unCont m id|])
(p[mup_text|
The first difference from the monad-transformer library is the type
of the captured continuation, |K w a|, corresponding to no object
function. Therefore, we need a special operation to invoke the
continuation: |throw|. Our implementation thus emphasizes in syntax
and in types the difference between undelimited continuations and
functions. The second, main difference is the higher-rank type of
|runCont|, ensuring that computations really not have looked at the
answer-type. The answer-type is fixed all throughout, yet a
computation cannot know what it is. Therefore, we may chose |w| as we
wish -- and we do, after the program has been constructed. The
higher-rank type of |runCont| hence prevents the program from using
|runCont| internally to run parts of its computation. No such
restriction exists for MTL's |runCont|, telling us that the latter is
the monad for _delimited_ continuations.|])
)
(version "February 2011")
(references
(tp
(file'ref "CPSs.hs")
"Complete Haskell code for the four implementations of the"
"monad of undelimited continuations. The code includes several"
"examples."))
recursion'iteration = DescriptionUnit "iter-recur"
(title "Recursion from Iteration") $ tdu
(dbody
(tp[mup_text|
Let us call, as Filinski did, the looping computation _iteration_ and identify
the least fix-point combinator with _recursion_:|])
([mup_code|
loop :: Monad m => (a -> m a) -> (a -> m b)
loop g = \x -> g x >>= loop g
fix :: Monad m => ((a -> m b) -> (a -> m b)) -> (a -> m b)
fix f = \x -> f (fix f) x|])
(tp[mup_text|
We wrote the monadic version of |loop| so that we can get out of it
(e.g., by throwing an exception). According to its type the above
|fix| is the applicative fix-point combinator (like the one
typically used in strict languages), to be distinguished from the very
different, lazy |mfix| of Haskell. It is trivial to write |loop| in
terms of |fix|. Can we do the other way around?|])
(p[mup_text|
Intuitively, that should be possible: after all, any recursive
program, however general, is going to be executed on a CPU, which runs
in a simple tail-recursive loop. The details are very tricky and the
final result is all but intuitive or comprehensible. One wonders how
it could ever work. Andrzej Filinski was the first to systematically
derive recursion from iteration, in the paper presented at the
Continuation Workshop 1992. The journal version developed the example
further, making it an excellent illustration for the duality of
computation. We show an alternative, shorter derivation -- in Haskell
rather than ML, relying on monads and demonstrating forward and
backward equational reasoning. The example also shows off the Haskell
library of proper undelimited continuations developed above.
We solve the problem by equationally deriving a constructive proof of
the proposition|])
([mup_code|
forall f x k. exists h y. fix f x k = loop h y k|])
(tp[mup_text|
stating that |fix| is expressible via |loop| in the continuation
monad.|])
(p[mup_text|
Let's first explore our intuition. If we take for a moment |g . f| to mean
the monadic composition |\x -> f x >>= g| then after unrolling the definitions
we find that|])
([mup_code|
loop1 g = ... g . g . g . g
fix1 f = f $ f $ f $ f ...|])
(tp[mup_text|
which is the first hint of their duality. The CPU analogy gives us the
second hint: to run a recursive program on bare metal we need to
explicitly deal with the stack, saving and restoring return addresses.
The stack with the saved return addresses is one realization of
undelimited continuations.|])
(p[mup_text|
As a warm-up, let us look at simple examples of using our |fix| and
|loop|. To show off |fix|, we define the factorial with open recursion
in an arbitrary monad:|])
([mup_code|
fact2 :: Monad m => (Int -> m Int) -> (Int -> m Int)
fact2 self n = if n == 0 then return 1 else liftM (n *) (self (n-1))
fact2r = fix fact2 5 >>= print|])
(tp[mup_text|
We tie the knot and run it in the |IO| monad, printing the result.|])
(p[mup_text|
Iteration, that is, |loop|, is easily expressed in terms of the more general
recursion:|])
([mup_code|
loop' g = fix (\s x -> g x >>= s)|])
(tp[mup_text|
The proof is simple equational reasoning:|])
([mup_code|
loop' g
= fix (\s x -> g x >>= s) {- definition of loop' -}
= (\s x -> g x >>= s) (fix (\s x -> g x >>= s)) {- definition of fix -}
= (\s x -> g x >>= s) (loop' g) {- definition of loop' -}
= \x -> g x >>= (loop' g) {- beta-reduction -}
= loop g {- definition of loop -}
|])
(p[mup_text|
We now search for an expression of fix in terms of loop.
We will be using a monad of undelimited continuations:|])
([mup_code|
newtype CPS3 m a = CPS3{unCPS3:: (a -> m Falsum) -> m Falsum}|])
(tp[mup_text|
For clarity, we will drop |CPS3| and |unCPS3| conversions,
which operationally the identity anyway. The monad operations take the
following form then:|])
([mup_code|
return x = \k -> k x
m >>= f = \k -> m (\v -> f v k)
callCC f = \k -> f k k
throw k x = \_ -> k x|])
(tp[mup_text|
We solve the problem by equationally deriving a constructive proof
of the proposition|])
([mup_code|
forall f x k. exists h y. fix f x k = loop h y k|])
(tp[mup_text|
stating that |fix| is expressible via |loop| in the continuation monad.
Skolemizing, we obtain|])
([mup_code|
fix f x k = loop (h f x k) (y f x k) k|])
(tp[mup_text|
We need to find such functions |h| and |y| that the equality above
holds for the arbitrary |f|, |x|, and |k|.|])
(p[mup_text|
Unrolling |fix| and |loop| and inlining the definition of |(>>=)| from
the above, gives further equalities|])
([mup_code|
f (fix f) x k = (h f x k) (y f x k) (\v -> loop (h f x k) v k)
f (\x1 k1 -> loop (h f x1 k1) (y f x1 k1) k1) x k
= (h f x k) (y f x k) (\v -> loop (h f x k) v k)|])
(tp[mup_text|
We have to match |(\x1 k1 -> loop ...)| on the left-hand-side
with |(\v -> loop (h f x k) v k)| on the right-hand-side.
The first function receives two independent pieces of data,
so the second should too. Thus |v| must be a pair, |(x,k)|;
the invoking function |h| will uncurry. The hint
that loop's argument, the `state' to be repeatedly
transformed by the loop function, must be a pair |(x,k)|
comes also from the low-level point of view.
Recall that |x| is the argument of the recursive
function, and |k| is its continuation, the stack with the
return address on it. Together, |(x,k)| is the stack frame:
the argument plus the return address. A processor running in a loop
is indeed a frame transformer.|])
(p[mup_text|
Letting |v| be a pair gives us|])
([mup_code|
f (\x1 k1 -> loop (h f x1 k1) (y f x1 k1) k1) x k
= (h f x k) (y f x k) (\(x1,k1) -> loop (h f x k) (x1,k1) k)|])
(tp[mup_text|
To match the two loop expressions in parentheses, we have to define|])
([mup_code|
y f x k = (x,k)
h f x k = h' f|])
(tp[mup_text|
that is, |h| should not depend of |x| and |k| arguments; |h'| is
to be determined. Substituting these definitions, we obtain|])
([mup_code|
f (\x1 k1 -> loop (h' f) (x1,k1) k1) x k
= (h' f) (x,k) (\ (x1,k1) -> loop (h' f) (x1,k1) k)|])
(tp[mup_text|
We need an auxiliary lemma:|])
([mup_code|
loop g x k = loop g x k1|])
(tp[mup_text|
for any |g|, |x|, |k|, |k1|. In other words, |loop| never invokes
its continuation and therefore can be given any: |loop| loops and
never returns. We can easily prove the lemma by co-induction.
Applying the lemma gives|])
([mup_code|
f (\x1 k1 -> loop (h' f) (x1,k1) k1) x k
= (h' f) (x,k) (\ (x1,k1) -> loop (h' f) (x1,k1) k1)|])
(tp[mup_text|
which can be read (after flipping) as the definition for the
sought |h'|:|])
([mup_code|
h' f (x,k) k2 = f (\x1 k1 -> k2 (x1,k1)) x k|])
(p[mup_text|
We are done. We would like to polish the result: during the derivation,
we broke the monadic abstraction, replacing bind and return
of the CPS monad with their concrete expressions. We want to get
the abstraction back, re-introducing |(>>=)| and |return|.
They won't be enough, as we can see by comparing the pattern
of using |k| in these operations with the pattern in |h'|.
Our |h'| not just passes the received continuation around or immediately
invokes it. It also stores continuations in
data structures (pairs). Our resulting expression:|])
([mup_code|
fix f x k = loop (h' f) (x,k) k|])
(tp[mup_text|
can be re-written, using the definition of |callCC| from above, as|])
([mup_code|
fix :: ((a -> CPS3 m b) -> (a -> CPS3 m b)) -> (a -> CPS3 m b)
fix f x = callCC (\k -> loop (h' f) (x,k))|])
(tp[mup_text|
The definition of |h'| can be likewise transformed in these steps:|])
([mup_code|
h' f (x,k) k2 = f (\x1 k1 -> k2 (x1,k1)) x k
h' f (x,k) k2 = f (\x1 -> callCC (\k1 -> throw k2 (x1,k1))) x (\v -> k v)|])
(tp[mup_text|
giving us the final result:|])
([mup_code|
h' :: ((a -> CPS3 m w) -> (b -> CPS3 m w))
-> ((b, K3 m w) -> CPS3 m (a, K3 m w))
h' f (x,k) = callCC (\k2 ->
f (\x1 -> callCC (\k1 -> throw k2 (x1,k1))) x >>= (\v -> throw k v))|])
(tp[mup_text|
The result is achieved by equational reasoning and is correct by construction.
It has indeed worked the first time.|])
(p[mup_text|
The type of |h'| makes _classical_ logical sense, expressing the proposition|])
([mup_code|
(f1 -> f2) = (NOT f1 -> NOTNOTNOT f2)|])
(tp[mup_text|
keeping in mind that |K3 m w| is logically |NOT w|, reading |CPS3 m|
as double-negation, and using|])
([mup_code|
K3 m (b -> CPS3 m w)
~ NOT (b -> NOTNOT w)
= NOT (NOT b OR (NOTNOT w)) = b AND (NOT w)
~ (b,K m w)|])
(p[mup_text|
What is left to prove is the fix-point thus obtained being the least one,
or the related property of universality. See Filinski's paper for details.|])
-- The monadic approach does help: the premise of the proof is that the
-- function |f| whose fix-point we seek, is rigid??
-- This property is equivalent?? to saying that although |f| has the
-- monadic type, it is pure. One can see why it matters: if |f| is allowed
-- mutations, it can easily distinguish a fix-point combinator that relies
-- on unrolling (such as ours) from the fix-point combinator relying on sharing
-- (which is typical of combinators implemented via built-in value recursion).
-- However, if we require |f| to be polymorphic over the monad, then
-- |f| is effectively pure. It cannot use any monadic operations but
-- |return| and |bind|.
-- We need delimited continuations so to obtain that even a pure
-- function using fix is pure. So, to use parametricity for
-- the proof we need delimited control!
(p[mup_text|
The original version of this article was written in early 2003, in
Scheme, after the independent re-discovery of recursion via iteration.
Unlike Filinski's, our derivation was directly in terms of |call/cc|
rather than the |C| operator.
The main hint probably came from the CPU point of view. The original
version too relied on equational reasoning. The present version
uses Haskell, which permits equational reasoning on the code itself.
We no longer have to translate Math into code; Math is code. The monadic style
also gives a shorter derivation.|])
)
(version "(original) February 2003; (monadic) February 2011")
(references
(tp
"Andrzej Filinski: Recursion from Iteration" br
"Lisp and Symbolic Computation, vol. 7, no.1, pp. 11-38 (January 1994)" br
(urlref (URL
"http://www.diku.dk/hjemmesider/ansatte/andrzej/papers/RfI.ps.gz"))
)
(p
(file'ref "IterRec.hs")
"Complete Haskell code for all the fragments mentioned in the article."
"The code contains several more examples, of nested and higher-order"
"fix-points, mentioned in Filinski's paper.")
(p
(item'ref fix'callcc) br
"In Scheme, we can obtain general recursion from" (code "call/cc")
"alone, without any loop. Types are again revealing, that"
"the application of" (code "call/cc") "to the identity has the recursive"
"type.")
(p
(aref (FileURL "../Computation/fixed-point-combinators.html")
"Many faces of the fixed-point combinator") br
"The web page points to several ways of defining the fix-point combinator"
"in OCaml and Haskell without value recursion or iteration.")
)
-- type: double-neg elimination (p31)
-- Looping in the direct-style corresponds to the fix-point in CPS.
-- (p32 and earlier)
fix'callcc = DescriptionUnit "fix-callcc"
(title "Self-application as the fixpoint of" (code "call/cc")) $ tdu
(dbody
(tp[mup_text|
Truly |call/cc| and the fix-point combinator |Y| are two sides of the
same coin. The following article makes the claims formal and proves
them. Two examples in the article demonstrate that first-class
continuations can be really tricky, and so being formal helps. For
proofs we rely on syntax-rules macros: one macro does a
continuation-passing-style (CPS) transform, and another simple macro
-- which is actually a lambda-calculator -- normalizes the result of
the transform.|])
(p "Theorem: The following expressions are observationally"
"equivalent in call-by-value untyped lambda-calculus.")
(ul
(li (code "((call/cc ... (call/cc call/cc)) p)"))
(li (code "((call/cc ... (call/cc (call/cc id))) p)"))
(li (code "((lambda (x) (x x)) p)")))
(tp[mup_text|
Here |p| is a value; |(call/cc ...)| signify zero or more
applications of |call/cc|, and |id| is the identity function. The
article also discusses self-applications of delimited continuation
operators.|])
(p[mup_text|
In this article, continuations, fixpoints, CPS, higher-order syntax,
syntax-rule macros and lambda-calculators all come together.|])
)
(references
(tp
(aref (FileURL "../Scheme/callcc-calc-page.html")
"Normal-order direct-style beta-evaluator with syntax-rules,"
"and the repeated applications of call/cc"))
(p
(file'ref "../Scheme/callcc-fixpoint.txt")
"Self-application as the fixpoint of call/cc" br
"The first version of the article, posted on the newsgroup"
(code "comp.lang.scheme")
"on Thu, 18 Sep 2003 23:39:02 -0700")
)
-- LocalWords: Undelimited undelimited SML callcc Bool Falsum Filinski's
-- LocalWords: Monads proven letrec Filinski parametricity Landin callCC
-- LocalWords: monads newtype forall unCPS runCont Andrzej equational