asai-0.0.0.0: A minimal library for delimited continuations.

PortabilityRank-2 types required
Stabilityexperimental
Maintainermad.one@gmail.com
Safe HaskellSafe-Inferred

Control.Delimited

Contents

Description

Delimited continuations featuring answer-type polymorphism, via indexed monads.

There is a tutorial available in Control.Delimited.Tutorial.

Synopsis

Delimited continuations

data Delim s t b Source

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 Delim operation does not capture the computation using one of the various 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 reset is String. If the hole (specified by [...]) 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 reset. 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):

>>> 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 reset is 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.

Instances

IxFunctor Delim

Delimited continuations form an IxFunctor.

IxPointed Delim

Delimited continuations form an IxPointed.

IxApplicative Delim

Delimited continuations form an IxApplicative.

IxMonad Delim

Delimited continuations form an IxMonad.

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 reset is polymorphic in its answer type.

>>> 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, shift0 is the "most pure shift of all" in that the delimited computation is completely pure. We may then use shift0 to build the definition of shift1, and use shift1 to build the definition of shift2, and so on until shift3.

We may then use shift3, the "most impure shift of all", to walk back downards and define shift2, and use shift2 to describe shift1 and so on and so forth.

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 shift0 but more pure than 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 shift2, this uses rank-2 polymorphism to ensure that the continuation 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

callCC :: ((b -> Delim u s a) -> Delim s t b) -> Delim s t bSource

Traditional call/cc operator.

This is defined in terms of shift0 and Delim internally.

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