schedule-0.2.0.0: src/Control/Clock.hs
{-# LANGUAGE RankNTypes #-}
{-| Pure abstractions for time and clocks. -}
module Control.Clock
( Clock(..)
, clockTick
, Clocked(..)
, module Data.Schedule
)
where
-- internal
import Data.Schedule (Tick, TickDelta)
{-| 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 {
-- | Get the current time.
clockNow :: !(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 :: !(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 @'Control.Exception.finally' ... (finClocked
clkAct)@ as cleanup.
-}
, clockWith :: !(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 :: !(forall a. TickDelta -> c a -> c (Either Tick a))
}
-- | Run 'clockDelay' then 'clockNow'.
clockTick :: Monad c => Clock c -> TickDelta -> c Tick
clockTick clock d = clockDelay clock d >> clockNow clock
-- | See 'clockWith' for details on what this is for.
data Clocked c a = Clocked {
runClocked :: !(c (Either Tick a))
, finClocked :: !(c ())
}