{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE GeneralizedNewtypeDeriving , StandaloneDeriving #-} {-| Pure abstractions for time and clocks. -} module Control.Clock ( Tick , checkNonNeg , checkPos , tickOp , Clock(..) ) where import Data.Monoid (Sum(..)) -- missing instances for Sum in Data.Monoid -- TODO: upstream this deriving instance Enum n => Enum (Sum n) deriving instance Real n => Real (Sum n) deriving instance Integral n => Integral (Sum n) deriving instance Fractional n => Fractional (Sum n) -- TODO: decide whether to change this to Int64 -- | The smallest discrete unit of time, in a pure scheduled computation. type Tick = Sum Integer -- | Check for a non-negative number. checkNonNeg :: (Num a, Ord a, Show a) => a -> a checkNonNeg n = if n >= 0 then n else error $ "must be non-negative: " ++ show n -- | Check for a positive number. checkPos :: (Num a, Ord a, Show a) => a -> a checkPos n = if n > 0 then n else error $ "must be positive: " ++ show n -- | Apply a binary operation to two real numbers, returning a Tick. tickOp :: (Real a, Real b) => (Rational -> Rational -> Rational) -> a -> b -> Tick tickOp op a b = Sum $ floor $ toRational a `op` toRational b {-| 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 monotic. See "System.Time.Monotonic" for an example on how to wrap non-monotonic clocks. TODO: provide a generic monotonic wrapper. -} 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 :: Tick -> c () -- Estimate the real time a tick is supposed to take. -- TODO: decide whether this is a good idea; it would add a depedency on [time] -- and also make it tempting to change a computation's behaviour based on this -- tickInterval :: Maybe DiffTime }