-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Pure deterministic scheduled computations -- -- Schedule computations to run later, in a pure and deterministic way. -- -- This library is a pure alternative to System.Timeout suitable -- for IO-bound non-blocking computations. System.Timeout has a -- few issues that are at-odds with a Haskell or purely functional -- paradigm: (1) it is not deterministic, (2) the timeout state is not -- serialisable, and (3) the timeout functionality must be shared between -- unrelated components, making it harder to design components that are -- easily decomposable and reusable. -- -- This library solves these issues by implementing all schedule and -- timeout logic as a pure deterministic computation, with callbacks -- represented in defunctionalised serialisable form. The interface with -- the runtime execution environment is minimal: a simple source of clock -- inputs similar to other inputs such as network traffic or user -- commands, which can either be an IO-based impure "real" runtime, or a -- pure "mock" one e.g. that replays previous inputs to reproduce -- previous outputs. -- -- This library does no pre-emption e.g. by sending interrupts or -- asynchronous exceptions, so it is probably not suitable for -- blocking computations. To be clear, things will work, but clock -- inputs will be delivered only after the blocking is over. A workaround -- is to separate the blocking computations from your main computation, -- arrange to have these run externally (e.g. in worker threads) with the -- results being sent back to your main computation via some pure -- abstract input interface, similar to how we deliver clock inputs. -- -- If this is not suitable and you absolutely need pre-emption, then -- you'll need a richer runtime interface than the one expected by this -- library; luckily the Haskell runtime itself is such an example. In -- other words, simply use other existing IO-based utilities for setting -- timeouts, that typically rely on concurrency or asynchronous -- exceptions. But then, you'll have to figure out your own way of -- overcoming the issues mentioned in the first paragraph. -- -- The original motivation for this library comes from implementing -- secure communications protocols and decentralised distributed systems. -- In these contexts one must often set local timeouts for remote events -- that may or may not happen in the future, or periodically synchronise -- local views of shared data with remote peers. Most operations are -- IO-bound and can be written to be non-blocking; the main exception is -- heavy cryptography which can be delegated to worker threads as -- described above. Of course, this library is not tied to these -- use-cases and is a general replacement for System.Timeout. -- -- See Control.Monad.Schedule for the main monad-based API of this -- library. -- -- See Control.Arrow.Schedule for the main arrow-based API of this -- library. -- -- See Control.Clock.IO for various ways of combining clock inputs -- with other inputs and injecting them into your pure computations. -- -- See Control.Schedule.* for higher-level utilities that one -- often wants to use on top of a timeout primitive, such as futures and -- monitors. -- -- See unit tests for example usage. @package schedule @version 0.2.0.0 -- | Extra utilities and abstractions for Control.Monad.Primitive. -- -- The API structure is stable, but the naming is not great and may -- change. Ideally we would push this upstream into -- Control.Monad.Primitive itself. module Control.Monad.Primitive.Extra -- | Type abstracting a mutable reference. -- -- This can be thought of as a mutable version of a Lens' (PrimState -- m) s with the lens functor specialised to (,) a for each -- a. newtype PrimST m s PrimST :: (forall a. (s -> (a, s)) -> m a) -> PrimST m s [statePrimST] :: PrimST m s -> forall a. (s -> (a, s)) -> m a readPrimST :: PrimST m s -> m s writePrimST :: PrimST m s -> s -> m () modifyPrimST :: PrimST m s -> (s -> s) -> m () stMutVar :: PrimMonad m => MutVar (PrimState m) s -> PrimST m s -- | Class of monads which can perform primitive state-transformer actions class Monad m => PrimMonad (m :: Type -> Type) where { -- | State token type type family PrimState (m :: Type -> Type) :: Type; } -- | Common utilities for implementing reservation data structures. -- -- A reservation data structure is one that allows multiple -- inserts of the same item, by returning a unique handle for each insert -- operation that must be given to the delete operation. -- -- If you need to store the handle together with the item, e.g. so that -- the item knows how to delete itself, this can be achieved via the -- standard Haskell "tying the knot" technique. -- -- This API is experimental at the moment, and parts of it may -- change. module Data.Rsv.Common -- | Handle generator. Runtime invariants: -- --
-- >>> data TPar = TPar !(Task TPar) deriving (Show, Eq)
--
-- >>> s = newSchedule
--
-- >>> let (t, s') = after 1 (TPar t) s -- @t@ on LHS & RHS, tying the knot
--
-- >>> t
-- Task (Delete 1 (RHandle {getHandle = 0}))
--
-- >>> taskStatus t s
-- TaskNotPending
--
-- >>> taskStatus t s'
-- TaskPending 1 (TPar (Task (Delete 1 (RHandle {getHandle = 0}))))
--
-- >>> taskStatus t s' == TaskPending 1 (TPar t)
-- True
--
after :: TickDelta -> t -> Schedule t -> (Task t, Schedule t)
-- | Cancel a task. Result is Nothing if task was not already pending.
cancel :: Task t -> Schedule t -> (Maybe t, Schedule t)
-- | Cancel a task, discarding the result.
cancel_ :: Task t -> Schedule t -> ((), Schedule t)
-- | Reschedule a pending task to instead run after a given number of
-- ticks.
--
-- If the task was not already pending, do nothing. If you need to
-- reschedule a task unconditionally even if it was already cancelled or
-- run, use both cancel_ and after in combination.
renew :: TickDelta -> Task t -> Schedule t -> (Maybe (Task t), Schedule t)
-- | Pop the next task to be run in this tick. If there are no more tasks
-- remaining, then advance to the next tick.
popOrTick :: HasCallStack => Schedule t -> (Maybe (Task t, t), Schedule t)
-- | Lock the schedule before running a particular task.
--
-- This prevents popOrTick from being called, or other tasks from
-- running. It is not re-entrant; only one task is supposed to run at
-- once.
acquireTask :: HasCallStack => (Task t, t) -> Schedule t -> Schedule t
-- | Unlock the schedule after running a particular task.
--
-- This allows popOrTick to be called again and other tasks to run. It is
-- not re-entrant; only one task is supposed to run at once.
releaseTask :: HasCallStack => Task t -> Schedule t -> Schedule t
instance GHC.Classes.Eq t => GHC.Classes.Eq (Data.Schedule.Internal.Schedule t)
instance GHC.Generics.Generic (Data.Schedule.Internal.Schedule t)
instance GHC.Read.Read t => GHC.Read.Read (Data.Schedule.Internal.Schedule t)
instance GHC.Show.Show t => GHC.Show.Show (Data.Schedule.Internal.Schedule t)
instance GHC.Classes.Ord t => GHC.Classes.Ord (Data.Schedule.Internal.TaskStatus t)
instance GHC.Classes.Eq t => GHC.Classes.Eq (Data.Schedule.Internal.TaskStatus t)
instance GHC.Generics.Generic (Data.Schedule.Internal.TaskStatus t)
instance GHC.Read.Read t => GHC.Read.Read (Data.Schedule.Internal.TaskStatus t)
instance GHC.Show.Show t => GHC.Show.Show (Data.Schedule.Internal.TaskStatus t)
instance GHC.Classes.Ord (Data.Schedule.Internal.Task t)
instance GHC.Classes.Eq (Data.Schedule.Internal.Task t)
instance GHC.Generics.Generic (Data.Schedule.Internal.Task t)
instance GHC.Read.Read (Data.Schedule.Internal.Task t)
instance GHC.Show.Show (Data.Schedule.Internal.Task t)
-- | Data structure representing scheduled tasks.
--
-- Most of the time you will want the more fully-featured
-- Control.Monad.Schedule or Control.Arrow.Schedule modules
-- instead, which re-export this module.
module Data.Schedule
type Tick = Integer
type TickDelta = Word64
-- | A task that is currently or was part of a schedule.
--
-- t is the type of input parameter for each task, i.e. the task
-- contents.
data Task t
-- | The current status of a task as returned by taskStatus.
data TaskStatus t
-- | The task is not pending - either it was already run, or cancelled.
TaskNotPending :: TaskStatus t
-- | The task is due to run at some future tick.
TaskPending :: !Tick -> !t -> TaskStatus t
-- | The task is running right now.
TaskRunning :: !t -> TaskStatus t
-- | The state of all scheduled pending tasks.
--
-- t is the type of task-params.
data Schedule t
newSchedule :: Schedule t
-- | Check the schedule that its internal invariants all hold.
--
-- You must run this on every instance obtained not via the API functions
-- here. For example, you must run this on instances obtained via
-- deserialisation, which in general cannot check the complex invariants
-- maintained by the API functions. Also, for all Tasks you obtain
-- via a similarly non-standard method, including by deserialisation of a
-- parent data structure, you must run checkTask schedule
-- task.
--
-- Nothing means the check passed; Just errmsg
-- gives a failure reason.
--
-- Note: this does not guard against all malicious behaviour, but it does
-- guard against violation (either malicious or accidental) of the
-- runtime invariants assumed by this data structure.
checkValidity :: Schedule t -> Maybe Text
-- | Check that an existing task is consistent with the current state of
-- the structure, i.e. it is not a task that could be generated in the
-- future.
checkTask :: Schedule t -> Task t -> Bool
-- | Get the current tick, whose tasks have not all run yet.
--
-- From the perspective of the pure computation that is running this
-- schedule, you should treat this as the current "logical time", even if
-- an impure clock is telling you that the "environment time" is in the
-- future.
tickNow :: Schedule t -> Tick
-- | Get the previous tick, whose tasks have all already run.
tickPrev :: Schedule t -> Tick
-- | Get the number of ticks until the next scheduled task.
--
-- This may be used by an impure runtime environment to set an actual
-- timeout; see Control.Clock for a starting point.
ticksToIdle :: Schedule t -> Maybe TickDelta
taskStatus :: HasCallStack => Task t -> Schedule t -> TaskStatus t
-- | Schedule a task to run after a given number of ticks.
--
-- This is relative to tickNow; a 0 delta schedules the
-- task to be run at the end of the current tick, i.e. as soon as
-- possible but not immediately.
--
-- If your task params needs to refer to the task itself, you may achieve
-- this by using the standard Haskell "tying the knot" technique, e.g.:
--
--
-- >>> data TPar = TPar !(Task TPar) deriving (Show, Eq)
--
-- >>> s = newSchedule
--
-- >>> let (t, s') = after 1 (TPar t) s -- @t@ on LHS & RHS, tying the knot
--
-- >>> t
-- Task (Delete 1 (RHandle {getHandle = 0}))
--
-- >>> taskStatus t s
-- TaskNotPending
--
-- >>> taskStatus t s'
-- TaskPending 1 (TPar (Task (Delete 1 (RHandle {getHandle = 0}))))
--
-- >>> taskStatus t s' == TaskPending 1 (TPar t)
-- True
--
after :: TickDelta -> t -> Schedule t -> (Task t, Schedule t)
-- | Cancel a task. Result is Nothing if task was not already pending.
cancel :: Task t -> Schedule t -> (Maybe t, Schedule t)
-- | Cancel a task, discarding the result.
cancel_ :: Task t -> Schedule t -> ((), Schedule t)
-- | Reschedule a pending task to instead run after a given number of
-- ticks.
--
-- If the task was not already pending, do nothing. If you need to
-- reschedule a task unconditionally even if it was already cancelled or
-- run, use both cancel_ and after in combination.
renew :: TickDelta -> Task t -> Schedule t -> (Maybe (Task t), Schedule t)
-- | Run an action, accumulating its monoid result until it returns
-- Nothing.
--
-- TODO: export to upstream extra
whileJustM :: (Monad m, Monoid a) => m (Maybe a) -> m a
-- | Convert a modification function into a state transition function.
modST :: (s -> s) -> s -> ((), s)
-- | Convert a getter function into a state transition function.
getST :: (s -> o) -> s -> (o, s)
-- | Convert a state transition function into a state transition arrow.
stA :: (s -> os) -> (i, s) -> os
-- | Convert a modification arrow into a state transition arrow.
imodA :: (i -> s -> s) -> (i, s) -> ((), s)
-- | Convert a getter function into a state transition arrow.
getA :: (s -> a) -> (i, s) -> (a, s)
-- | Pure serialisable futures.
--
-- This API is experimental at the moment, and parts of it may
-- change.
module Control.Schedule.Future
type OSet a = Set a
type OMap k v = Map k v
data TimedResult tk r
TimedOut :: !tk -> TimedResult tk r
GotResult :: !r -> TimedResult tk r
data SFuture wo ro
-- | SExpects waiting on us
SFWaiting :: !OSet wo -> SFuture wo ro
-- | Result of the Future
SFResult :: !ro -> SFuture wo ro
_SFResult :: forall wo_avlh ro_avli ro_avEg. Prism (SFuture wo_avlh ro_avEg) (SFuture wo_avlh ro_avli) ro_avEg ro_avli
_SFWaiting :: forall wo_avlh ro_avli wo_avEb. Prism (SFuture wo_avEb ro_avli) (SFuture wo_avlh ro_avli) (OSet wo_avEb) (OSet wo_avlh)
data SExpect wi ri tk
SExpect :: !OMap wi (Task tk) -> !OMap wi (TimedResult tk ri) -> SExpect wi ri tk
-- | SFutures we're waiting for, with our own timeout.
--
-- Note that the SFuture might have its own separate timeout which is
-- different; this t timeout is when *we* stop waiting on it.
--
-- For example if (i ~ TimedResult a) and our timeout is longer
-- than their timeout then seResults will get a GotResult
-- (TimedOut t).
[seExpects] :: SExpect wi ri tk -> !OMap wi (Task tk)
-- | SFutures that have completed, with the result. This is meant to be a
-- holding place and the caller of this should move items from here into
-- some other place to indicate that the results have been processed, so
-- that if it is called twice it does not process these results twice.
[seResults] :: SExpect wi ri tk -> !OMap wi (TimedResult tk ri)
_seResults :: forall wi_avEr ri_avEs tk_avEt ri_aw0V. Lens (SExpect wi_avEr ri_avEs tk_avEt) (SExpect wi_avEr ri_aw0V tk_avEt) (OMap wi_avEr (TimedResult tk_avEt ri_avEs)) (OMap wi_avEr (TimedResult tk_avEt ri_aw0V))
_seExpects :: forall wi_avEr ri_avEs tk_avEt. Lens' (SExpect wi_avEr ri_avEs tk_avEt) (OMap wi_avEr (Task tk_avEt))
data SFStatus e
Expecting :: e -> SFStatus e
NotExpecting :: SFStatus e
type SFStatusFull wo tk = SFStatus (OSet wo, Task tk)
data SFError
SFEAlreadyFinished :: SFError
SFEInvalidPrecondition :: !SFStatus () -> !SFStatus () -> SFError
[sfePreExpect] :: SFError -> !SFStatus ()
[sfePreActual] :: SFError -> !SFStatus ()
sCheckStatus :: (HasCallStack, Ord wi, Ord wo) => wi -> wo -> Lens' s (SFuture wo r) -> Lens' s (SExpect wi r tk) -> s -> SFStatusFull wo tk
sExpectFuture :: (Ord wi, Ord wo) => TickDelta -> tk -> wi -> wo -> Lens' s (SFuture wo r) -> Lens' s (SExpect wi r tk) -> Lens' s (Schedule tk) -> s -> Either SFError s
sExpectCancel :: (Ord wi, Ord wo) => wi -> wo -> Lens' s (SFuture wo r) -> Lens' s (SExpect wi r tk) -> Lens' s (Schedule tk) -> s -> Either SFError s
sExpectTimeout :: (HasCallStack, Ord wi, Ord wo) => tk -> wi -> wo -> Lens' s (SFuture wo r) -> Lens' s (SExpect wi r tk) -> Lens' s (Schedule tk) -> s -> Either SFError s
sFutureResult :: (Ord wi, Ord wo) => r -> wi -> Lens' s (SFuture wo r) -> IndexedTraversal' wo s (SExpect wi r tk) -> Lens' s (Schedule tk) -> s -> Either SFError s
instance GHC.Classes.Ord Control.Schedule.Future.SFError
instance GHC.Classes.Eq Control.Schedule.Future.SFError
instance GHC.Generics.Generic Control.Schedule.Future.SFError
instance GHC.Read.Read Control.Schedule.Future.SFError
instance GHC.Show.Show Control.Schedule.Future.SFError
instance GHC.Classes.Ord e => GHC.Classes.Ord (Control.Schedule.Future.SFStatus e)
instance GHC.Classes.Eq e => GHC.Classes.Eq (Control.Schedule.Future.SFStatus e)
instance GHC.Generics.Generic (Control.Schedule.Future.SFStatus e)
instance GHC.Read.Read e => GHC.Read.Read (Control.Schedule.Future.SFStatus e)
instance GHC.Show.Show e => GHC.Show.Show (Control.Schedule.Future.SFStatus e)
instance GHC.Classes.Ord wi => GHC.Base.Semigroup (Control.Schedule.Future.SExpect wi ri tk)
instance GHC.Classes.Ord wi => GHC.Base.Monoid (Control.Schedule.Future.SExpect wi ri tk)
instance (GHC.Classes.Ord wi, GHC.Classes.Ord tk, GHC.Classes.Ord ri) => GHC.Classes.Ord (Control.Schedule.Future.SExpect wi ri tk)
instance (GHC.Classes.Eq wi, GHC.Classes.Eq tk, GHC.Classes.Eq ri) => GHC.Classes.Eq (Control.Schedule.Future.SExpect wi ri tk)
instance GHC.Generics.Generic (Control.Schedule.Future.SExpect wi ri tk)
instance (GHC.Classes.Ord wi, GHC.Read.Read wi, GHC.Read.Read tk, GHC.Read.Read ri) => GHC.Read.Read (Control.Schedule.Future.SExpect wi ri tk)
instance (GHC.Show.Show wi, GHC.Show.Show tk, GHC.Show.Show ri) => GHC.Show.Show (Control.Schedule.Future.SExpect wi ri tk)
instance (GHC.Classes.Ord wo, GHC.Classes.Ord ro) => GHC.Classes.Ord (Control.Schedule.Future.SFuture wo ro)
instance (GHC.Classes.Eq wo, GHC.Classes.Eq ro) => GHC.Classes.Eq (Control.Schedule.Future.SFuture wo ro)
instance GHC.Generics.Generic (Control.Schedule.Future.SFuture wo ro)
instance (GHC.Classes.Ord wo, GHC.Read.Read wo, GHC.Read.Read ro) => GHC.Read.Read (Control.Schedule.Future.SFuture wo ro)
instance (GHC.Show.Show wo, GHC.Show.Show ro) => GHC.Show.Show (Control.Schedule.Future.SFuture wo ro)
instance (GHC.Classes.Ord tk, GHC.Classes.Ord r) => GHC.Classes.Ord (Control.Schedule.Future.TimedResult tk r)
instance (GHC.Classes.Eq tk, GHC.Classes.Eq r) => GHC.Classes.Eq (Control.Schedule.Future.TimedResult tk r)
instance GHC.Generics.Generic (Control.Schedule.Future.TimedResult tk r)
instance (GHC.Read.Read tk, GHC.Read.Read r) => GHC.Read.Read (Control.Schedule.Future.TimedResult tk r)
instance (GHC.Show.Show tk, GHC.Show.Show r) => GHC.Show.Show (Control.Schedule.Future.TimedResult tk r)
-- | Pure abstractions for time and clocks.
module Control.Clock
-- | A maybe-impure supplier of time, to a pure scheduled computation.
--
-- The type c is the computational context where clock
-- operations occur, e.g. a Monad such as IO.
--
-- Clock implementations must be monotonic. See
-- System.Time.Monotonic for an example on how to wrap
-- non-monotonic clocks to be monotonic.
data Clock c
Clock :: !c Tick -> !TickDelta -> c () -> !forall a. c a -> c (Clocked c a) -> !forall a. TickDelta -> c a -> c (Either Tick a) -> Clock c
-- | Get the current time.
[clockNow] :: Clock c -> !c Tick
-- | Suspend the current computation for a given number of ticks.
--
-- Nothing else in the computation runs until the suspension is over.
-- Afterwards, clockNow will give the expected value, i.e. for all
-- n:
--
-- -- do -- old <- clockNow -- clockDelay n -- new <- clockNow -- let new' = assert (old + n <= new) new ---- -- The relation is <= not ==, because the computer might -- have slept during the mean time or something. On the other hand, if -- the underlying physical clock might delay for a shorter period than -- requested, then implementations of this function must -- loop-delay until the <= condition is satisfied. -- -- The above is the only condition that scheduled computations should -- rely on, and any actual physical real delay is up to the -- implementation. [clockDelay] :: Clock c -> !TickDelta -> c () -- | Interleave actions with ticks. -- -- This is typically recommended for the use-case where your action -- represents a stream of inputs, e.g. from the network or the user. It -- is meant to satisfy the same functionality as the select -- system call found in common operating systems, used with a timeout -- parameter. -- -- If action when executed repeatedly gives a sequence of -- results, then in the expression clkAct <- clockWith -- clock action, a subsequent call to runClocked -- clkAct when executed repeatedly gives the same sequence of -- results but with ticks interleaved in between them. Executing -- finClocked clkAct closes any resources and invalidates -- any future calls to clkAct. -- -- It is not necessary to call finClocked if any part of -- runClocked (e.g. child threads) throws an exception - -- implementations should detect these situations and clean these up -- automatically. This frees the user of this function from having to add -- extra constraints which would be the case if it had been necessary to -- run finally ... (finClocked clkAct) as cleanup. [clockWith] :: Clock c -> !forall a. c a -> c (Clocked c a) -- | Given an action, run it with a timeout. -- -- This is typically recommended for the use-case where your action -- represents the response to a single previously-sent request. -- -- The action may complete despite the timeout firing, in which case its -- result will be lost. This is in general unavoidable and is a common -- property that one simply has to live with in distributed systems. If -- you run the input action repeatedly, then this property applies *for -- every execution*, i.e. it is possible that you get 10 timeouts even -- though the action succeeded 10 times, and you'll lose 10 results. -- -- If you want all results of all actions, use clockWith -- instead. The downside with that, is that it's slightly less efficient -- than this, as it will interleave every single Tick event and it -- is up to you to deal with skipping/ignoring any of them. [clockTimer] :: Clock c -> !forall a. TickDelta -> c a -> c (Either Tick a) -- | Run clockDelay then clockNow. clockTick :: Monad c => Clock c -> TickDelta -> c Tick -- | See clockWith for details on what this is for. data Clocked c a Clocked :: !c (Either Tick a) -> !c () -> Clocked c a [runClocked] :: Clocked c a -> !c (Either Tick a) [finClocked] :: Clocked c a -> !c () type TickDelta = Word64 type Tick = Integer -- | Implementations of Clock in the IO monad. module Control.Clock.IO -- | Create a new clock ticking at a given interval. newClock :: DiffTime -> IO (Clock IO) -- | Create a new clock ticking at a given interval in picoseconds. newClockPico :: Integer -> IO (Clock IO) -- | Create a new clock ticking at a given interval in milliseconds. newClockMilli :: Integer -> IO (Clock IO) -- | Create a new clock ticking at 1 millisecond. newClock1ms :: IO (Clock IO) -- | Create a new clock ticking at 1 second. newClock1s :: IO (Clock IO) -- | Convert a System.Time.Monotonic.Clock into an abstract -- Clock for scheduled computations, ticking at the given -- interval. convClock :: DiffTime -> Clock -> Clock IO clockWithIO :: Clock IO -> IO a -> IO (Clocked IO a) clockTimerIO :: Clock IO -> TickDelta -> IO a -> IO (Either Tick a) voidInput :: IO Void -- | Run scheduled computations in any (stateful) monad, using an adapter. -- -- This module mostly contains utilities for dealing with clock inputs. -- To get or set the existing timeouts, use your RunSched adapter -- on one of the functions from Data.Schedule, which this module -- also re-exports. module Control.Monad.Schedule -- | Something that can run Schedule state transition functions. -- -- This could be pure (e.g. StateT) or impure (e.g. reference to a -- PrimST). -- -- Examples: -- --
-- primState :: PrimMonad m => RunSched t (ReaderT (PrimST m (Schedule t)) m) -- primState sched = asks statePrimST >>= run -> lift (run sched) -- -- state :: Monad m => RunSched t (StateT (Schedule t) m) -- zoom _lens . state :: Monad m => RunSched t (StateT s m) ---- -- See the unit tests for more examples. type RunSched t m = forall a. (Schedule t -> (a, Schedule t)) -> m a runTick :: (Monad m, Monoid a) => RunSched t m -> (t -> m a) -> m a runTicksTo :: (Monad m, Monoid a) => RunSched t m -> (Tick -> t -> m a) -> Tick -> m a getInput :: Monad m => RunSched t m -> (TickDelta -> m (Either Tick i)) -> m (Either Tick i) mkOutput :: (Monad m, Monoid a) => RunSched t m -> (Tick -> t -> m a) -> (i -> m a) -> Either Tick i -> m a -- | A more general version of mkOutput that uses a -- Prism-like optic. -- -- Given an inner computation it -> m a where one branch of -- the it type has a (Tick, t) tuple -- representing individual input tasks, return an outer computation of -- type i -> m a where the i type only has a -- Tick. When the outer computation receives these Tick -- inputs, it automatically resolves the relevant tasks of type -- t that are active for that Tick, and passes each tuple -- in sequence to the wrapped inner computation. tickTask :: (Monad m, Monoid a) => RunSched t m -> (forall f. Applicative f => (Tick -> f (Tick, t)) -> i -> f it) -> (it -> m a) -> i -> m a -- | Run scheduled computations in any (stateful) arrow, using an adapter. -- -- This module mostly contains utilities for dealing with clock inputs. -- To get or set the existing timeouts, use your RunSched adapter -- on one of the functions from Data.Schedule, which this module -- also re-exports. module Control.Arrow.Schedule -- | Something that can run Schedule state transition arrows. -- -- This could be pure (e.g. StateArrow) or impure (e.g. reference -- to a PrimST). type RunSched t a = forall i o. ((i, Schedule t) -> (o, Schedule t)) -> a i o runTick :: (ArrowChoice a, Monoid o) => RunSched t a -> a (Tick, t) o -> a Tick o runTicksTo :: (ArrowChoice a, Monoid o) => RunSched t a -> a (Tick, t) o -> a Tick o getInput :: Arrow a => RunSched t a -> a TickDelta (Either Tick i) -> a i' (Either Tick i) mkOutput :: (ArrowChoice a, Monoid o) => RunSched t a -> a (Tick, t) o -> a i o -> a (Either Tick i) o -- | A more general version of mkOutput that uses a -- Prism-like optic. -- -- Given an inner computation a it o where one branch of the -- it type has a (Tick, t) tuple representing -- individual input tasks, return an outer computation of type a i -- o where the i type only has a Tick. When the -- outer computation receives these Tick inputs, it automatically -- resolves the relevant tasks of type t that are active for -- that Tick, and passes each tuple in sequence to the wrapped -- inner computation. tickTask :: (ArrowChoice a, ArrowApply a, Monoid o) => RunSched t a -> (forall f. Applicative f => (Tick -> f (Tick, t)) -> i -> f it) -> a it o -> a i o