packages feed

elerea 2.2.0 → 2.3.0

raw patch · 3 files changed

+253/−157 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ FRP.Elerea.Clocked: global :: SignalGen a -> SignalGen a
+ FRP.Elerea.Clocked: transfer :: a -> (t -> a -> a) -> Signal t -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: transfer2 :: a -> (t1 -> t2 -> a -> a) -> Signal t1 -> Signal t2 -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: transfer3 :: a -> (t1 -> t2 -> t3 -> a -> a) -> Signal t1 -> Signal t2 -> Signal t3 -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: transfer4 :: a -> (t1 -> t2 -> t3 -> t4 -> a -> a) -> Signal t1 -> Signal t2 -> Signal t3 -> Signal t4 -> SignalGen (Signal a)

Files

CHANGES view
@@ -1,3 +1,6 @@+2.3.0 - 110627+* reimplemented clocked variant in a correct and more efficient way+ 2.2.0 - 110402 * added n-ary transfer functions * temporarily removed transfer from the clocked variant
FRP/Elerea/Clocked.hs view
@@ -78,9 +78,13 @@     , memo     , until     , withClock+    , global     -- * Derived combinators     , stateful-    --, transfer+    , transfer+    , transfer2+    , transfer3+    , transfer4     -- * Random sources     , noise     , getRandom@@ -116,9 +120,18 @@ -- 'start'. newtype Signal a = S (IO a) deriving (Functor, Applicative, Monad) +-- | A pair of actions to update a signal in two phases: internal+-- update without changing the output, finalisation (throwing away+-- previous state).+type UpdateAction = (IO (), IO ())++-- | A pointer to an update pair.+data Update = USig (Weak UpdateAction)  -- ^ ordinary signal+            | UClk UpdateAction         -- ^ clocked subnetwork superstep+ -- | A dynamic set of actions to update a network without breaking -- consistency.-type UpdatePool = [Weak (IO (),IO ())]+type UpdatePool = [Update]  -- | A signal generator is the only source of stateful signals.  It -- can be thought of as a function of type @Nat -> a@, where the@@ -141,25 +154,29 @@ -- are passed a sampling time, while generators expect a start time -- that will be the creation time of all the freshly generated -- signals in the resulting structure.-newtype SignalGen a = SG { unSG :: IORef UpdatePool -> Signal Bool -> IO a }+newtype SignalGen a = SG { unSG :: IORef UpdatePool -> IORef UpdatePool -> IO a }  -- | The phases every signal goes through during a superstep. data Phase a = Ready a | Updated a a  instance Functor SignalGen where-  fmap = (<*>).pure+    fmap = (<*>).pure  instance Applicative SignalGen where-  pure = return-  (<*>) = ap+    pure = return+    (<*>) = ap  instance Monad SignalGen where-  return = SG . const . const . return-  SG g >>= f = SG $ \p c -> g p c >>= \x -> unSG (f x) p c+    return x = SG $ \_ _ -> return x+    SG g >>= f = SG $ \p1 p2 -> g p1 p2 >>= \x -> unSG (f x) p1 p2  instance MonadFix SignalGen where-  mfix f = SG $ \p c -> mfix (($c).($p).unSG.f)+    mfix f = SG $ \p1 p2 -> mfix (\x -> unSG (f x) p1 p2) +getUpdate :: Update -> IO (Maybe (Update, UpdateAction))+getUpdate upd@(USig ptr) = (fmap.fmap) ((,) upd) (deRefWeak ptr)+getUpdate upd@(UClk ua) = return (Just (upd,ua))+ -- | Embedding a signal into an 'IO' environment.  Repeated calls to -- the computation returned cause the whole network to be updated, and -- the current sample of the top-level signal is produced as a@@ -183,16 +200,15 @@ start :: SignalGen (Signal a) -- ^ the generator of the top-level signal       -> IO (IO a)            -- ^ the computation to sample the signal start (SG gen) = do-  pool <- newIORef []-  S sample <- gen pool (pure True)-  return $ do-    let deref ptr = (fmap.fmap) ((,) ptr) (deRefWeak ptr)-    res <- sample-    (ptrs,acts) <- unzip.catMaybes <$> (mapM deref =<< readIORef pool)-    writeIORef pool ptrs-    mapM_ fst acts-    mapM_ snd acts-    return res+    pool <- newIORef []+    S sample <- gen pool pool+    return $ do+        res <- sample+        (ptrs,acts) <- unzip.catMaybes <$> (mapM getUpdate =<< readIORef pool)+        writeIORef pool ptrs+        mapM_ fst acts+        mapM_ snd acts+        return res  -- | Auxiliary function used by all the primitives that create a -- mutable variable.@@ -202,21 +218,21 @@           -> IORef UpdatePool -- ^ the pool of update actions           -> IO (Signal a)    -- ^ the signal created addSignal sample update ref pool = do-  let  upd = readIORef ref >>= \v -> case v of-               Ready x  -> update x-               _        -> return ()+    let upd = readIORef ref >>= \v -> case v of+            Ready x -> update x+            _       -> return () -       fin = readIORef ref >>= \v -> case v of-               Updated x _  -> writeIORef ref $! Ready x-               _            -> error "Signal not updated!"+        fin = readIORef ref >>= \v -> case v of+            Updated x _ -> writeIORef ref $! Ready x+            _           -> error "Signal not updated!" -       sig = S $ readIORef ref >>= \v -> case v of-               Ready x      -> sample x-               Updated _ x  -> return x+        sig = S $ readIORef ref >>= \v -> case v of+            Ready x     -> sample x+            Updated _ x -> return x -  updateActions <- mkWeak sig (upd,fin) Nothing-  modifyIORef pool (updateActions:)-  return sig+    updateActions <- mkWeak sig (upd,fin) Nothing+    modifyIORef pool (USig updateActions:)+    return sig  -- | The 'delay' combinator is the elementary building block for -- adding state to the signal network by constructing delayed versions@@ -257,14 +273,12 @@ delay :: a                    -- ^ initial output at creation time       -> Signal a             -- ^ the signal to delay       -> SignalGen (Signal a) -- ^ the delayed signal-delay x0 (S s) = SG $ \pool (S clk) -> do-  ref <- newIORef (Ready x0)+delay x0 (S s) = SG $ \_gpool pool -> do+    ref <- newIORef (Ready x0) -  let update x = do  x' <- s-                     c <- clk-                     x' `seq` writeIORef ref (Updated (if c then x' else x) x)+    let update x = s >>= \x' -> x' `seq` writeIORef ref (Updated x' x) -  addSignal return update ref pool+    addSignal return update ref pool  -- | A reactive signal that takes the value to output from a signal -- generator carried by its input with the sampling time provided as@@ -284,7 +298,7 @@ -- -- It can be thought of as the following function: ----- > generator g t_start s_clock t_sample = g t_sample t_sample s_clock+-- > generator g t_start s_clock t_sample = g t_sample s_clock t_sample -- -- It has to live in the 'SignalGen' monad, because it needs to -- maintain an internal state to be able to cache the current sample@@ -297,35 +311,27 @@ -- see how it can be used. generator :: Signal (SignalGen a) -- ^ the signal of generators to run           -> SignalGen (Signal a) -- ^ the signal of generated structures-generator (S s) = SG $ \pool clk -> do-  ref <- newIORef (Ready undefined)--  let sample = do  SG g <- s-                   x <- g pool clk-                   writeIORef ref (Updated undefined x)-                   return x+generator (S s) = SG $ \gpool pool -> do+    ref <- newIORef (Ready undefined) -  addSignal (const sample) (const (sample >> return ())) ref pool+    let sample = do+            SG g <- s+            x <- g gpool pool+            writeIORef ref (Updated undefined x)+            return x --- | Override the clock used in a generator.  Note that clocks don't--- interact unless one is used in the definition of the other, i.e. it--- is possible to provide a fast clock within a generator with a slow--- associated clock.  It is equivalent to the following function:------ > withClock s g t_start s_clock = g t_start s------ For instance, the following equivalence holds:------ > withClock (pure False) (stateful x f) == pure x-withClock :: Signal Bool -> SignalGen a -> SignalGen a-withClock clk (SG g) = SG $ \pool _ -> g pool clk+    addSignal (const sample) (const (() <$ sample)) ref gpool  -- | Memoising combinator.  It can be used to cache results of -- applicative combinators in case they are used in several places.--- It is observationally equivalent to 'return' in the 'SignalGen'--- monad.+-- Unlike in the simple variant, it is not observationally equivalent+-- to 'return' in the 'SignalGen' monad, because it only samples its+-- input signal when the associated clock ticks.  The @memo@+-- combinator can be modelled by the following function: ----- > memo s = <|s s s s ...|>+-- > memo s t_start s_clock t_sample+-- >   | s_clock t_sample = s t_sample+-- >   | otherwise        = memo s t_start s_clock (t_sample-1) -- -- For instance, if @s = f \<$\> s'@, then @f@ will be recalculated -- once for each sampling of @s@.  This can be avoided by writing @s@@ -335,12 +341,12 @@ -- All the functions defined in this module return memoised signals. memo :: Signal a             -- ^ the signal to cache      -> SignalGen (Signal a) -- ^ a signal observationally equivalent to the argument-memo (S s) = SG $ \pool _ -> do-  ref <- newIORef (Ready undefined)+memo (S s) = SG $ \_gpool pool -> do+    ref <- newIORef (Ready undefined) -  let sample = s >>= \x -> writeIORef ref (Updated undefined x) >> return x+    let sample = s >>= \x -> writeIORef ref (Updated undefined x) >> return x -  addSignal (const sample) (const (sample >> return ())) ref pool+    addSignal (const sample) (const (() <$ sample)) ref pool  -- | A signal that is true exactly once: the first time the input -- signal is true.  Afterwards, it is constantly false, and it holds@@ -363,7 +369,7 @@ -- It is observationally equivalent to the following expression (which -- would hold onto @s@ forever): ----- > until s = withClock (pure True) $ do+-- > until s = global $ do -- >     step <- transfer False (||) s -- >     dstep <- delay False step -- >     memo (liftA2 (/=) step dstep)@@ -383,21 +389,59 @@ -- > [(0,False),(1,False),(2,False),(3,True),(4,False),(5,False)] until :: Signal Bool             -- ^ the boolean input signal       -> SignalGen (Signal Bool) -- ^ a one-shot signal true only the first time the input is true-until (S s) = SG $ \pool _ -> do-  ref <- newIORef (Ready undefined)+until (S s) = SG $ \gpool _pool -> do+    ref <- newIORef (Ready undefined) -  rsmp <- mfix $ \rs -> newIORef $ do-    x <- s-    writeIORef ref (Updated undefined x)-    when x $ writeIORef rs $ do-      writeIORef ref (Updated undefined False)-      return False-    return x+    rsmp <- mfix $ \rs -> newIORef $ do+        x <- s+        writeIORef ref (Updated undefined x)+        when x $ writeIORef rs $ do+            writeIORef ref (Updated undefined False)+            return False+        return x -  let sample = join (readIORef rsmp)+    let sample = join (readIORef rsmp) -  addSignal (const sample) (const (() <$ sample)) ref pool+    addSignal (const sample) (const (() <$ sample)) ref gpool +-- | Override the clock used in a generator.  Note that clocks don't+-- interact unless one is used in the definition of the other, i.e. it+-- is possible to provide a fast clock within a generator with a slow+-- associated clock.  It is equivalent to the following function:+--+-- > withClock s g t_start s_clock = g t_start s+--+-- For instance, the following equivalence holds:+--+-- > withClock (pure False) (stateful x f) == pure x+withClock :: Signal Bool -> SignalGen a -> SignalGen a+withClock (S cs) (SG g) = SG $ \gpool _pool -> do+    pool' <- newIORef []+    pref <- newIORef Nothing++    let whenc act = cs >>= flip when act++        upd = readIORef pref >>= \mp -> case mp of+            Nothing -> do+                (ptrs,acts) <- unzip.catMaybes <$> (mapM getUpdate =<< readIORef pool')+                writeIORef pool' ptrs+                writeIORef pref (Just acts)+                mapM_ fst acts+            Just _  -> return ()++        fin = readIORef pref >>= \mp -> case mp of+            Nothing   -> return ()+            Just acts -> do+                writeIORef pref Nothing+                mapM_ snd acts++    modifyIORef gpool (UClk (whenc upd, whenc fin):)+    g gpool pool'++-- | Equivalent to @withClock (pure True)@, but more efficient.+global :: SignalGen a -> SignalGen a+global (SG g) = SG $ \gpool _ -> g gpool gpool+ -- | A signal that can be directly fed through the sink function -- returned.  This can be used to attach the network to the outer -- world.  The signal always yields the value last written to the@@ -426,17 +470,18 @@ external :: a                         -- ^ initial value          -> IO (Signal a, a -> IO ()) -- ^ the signal and an IO function to feed it external x = do-  ref <- newIORef x-  return (S (readIORef ref), writeIORef ref)+    ref <- newIORef x+    return (S (readIORef ref), writeIORef ref)  -- | An event-like signal that can be fed through the sink function -- returned.  The signal carries a list of values fed in since the--- last sampling, i.e. it is constantly @[]@ if the sink is never--- invoked.  The order of elements is reversed, so the last value--- passed to the sink is the head of the list.  Note that unlike--- 'external' this function only returns a generator to be used within--- the expression constructing the top-level stream, and this--- generator can only be used once.+-- last sampling (always synchronised to the top-level samplings+-- regardless of any associated clock), i.e. it is constantly @[]@ if+-- the sink is never invoked.  The order of elements is reversed, so+-- the last value passed to the sink is the head of the list.  Note+-- that unlike 'external' this function only returns a generator to be+-- used within the expression constructing the top-level stream, and+-- this generator can only be used once. -- -- Example: --@@ -457,15 +502,16 @@ -- > [[],[7],[],[2,9]] externalMulti :: IO (SignalGen (Signal [a]), a -> IO ()) -- ^ a generator for the event signal and the associated sink externalMulti = do-  var <- newMVar []-  return (SG $ \pool _ -> do-             let sig = S $ readMVar var-             update <- mkWeak sig (return (),takeMVar var >> putMVar var []) Nothing-             modifyIORef pool (update:)-             return sig-         ,\val -> do  vals <- takeMVar var-                      putMVar var (val:vals)-         )+    var <- newMVar []+    return (SG $ \gpool _pool -> do+                 let sig = S $ readMVar var+                 update <- mkWeak sig (return (),takeMVar var >> putMVar var []) Nothing+                 modifyIORef gpool (USig update:)+                 return sig+           ,\val -> do+                 vals <- takeMVar var+                 putMVar var (val:vals)+           )  -- | A pure stateful signal.  The initial state is the first output, -- and every subsequent state is derived from the preceding one by@@ -488,20 +534,69 @@          -> SignalGen (Signal a) stateful x0 f = mfix $ \sig -> delay x0 (f <$> sig) -{--+-- | A stateful transfer function.  The current input affects the+-- current output, i.e. the initial state given in the first argument+-- is considered to appear before the first output, and can never be+-- observed, and subsequent states are determined by combining the+-- preceding state with the current output of the input signal using+-- the function supplied.  It is affected by the associated clock like+-- 'delay': no transformation is performed in the absence of a tick;+-- see the example at the top.+--+-- Example:+--+-- > do+-- >     smp <- start $ do+-- >         cnt <- stateful 1 (+1)+-- >         transfer 10 (+) cnt+-- >     res <- replicateM 5 smp+-- >     print res+--+-- Output:+--+-- > [11,13,16,20,25] transfer :: a                    -- ^ initial internal state          -> (t -> a -> a)        -- ^ state updater function          -> Signal t             -- ^ input signal          -> SignalGen (Signal a) transfer x0 f s = mfix $ \sig -> do     sig' <- delay x0 sig-    -- TODO: we shouldn't apply the function when there is no tick     memo (liftA2 f s sig') --}+-- | A variation of 'transfer' with two input signals.+transfer2 :: a                     -- ^ initial internal state+          -> (t1 -> t2 -> a -> a)  -- ^ state updater function+          -> Signal t1             -- ^ input signal 1+          -> Signal t2             -- ^ input signal 2+          -> SignalGen (Signal a)+transfer2 x0 f s1 s2 = mfix $ \sig -> do+    sig' <- delay x0 sig+    memo (liftA3 f s1 s2 sig') --- | A random signal.+-- | A variation of 'transfer' with three input signals.+transfer3 :: a                           -- ^ initial internal state+          -> (t1 -> t2 -> t3 -> a -> a)  -- ^ state updater function+          -> Signal t1                   -- ^ input signal 1+          -> Signal t2                   -- ^ input signal 2+          -> Signal t3                   -- ^ input signal 3+          -> SignalGen (Signal a)+transfer3 x0 f s1 s2 s3 = mfix $ \sig -> do+    sig' <- delay x0 sig+    memo (liftM4 f s1 s2 s3 sig')++-- | A variation of 'transfer' with four input signals.+transfer4 :: a                                 -- ^ initial internal state+          -> (t1 -> t2 -> t3 -> t4 -> a -> a)  -- ^ state updater function+          -> Signal t1                         -- ^ input signal 1+          -> Signal t2                         -- ^ input signal 2+          -> Signal t3                         -- ^ input signal 3+          -> Signal t4                         -- ^ input signal 4+          -> SignalGen (Signal a)+transfer4 x0 f s1 s2 s3 s4 = mfix $ \sig -> do+    sig' <- delay x0 sig+    memo (liftM5 f s1 s2 s3 s4 sig')++-- | A random signal.  It is affected by the associated clock. -- -- Example: --@@ -518,87 +613,85 @@  -- | A random source within the 'SignalGen' monad. getRandom :: MTRandom a => SignalGen a-getRandom = SG (const (const randomIO))+getRandom = SG $ \_ _ -> randomIO  -- | A printing action within the 'SignalGen' monad. debug :: String -> SignalGen ()-debug = SG . const . const . putStrLn+debug s = SG $ \_ _ -> putStrLn s --- | The Show instance is only defined for the sake of Num... instance Show (Signal a) where-  showsPrec _ _ s = "<SIGNAL>" ++ s+    showsPrec _ _ s = "<SIGNAL>" ++ s --- | Equality test is impossible. instance Eq (Signal a) where-  _ == _ = False+    _ == _ = False  -- | Error message for unimplemented instance functions. unimp :: String -> a unimp = error . ("Signal: "++)  instance Ord t => Ord (Signal t) where-  compare = unimp "compare"-  min = liftA2 min-  max = liftA2 max+    compare = unimp "compare"+    min = liftA2 min+    max = liftA2 max  instance Enum t => Enum (Signal t) where-  succ = fmap succ-  pred = fmap pred-  toEnum = pure . toEnum-  fromEnum = unimp "fromEnum"-  enumFrom = unimp "enumFrom"-  enumFromThen = unimp "enumFromThen"-  enumFromTo = unimp "enumFromTo"-  enumFromThenTo = unimp "enumFromThenTo"+    succ = fmap succ+    pred = fmap pred+    toEnum = pure . toEnum+    fromEnum = unimp "fromEnum"+    enumFrom = unimp "enumFrom"+    enumFromThen = unimp "enumFromThen"+    enumFromTo = unimp "enumFromTo"+    enumFromThenTo = unimp "enumFromThenTo"  instance Bounded t => Bounded (Signal t) where-  minBound = pure minBound-  maxBound = pure maxBound+    minBound = pure minBound+    maxBound = pure maxBound  instance Num t => Num (Signal t) where-  (+) = liftA2 (+)-  (-) = liftA2 (-)-  (*) = liftA2 (*)-  signum = fmap signum-  abs = fmap abs-  negate = fmap negate-  fromInteger = pure . fromInteger+    (+) = liftA2 (+)+    (-) = liftA2 (-)+    (*) = liftA2 (*)+    signum = fmap signum+    abs = fmap abs+    negate = fmap negate+    fromInteger = pure . fromInteger  instance Real t => Real (Signal t) where-  toRational = unimp "toRational"+    toRational = unimp "toRational"  instance Integral t => Integral (Signal t) where-  quot = liftA2 quot-  rem = liftA2 rem-  div = liftA2 div-  mod = liftA2 mod-  quotRem a b = (fst <$> qrab,snd <$> qrab)-    where qrab = quotRem <$> a <*> b-  divMod a b = (fst <$> dmab,snd <$> dmab)-    where dmab = divMod <$> a <*> b-  toInteger = unimp "toInteger"+    quot = liftA2 quot+    rem = liftA2 rem+    div = liftA2 div+    mod = liftA2 mod+    quotRem a b = (fst <$> qrab,snd <$> qrab)+      where qrab = quotRem <$> a <*> b+    divMod a b = (fst <$> dmab,snd <$> dmab)+      where dmab = divMod <$> a <*> b+    toInteger = unimp "toInteger"  instance Fractional t => Fractional (Signal t) where-  (/) = liftA2 (/)-  recip = fmap recip-  fromRational = pure . fromRational+    (/) = liftA2 (/)+    recip = fmap recip+    fromRational = pure . fromRational  instance Floating t => Floating (Signal t) where-  pi = pure pi-  exp = fmap exp-  sqrt = fmap sqrt-  log = fmap log-  (**) = liftA2 (**)-  logBase = liftA2 logBase-  sin = fmap sin-  tan = fmap tan-  cos = fmap cos-  asin = fmap asin-  atan = fmap atan-  acos = fmap acos-  sinh = fmap sinh-  tanh = fmap tanh-  cosh = fmap cosh-  asinh = fmap asinh-  atanh = fmap atanh-  acosh = fmap acosh+    pi = pure pi+    exp = fmap exp+    sqrt = fmap sqrt+    log = fmap log+    (**) = liftA2 (**)+    logBase = liftA2 logBase+    sin = fmap sin+    tan = fmap tan+    cos = fmap cos+    asin = fmap asin+    atan = fmap atan+    acos = fmap acos+    sinh = fmap sinh+    tanh = fmap tanh+    cosh = fmap cosh+    asinh = fmap asinh+    atanh = fmap atanh+    acosh = fmap acosh
elerea.cabal view
@@ -1,5 +1,5 @@ Name:                elerea-Version:             2.2.0+Version:             2.3.0 Cabal-Version:       >= 1.2 Synopsis:            A minimalistic FRP library Category:            reactivity, FRP