schedule 0.1.0.0 → 0.2.0.0
raw patch · 11 files changed
+94/−75 lines, 11 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Control.Arrow.Schedule: type RunSchedA t a = forall i o. ((i, Schedule t) -> (o, Schedule t)) -> a i o
+ Control.Arrow.Schedule: type RunSched t a = forall i o. ((i, Schedule t) -> (o, Schedule t)) -> a i o
+ Data.Schedule: checkTask :: Schedule t -> Task t -> Bool
- Control.Arrow.Schedule: getInput :: Arrow a => RunSchedA t a -> a TickDelta (Either Tick i) -> a i' (Either Tick i)
+ Control.Arrow.Schedule: getInput :: Arrow a => RunSched t a -> a TickDelta (Either Tick i) -> a i' (Either Tick i)
- Control.Arrow.Schedule: mkOutput :: (ArrowChoice a, Monoid o) => RunSchedA t a -> a (Tick, t) o -> a i o -> a (Either Tick i) o
+ Control.Arrow.Schedule: mkOutput :: (ArrowChoice a, Monoid o) => RunSched t a -> a (Tick, t) o -> a i o -> a (Either Tick i) o
- Control.Arrow.Schedule: runTick :: (ArrowChoice a, Monoid o) => RunSchedA t a -> a (Tick, t) o -> a Tick o
+ Control.Arrow.Schedule: runTick :: (ArrowChoice a, Monoid o) => RunSched t a -> a (Tick, t) o -> a Tick o
- Control.Arrow.Schedule: runTicksTo :: (ArrowChoice a, Monoid o) => RunSchedA t a -> a (Tick, t) o -> a Tick o
+ Control.Arrow.Schedule: runTicksTo :: (ArrowChoice a, Monoid o) => RunSched t a -> a (Tick, t) o -> a Tick o
- Control.Arrow.Schedule: tickTask :: (ArrowChoice a, ArrowApply a, Monoid o) => RunSchedA t a -> (forall f. Applicative f => (Tick -> f (Tick, t)) -> i -> f it) -> a it o -> a i o
+ Control.Arrow.Schedule: 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
Files
- CHANGELOG.md +4/−0
- schedule.cabal +1/−1
- src/Control/Arrow/Schedule.hs +14/−13
- src/Control/Clock.hs +46/−46
- src/Control/Monad/Primitive/Extra.hs +1/−1
- src/Control/Monad/Schedule.hs +6/−5
- src/Control/Schedule/Future.hs +1/−1
- src/Data/Rsv/Common.hs +13/−1
- src/Data/Rsv/RMMap.hs +5/−5
- src/Data/Schedule.hs +1/−0
- src/Data/Schedule/Internal.hs +2/−2
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for schedule +## 0.2.0.0 -- 2019-12-20++* Minor API and documentation tweaks.+ ## 0.1.0.0 -- 2019-12-20 * Initial release.
schedule.cabal view
@@ -1,6 +1,6 @@ Cabal-Version: 2.4 Name: schedule-Version: 0.1.0.0+Version: 0.2.0.0 Synopsis: Pure deterministic scheduled computations Description: Schedule computations to run later, in a pure and deterministic way.
src/Control/Arrow/Schedule.hs view
@@ -5,11 +5,11 @@ {-| 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 'RunSchedA' adapter on one of the functions+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- ( RunSchedA+ ( RunSched , runTick , runTicksTo , getInput@@ -44,10 +44,10 @@ -- -- This could be pure (e.g. 'Control.Arrow.Transformer.State.StateArrow') or -- impure (e.g. reference to a 'Control.Monad.Primitive.Extra.PrimST').-type RunSchedA t a = forall i o . ((i, Schedule t) -> (o, Schedule t)) -> a i o+type RunSched t a = forall i o . ((i, Schedule t) -> (o, Schedule t)) -> a i o runTick- :: (ArrowChoice a, Monoid o) => RunSchedA t a -> a (Tick, t) o -> a Tick o+ :: (ArrowChoice a, Monoid o) => RunSched t a -> a (Tick, t) o -> a Tick o runTick runS runTickTask = whileJustA $ proc tick -> do r' <- runS (stA popOrTick) -< () case r' of@@ -59,7 +59,7 @@ returnA -< Just r runTicksTo- :: (ArrowChoice a, Monoid o) => RunSchedA t a -> a (Tick, t) o -> a Tick o+ :: (ArrowChoice a, Monoid o) => RunSched t a -> a (Tick, t) o -> a Tick o runTicksTo runS runTask = whileJustA $ proc tick -> do tick' <- runS (getA tickNow) -< () if tick' >= tick@@ -68,7 +68,7 @@ getInput :: (Arrow a)- => RunSchedA t a+ => RunSched t a -> a TickDelta (Either Tick i) -> a i' (Either Tick i) getInput runS getTimedInput =@@ -76,23 +76,24 @@ mkOutput :: (ArrowChoice a, Monoid o)- => RunSchedA t a+ => RunSched t a -> a (Tick, t) o -> a i o -> a (Either Tick i) o mkOutput runS runTask runInput = runTicksTo runS runTask ||| runInput --- | A more general version of 'mkOutput' that uses a prism-like optic.+-- | A more general version of 'mkOutput' that uses a+-- 'Control.Lens.Prism.Prism'-like optic. -- -- Given an inner computation @a it o@ where one branch of the @it@ type has--- a @(Tick, t)@ tuple that represents 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+-- 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)- => RunSchedA t a+ => RunSched t a -> (forall f . Applicative f => (Tick -> f (Tick, t)) -> i -> f it) -> a it o -> a i o
src/Control/Clock.hs view
@@ -15,77 +15,77 @@ {-| 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'.+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.+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@:+ 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+ > 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 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.- -}+ 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.+ 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@.+ 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 requiring extra constraints which would- be necessary if it's necessary to run @`finally` finClocked clkAct@ as- cleanup.+ 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.+ 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.+ 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.+ 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
src/Control/Monad/Primitive/Extra.hs view
@@ -5,7 +5,7 @@ {-| Extra utilities and abstractions for "Control.Monad.Primitive". -The API structure is stable, but the naming is not very good and may change.+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
src/Control/Monad/Schedule.hs view
@@ -80,13 +80,14 @@ -> (Either Tick i -> m a) mkOutput runS runTask runInput = runTicksTo runS runTask `either` runInput --- | A more general version of 'mkOutput' that uses a prism-like optic.+-- | A more general version of 'mkOutput' that uses a+-- 'Control.Lens.Prism.Prism'-like optic. -- -- Given an inner computation @it -> m a@ where one branch of the @it@ type has--- a @(Tick, t)@ tuple that represents 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+-- 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)
src/Control/Schedule/Future.hs view
@@ -7,7 +7,7 @@ {-| Pure serialisable futures. -This API is experimental at the moment, and parts of it may change.+__This API is experimental at the moment, and parts of it may change.__ -} module Control.Schedule.Future where
src/Data/Rsv/Common.hs view
@@ -1,6 +1,18 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-| 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 ( RHandles(..) , RHandle@@ -23,7 +35,7 @@ import GHC.Stack (HasCallStack) --- Handle generator. Runtime invariants:+-- | Handle generator. Runtime invariants: -- -- 1. A handle from one generator is not used in a context that expects a -- handle from a different generator. TODO: use a string or other data to
src/Data/Rsv/RMMap.hs view
@@ -9,9 +9,9 @@ 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 deletion operation. If you need to store the handle together with the-item, e.g. so that the item knows how to delete itself, you can achieve this by-the standard Haskell "tying the knot" technique.+to the delete operation.++__This API is experimental at the moment, and parts of it may change.__ -} module Data.Rsv.RMMap ( RMMap(..)@@ -75,9 +75,9 @@ -- which in general cannot check the complex invariants maintained by the API -- functions. Also, for all handles you obtain via a similarly non-standard -- method, including by deserialisation of a parent data structure, you must--- run @checkHandle map handle@.+-- run @'checkHandle' map handle@. ----- @Nothing@ means the check passed, else @Just errmsg@ gives a failure reason.+-- '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
src/Data/Schedule.hs view
@@ -14,6 +14,7 @@ , Schedule , newSchedule , checkValidity+ , checkTask , tickNow , tickPrev , ticksToIdle
src/Data/Schedule/Internal.hs view
@@ -58,9 +58,9 @@ -- which in general cannot check the complex invariants maintained by the API -- functions. Also, for all 'Task's you obtain via a similarly non-standard -- method, including by deserialisation of a parent data structure, you must--- run @checkHandle schedule task@.+-- run @'checkTask' schedule task@. ----- @Nothing@ means the check passed, else @Just errmsg@ gives a failure reason.+-- '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