{-# LANGUAGE CPP, Rank2Types #-}
-----------------------------------------------------------------------------------------
-- |
-- Module : FRP.Yampa.Task
-- Copyright : (c) Antony Courtney and Henrik Nilsson, Yale University, 2003
-- License : BSD-style (see the LICENSE file in the distribution)
--
-- Maintainer : ivan.perez@keera.co.uk
-- Stability : provisional
-- Portability : non-portable (GHC extensions)
--
-- Task abstraction on top of signal transformers.
--
-----------------------------------------------------------------------------------------
module FRP.Yampa.Task (
Task,
mkTask, -- :: SF a (b, Event c) -> Task a b c
runTask, -- :: Task a b c -> SF a (Either b c) -- Might change.
runTask_, -- :: Task a b c -> SF a b
taskToSF, -- :: Task a b c -> SF a (b, Event c) -- Might change.
constT, -- :: b -> Task a b c
sleepT, -- :: Time -> b -> Task a b ()
snapT, -- :: Task a b a
timeOut, -- :: Task a b c -> Time -> Task a b (Maybe c)
abortWhen, -- :: Task a b c -> SF a (Event d) -> Task a b (Either c d)
) where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative (Applicative(..))
#endif
import FRP.Yampa
import FRP.Yampa.EventS (snap)
import FRP.Yampa.Diagnostics
infixl 0 `timeOut`, `abortWhen`
-- * The Task type
-- | A task is a partially SF that may terminate with a result.
-- CPS-based representation allowing a termination to be detected.
-- (Note the rank 2 polymorphic type!)
-- The representation can be changed if necessary, but the Monad laws
-- follow trivially in this case.
newtype Task a b c =
Task (forall d . (c -> SF a (Either b d)) -> SF a (Either b d))
unTask :: Task a b c -> ((c -> SF a (Either b d)) -> SF a (Either b d))
unTask (Task f) = f
-- | Creates a 'Task' from an SF that returns, as a second output, an 'Event'
-- when the SF terminates. See 'switch'.
mkTask :: SF a (b, Event c) -> Task a b c
mkTask st = Task (switch (st >>> first (arr Left)))
-- | Runs a task.
--
-- The output from the resulting signal transformer is tagged with Left while
-- the underlying task is running. Once the task has terminated, the output
-- goes constant with the value Right x, where x is the value of the
-- terminating event.
-- Check name.
runTask :: Task a b c -> SF a (Either b c)
runTask tk = (unTask tk) (constant . Right)
-- | Runs a task that never terminates.
--
-- The output becomes undefined once the underlying task has terminated.
--
-- Convenience function for tasks which are known not to terminate.
runTask_ :: Task a b c -> SF a b
runTask_ tk = runTask tk
>>> arr (either id (usrErr "AFRPTask" "runTask_"
"Task terminated!"))
-- | Creates an SF that represents an SF and produces an event
-- when the task terminates, and otherwise produces just an output.
-- Seems as if the following is convenient after all. Suitable name???
-- Maybe that implies a representation change for Tasks?
-- Law: mkTask (taskToSF task) = task (but not (quite) vice versa.)
taskToSF :: Task a b c -> SF a (b, Event c)
taskToSF tk = runTask tk
>>> (arr (either id (usrErr "AFRPTask" "runTask_"
"Task terminated!"))
&&& edgeBy isEdge (Left undefined))
where
isEdge (Left _) (Left _) = Nothing
isEdge (Left _) (Right c) = Just c
isEdge (Right _) (Right _) = Nothing
isEdge (Right _) (Left _) = Nothing
-- * Functor, Applicative and Monad instance
instance Functor (Task a b) where
fmap f tk = Task (\k -> unTask tk (k . f))
instance Applicative (Task a b) where
pure x = Task (\k -> k x)
f <*> v = Task (\k -> (unTask f) (\c -> unTask v (k . c)))
instance Monad (Task a b) where
tk >>= f = Task (\k -> unTask tk (\c -> unTask (f c) k))
return x = Task (\k -> k x)
{-
Let's check the monad laws:
t >>= return
= \k -> t (\c -> return c k)
= \k -> t (\c -> (\x -> \k -> k x) c k)
= \k -> t (\c -> (\x -> \k' -> k' x) c k)
= \k -> t (\c -> k c)
= \k -> t k
= t
QED
return x >>= f
= \k -> (return x) (\c -> f c k)
= \k -> (\k -> k x) (\c -> f c k)
= \k -> (\k' -> k' x) (\c -> f c k)
= \k -> (\c -> f c k) x
= \k -> f x k
= f x
QED
(t >>= f) >>= g
= \k -> (t >>= f) (\c -> g c k)
= \k -> (\k' -> t (\c' -> f c' k')) (\c -> g c k)
= \k -> t (\c' -> f c' (\c -> g c k))
= \k -> t (\c' -> (\x -> \k' -> f x (\c -> g c k')) c' k)
= \k -> t (\c' -> (\x -> f x >>= g) c' k)
= t >>= (\x -> f x >>= g)
QED
No surprises (obviously, since this is essentially just the CPS monad).
-}
-- * Basic tasks
-- | Non-terminating task with constant output b.
constT :: b -> Task a b c
constT b = mkTask (constant b &&& never)
-- | "Sleeps" for t seconds with constant output b.
sleepT :: Time -> b -> Task a b ()
sleepT t b = mkTask (constant b &&& after t ())
-- | Takes a "snapshot" of the input and terminates immediately with the input
-- value as the result.
--
-- No time passes; therefore, the following must hold:
--
-- @snapT >> snapT = snapT@
snapT :: Task a b a
snapT = mkTask (constant (intErr "AFRPTask" "snapT" "Bad switch?") &&& snap)
-- * Basic tasks combinators
-- | Impose a time out on a task.
timeOut :: Task a b c -> Time -> Task a b (Maybe c)
tk `timeOut` t = mkTask ((taskToSF tk &&& after t ()) >>> arr aux)
where
aux ((b, ec), et) = (b, (lMerge (fmap Just ec)
(fmap (const Nothing) et)))
-- | Run a "guarding" event source (SF a (Event b)) in parallel with a
-- (possibly non-terminating) task.
--
-- The task will be aborted at the first occurrence of the event source (if it
-- has not terminated itself before that).
--
-- Useful for separating sequencing and termination concerns. E.g. we can do
-- something "useful", but in parallel watch for a (exceptional) condition
-- which should terminate that activity, without having to check for that
-- condition explicitly during each and every phase of the activity.
--
-- Example: @tsk `abortWhen` lbp@
abortWhen :: Task a b c -> SF a (Event d) -> Task a b (Either c d)
tk `abortWhen` est = mkTask ((taskToSF tk &&& est) >>> arr aux)
where
aux ((b, ec), ed) = (b, (lMerge (fmap Left ec) (fmap Right ed)))