Portability | Rank-2 types required |
---|---|
Stability | experimental |
Maintainer | mad.one@gmail.com |
Safe Haskell | Safe-Inferred |
Delimited continuations featuring answer-type polymorphism, via indexed monads.
There is a tutorial available in Control.Delimited.Tutorial.
- data Delim s t b
- type :~> a b = forall s. a -> Delim s s b
- type :=> a b = forall s. Delim s s a -> Delim s s b
- reset :: Delim s t s :~> t
- shift0 :: ((b -> s) -> t) -> Delim s t b
- shift1 :: ((b -> s) -> Delim a t a) -> Delim s t b
- shift2 :: ((b :~> s) -> Delim a t a) -> Delim s t b
- shift3 :: ((b :=> s) -> Delim a t a) -> Delim s t b
- callCC :: ((b -> Delim u s a) -> Delim s t b) -> Delim s t b
- runDelim :: Delim t t t -> t
- module Data.Indexed.Functor
- module Control.Indexed.Applicative
- module Control.Indexed.Monad
Delimited continuations
The type of a delimited continuation, which is answer-type polymorphic.
Functions of type a ->
Delim
s t b
can be thought of as
functions of type a / s -> b / t
, which means given an a
we
return b
, changing the answer type of the delimited computation
from s
to t
. The take away from this is that the s
and t
variables representing the input and output answer type,
respectively. (This notation is used in both OchaCaml and the
definition of Asai's lambda/shift calculus.)
If a
operation does not capture the computation using one
of the various Delim
shift
operators (or shift
does not change the
answer type,) then the term is polymorphic in the answer type.
Consider a term like:
reset
$ [...]!>>=
\r ->returnI
(r ++ " world")
Here, the answer type of the enclosing
is reset
. If
the hole (specified by String
[...]
) does not have a shift
operator,
or the shift
operator does not change the answer type, then the
answer type of the hole is polymorphic: the hole cannot change the
answer type of the enclosing reset
.
To make this clear, consider using a shift
operator to deliver
the delimited continuation to the outer
. Doing this means
you cannot modify the answer type: the returned continuation may
only return a value whose type is that of the enclosing reset
(because all it can do is fill in the hole):
reset
>>>
let f :: Int -> Int; f = (+1)
>>>
let k = runDelim $ reset (shift2 returnI !>>= returnI . f)
>>>
:t k
k :: Int -> Delim s s Int>>>
runDelim (k 5)
6
Here, the return type of the overall
is reset
Int
. Note how
the quantified variable s
is both the input and output answer
type of k
, and the input and return are fixed to Int
: thus, k
cannot change the answer type of
.
reset
type :~> a b = forall s. a -> Delim s s bSource
This is a simple alias for a delimited continuation which is
polymorphic in its answer type. This corresponds directly to the
definition of
.
shift2
type :=> a b = forall s. Delim s s a -> Delim s s bSource
This is a simple alias for a delimited continuation which is
polymorphic in its answer type, but where the continuation is
monadic even in the input. This corresponds directly to the
definition of
.
shift3
reset :: Delim s t s :~> tSource
Delimit a computation. The type variable a
indicates that
is polymorphic in its answer type.
reset
>>>
runDelim (reset $ returnI "hello")
"hello"
>>>
runDelim (returnI 5)
5
A family of shift operators
There exists not one, but a family of equivalent shift
operators
for delimited continuations, which range in the purity of their
constituent continuation parameters and output types, from most pure
to most impure. This family of operators can be used to define each
other in a stepwise manner.
Briefly,
is the "most pure shift0
shift
of all" in that the
delimited computation is completely pure. We may then use shift0
to
build the definition of
, and use shift1
to build the
definition of shift1
, and so on until shift2
.
shift3
We may then use
, the "most impure shift3
shift
of all", to
walk back downards and define
, and use shift2
to
describe shift2
and so on and so forth.
shift1
We offer the full family of shift
operators here, and where
appropriate we use rank-2 typing to ensure the answer types of a
computation are polymorphic (strictly speaking this likely isn't
necessary, but it makes the type signature far more clear and is
fairly non-controversial.)
(Many thanks go to Leon P Smith who showed me notes describing this
family and 'zipper' of shift
operators.)
shift0 :: ((b -> s) -> t) -> Delim s t bSource
Clear the current continuation and invoke our handler with it bound as a parameter.
This is the most pure definition of shift
: both the continuation
k
and the enclosed body are pure, and may be composed with
.
.
This is defined in terms of
.
Delim
>>>
runDelim $ reset $ shift0 (const "hello") !>>= returnI . (+1)
"hello"
>>>
runDelim $ reset $ shift0 (\k -> (k . k) 3) !>>= returnI . (+1)
5
shift1 :: ((b -> s) -> Delim a t a) -> Delim s t bSource
Clear the current continuation and invoke our handler with it bound as a parameter.
This definition of shift
prohibits the continuation k
from
being monadic, while allowing the body to be monadic (this means
k
is trivially polymorphic in its answer type, while remaining
Haskell98.) For this reason it is less pure than
but more
pure than shift0
.
shift2
This is defined in terms of
.
shift0
>>>
runDelim $ reset $ shift1 (const $ returnI "hello") !>>= returnI . (+1)
"hello"
>>>
runDelim $ reset $ shift1 (\k -> returnI $ (k . k) 3) !>>= returnI . (+1)
5
shift2 :: ((b :~> s) -> Delim a t a) -> Delim s t bSource
Clear the current continuation and invoke our handler with it bound as a parameter.
This definition of shift
is the most "genuine" as it perfectly
encapsulates the typing rules of Asai's lambda/shift calculus using
rank-2 polymorphism: the continuation's answer type is fully
polymorphic.
This is defined in terms of
.
shift1
>>>
runDelim $ reset $ shift2 (const $ returnI "hello") !>>= returnI . (+1)
"hello"
>>>
runDelim $ reset $ shift2 (\k -> (k !>=> k) 3) !>>= returnI . (+1)
5
shift3 :: ((b :=> s) -> Delim a t a) -> Delim s t bSource
Clear the current continuation and invoke our handler with it bound as a parameter.
This is the most impure definition of shift
in that all
components of the delimited computation are monadic. It is akin to
the definition of withSubCont
in Amr Sabry's paper "A Monadic
Framework for Delimited Continuations", available in the
CC-delcont
package.
Like
, this uses rank-2 polymorphism to ensure that the
continuation shift2
k
is polymorphic in its answer type.
This is defined in terms of
.
shift2
>>>
runDelim $ reset $ shift3 (const $ returnI "hello") !>>= returnI . (+1)
"hello"
>>>
runDelim $ reset $ shift3 (\k -> k $ returnI 3) !>>= returnI . (+1)
4
Traditional call/cc
Executing delimited computations
runDelim :: Delim t t t -> tSource
Run a delimited computation.
>>>
runDelim (reset $ returnI "hello")
"hello"
>>>
runDelim (returnI 5)
5
Re-exports for convenience
module Data.Indexed.Functor
module Control.Indexed.Applicative
module Control.Indexed.Monad