{- | 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.