schedule-0.1.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 <- clock `clockWith` action@, @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 requiring extra constraints which would
be necessary if it's necessary to run @`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))
}
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 ())
}