ion-1.0.0.0: src/Ivory/Language/Ion/CPS.hs
{- |
Module: CPS
Description: Ion types for continuations & continuation-passing style
Copyright: (c) 2015 Chris Hodapp
-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
module Ivory.Language.Ion.CPS where
import Ivory.Language
import Ivory.Language.Ion.Base
import Ivory.Language.Ion.Operators
-- | This wraps a pattern of functions calling each other in
-- continuation-passing style. The intent is that the returned entry
-- function (which takes arguments 'a') causes the supplied
-- continuation function to be called (passing arguments 'b').
--
-- This is a common pattern for asynchronous calls, for instance, in
-- which the callback or interrupt calls the continuation function.
--
-- Multiple calls of this sort can be composed with '(=<<)' (and with
-- @RecursiveDo@ and 'mdo') to chain them in the order in which they
-- would proceed.
--
-- For instance, in @start <- call1 =<< call2 =<< call3 final@,
-- @start@ contains the entry function to @call1@, whose continuation
-- is set to the entry function of @call2@, whose continuation in turn
-- is set to the entry function of @call3@, whose continuation is
-- 'final'. Note that chaining these with '(>>=)' is possible too,
-- but the order is somewhat reversed from what is logical - hence,
-- 'mdo' often being sensible here.
type IonCont a b = Def (b ':-> ()) -- ^ Continuation function
-> Ion (Def (a ':-> ())) -- ^ Entry function
-- | 'Lift' a Haskell function up into an 'IonCont'.
lift :: (IvoryType a, IvoryVar a, IvoryType b, IvoryVar b) =>
(a -> b) -> IonCont '[a] '[b]
lift f cont = newProc $ \a -> body $ call_ cont $ f a
-- | 'Accumulate' an argument into a continuation function.
-- Specifically: Given an 'IonCont' taking some argument in its entry
-- function, generate another 'IonCont' with the same type of entry
-- function, but whose continuation function contains another argument
-- (which will receive the same value of that argument).
--
-- Note that every use of this requires a static variable of type 'a'.
-- Also, this implementation does not protect against the continuation
-- function being called without the entry function; if this occurs,
-- the continuation will contain old values of 'a' from earlier
-- invocations, or possibly a zero value.
--
-- TODO: Right now this handles only converting single-argument to
-- double-argument. I intend to modify this to work similarly to
-- 'call' and 'callAux' in Ivory.
accum :: (IvoryType a, IvoryVar a, IvoryStore a, IvoryZeroVal a,
IvoryType b, IvoryVar b) =>
IonCont '[] '[b] -> IonCont '[a] (a ': '[b])
accum f_ab cont = do
-- Temporary variable to hold 'a' while waiting to be called back:
tempA <- newArea Nothing
-- Generate a new continuation which calls the continuation with the
-- temporary 'a' value:
cont2 <- newProc $ \b -> body $ do
a <- deref tempA
call_ cont a b
-- 'entry2' is the entry function using 'cont2' as the continuation:
entry2 <- f_ab cont2
-- And finally, the new entry function:
entry <- newProc $ \a -> body $ do
store tempA a
call_ entry2
return entry
-- Another function that will be much more difficult to implement:
join :: (a -> b -> c) -> IonCont t '[a] -> IonCont t '[b] -> IonCont t '[c]
join _ _ _ = undefined
-- This would implement a 'join point' of sorts. The returned IonCont
-- would not call its own continuation until the other two continuations
-- (those of the first two IonCont arguments) have been called. The entry
-- function should call that of both of the arguments.