packages feed

rhine 1.5 → 1.6

raw patch · 28 files changed

+269/−255 lines, 28 filesdep ~automatondep ~basedep ~monad-schedule

Dependency ranges changed: automaton, base, monad-schedule, time-domain

Files

ChangeLog.md view
@@ -1,5 +1,15 @@ # Revision history for rhine +## Upcoming++* Removed `SN` GADT in favour of semantic functions, for a > 100x speedup in some benchmarks+  (https://github.com/turion/rhine/pull/348)++## 1.6++* Support GHC 9.12+* Replace 'SN' GADT definition by newtype. Thanks to András Kovács for the suggestion.+ ## 1.5  * Added `forever` utility for recursion in `ClSFExcept`
rhine.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: rhine-version: 1.5+version: 1.6 synopsis: Functional Reactive Programming with type-level clocks description:   Rhine is a library for synchronous and asynchronous Functional Reactive Programming (FRP).@@ -31,11 +31,12 @@   test/assets/*.txt  tested-with:-  ghc ==9.2.8-  ghc ==9.4.7-  ghc ==9.6.4-  ghc ==9.8.2-  ghc ==9.10.1+  ghc ==9.2+  ghc ==9.4+  ghc ==9.6+  ghc ==9.8+  ghc ==9.10+  ghc ==9.12  source-repository head   type: git@@ -44,13 +45,13 @@ source-repository this   type: git   location: https://github.com/turion/rhine.git-  tag: v1.5+  tag: v1.6  common opts   build-depends:-    automaton ^>=1.5,-    base >=4.16 && <4.21,-    monad-schedule ^>=0.2,+    automaton ^>=1.6,+    base >=4.16 && <4.22,+    monad-schedule ^>=1.6,     mtl >=2.2 && <2.4,     selective ^>=0.7,     text >=1.2 && <2.2,@@ -86,7 +87,7 @@     QuickCheck >=2.14 && <2.16,     tasty >=1.4 && <1.6,     tasty-hunit ^>=0.10,-    tasty-quickcheck ^>=0.10,+    tasty-quickcheck >=0.10 && <1.12,  common bench-deps   build-depends:@@ -133,6 +134,7 @@     FRP.Rhine.ResamplingBuffer.Util     FRP.Rhine.SN     FRP.Rhine.SN.Combinators+    FRP.Rhine.SN.Type     FRP.Rhine.Schedule     FRP.Rhine.Type @@ -157,7 +159,7 @@     sop-core ^>=0.5,     text >=1.2 && <2.2,     time >=1.8,-    time-domain ^>=0.1.0.2,+    time-domain ^>=1.6,     transformers >=0.5,    -- Directories containing source files.
src/FRP/Rhine/Clock.hs view
@@ -148,6 +148,7 @@       ( runningClock >>> first (arr f)       , f initTime       )+  {-# INLINE initClock #-}  {- | Instead of a mere function as morphism of time domains,    we can transform one time domain into the other with an effectful morphism.@@ -172,6 +173,7 @@       ( runningClock >>> first (arrM rescaleM)       , rescaledInitTime       )+  {-# INLINE initClock #-}  -- | A 'RescaledClock' is trivially a 'RescaledClockM'. rescaledClockToM :: (Monad m) => RescaledClock cl time -> RescaledClockM m cl time@@ -205,6 +207,7 @@       ( runningClock >>> rescaling       , rescaledInitTime       )+  {-# INLINE initClock #-}  -- | A 'RescaledClockM' is trivially a 'RescaledClockS'. rescaledClockMToS ::@@ -242,9 +245,10 @@       ( hoistS monadMorphism runningClock       , initialTime       )+  {-# INLINE initClock #-}  -- | Lift a clock type into a monad transformer.-type LiftClock m t cl = HoistClock m (t m) cl+type LiftClock m t = HoistClock m (t m)  -- | Lift a clock value into a monad transformer. liftClock :: (Monad m, MonadTrans t) => cl -> LiftClock m t cl
src/FRP/Rhine/Clock/Except.hs view
@@ -5,6 +5,7 @@ import Control.Exception import Control.Exception qualified as Exception import Control.Monad ((<=<))+import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Functor ((<&>)) import Data.Void @@ -13,7 +14,6 @@  -- mtl import Control.Monad.Error.Class-import Control.Monad.IO.Class (MonadIO, liftIO)  -- time-domain import Data.TimeDomain (TimeDomain)@@ -58,6 +58,7 @@     where       ioerror :: (MonadError e eio, MonadIO eio) => IO (Either e a) -> eio a       ioerror = liftEither <=< liftIO+  {-# INLINE initClock #-}  instance GetClockProxy (ExceptClock cl e) @@ -87,6 +88,7 @@               safe $ runningClock' >>> arr (second Left)         return (catchingClock, initTime)       Left e -> (fmap (first (>>> arr (second Left))) . initClock) $ handler e+  {-# INLINE initClock #-}  instance (GetClockProxy (CatchClock cl1 e cl2)) @@ -142,6 +144,7 @@         errorT :: (MonadError e m) => m (Either e a) -> m a         errorT = (>>= liftEither)     return (runningClock, initTime)+  {-# INLINE initClock #-}  -- * 'DelayException' 
src/FRP/Rhine/Clock/FixedStep.hs view
@@ -57,6 +57,7 @@               >>> arrM (\time -> wait step $> (time, ()))           , 0           )+  {-# INLINE initClock #-}  instance GetClockProxy (FixedStep n) 
src/FRP/Rhine/Clock/Periodic.hs view
@@ -52,6 +52,7 @@       ( cycleS (theList cl) >>> withSideEffect wait >>> accumulateWith (+) 0 &&& arr (const ())       , 0       )+  {-# INLINE initClock #-}  instance GetClockProxy (Periodic v) 
src/FRP/Rhine/Clock/Realtime/Audio.hs view
@@ -126,6 +126,7 @@       ( runningClock initialTime Nothing       , initialTime       )+  {-# INLINE initClock #-}  instance GetClockProxy (AudioClock rate bufferSize) @@ -155,6 +156,7 @@       ( arr (const (1 / thePureRateNum audioClock)) >>> sumS &&& arr (const ())       , 0       )+  {-# INLINE initClock #-}  instance GetClockProxy (PureAudioClock rate) 
src/FRP/Rhine/Clock/Realtime/Busy.hs view
@@ -36,5 +36,6 @@           &&& arr (const ())       , initialTime       )+  {-# INLINE initClock #-}  instance GetClockProxy Busy
src/FRP/Rhine/Clock/Realtime/Event.hs view
@@ -160,6 +160,7 @@           return (time, event)       , initialTime       )+  {-# INLINE initClock #-}  instance GetClockProxy (EventClock event) 
src/FRP/Rhine/Clock/Realtime/Millisecond.hs view
@@ -41,6 +41,7 @@   type Time (Millisecond n) = UTCTime   type Tag (Millisecond n) = Maybe Double   initClock (Millisecond cl) = initClock cl <&> first (>>> arr (second snd))+  {-# INLINE initClock #-}  instance GetClockProxy (Millisecond n) 
src/FRP/Rhine/Clock/Realtime/Never.hs view
@@ -33,5 +33,6 @@       ( constM (liftIO . forever . threadDelay $ 10 ^ 9)       , initialTime       )+  {-# INLINE initClock #-}  instance GetClockProxy Never
src/FRP/Rhine/Clock/Realtime/Stdin.hs view
@@ -45,6 +45,7 @@           return (time, line)       , initialTime       )+  {-# INLINE initClock #-}  instance GetClockProxy StdinClock 
src/FRP/Rhine/Clock/Select.hs view
@@ -64,6 +64,7 @@         (time, tag) <- runningClock -< ()         returnA -< (time,) <$> select tag     return (runningSelectClock, initialTime)+  {-# INLINE initClock #-}  instance GetClockProxy (SelectClock cl a) 
src/FRP/Rhine/Clock/Trivial.hs view
@@ -14,5 +14,6 @@   type Time Trivial = ()   type Tag Trivial = ()   initClock _ = return (arr $ const ((), ()), ())+  {-# INLINE initClock #-}  instance GetClockProxy Trivial
src/FRP/Rhine/Clock/Unschedule.hs view
@@ -43,3 +43,4 @@     where       run :: ScheduleT (Diff (Time cl)) m a -> m a       run = runScheduleT scheduleWait+  {-# INLINE initClock #-}
src/FRP/Rhine/Clock/Util.hs view
@@ -35,3 +35,4 @@         , sinceInit = absolute `diffTime` initialTime         , ..         }+{-# INLINE genTimeInfo #-}
src/FRP/Rhine/Reactimation/ClockErasure.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE Arrows #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE TupleSections #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}  {- | Translate clocked signal processing components to stream functions without explicit clock types. @@ -10,9 +11,6 @@ -} module FRP.Rhine.Reactimation.ClockErasure where --- base-import Control.Monad (join)- -- automaton import Data.Automaton.Trans.Reader import Data.Stream.Result (Result (..))@@ -23,7 +21,7 @@ import FRP.Rhine.Clock.Proxy import FRP.Rhine.Clock.Util import FRP.Rhine.ResamplingBuffer-import FRP.Rhine.SN+import FRP.Rhine.SN.Type (SN (..))  {- | Run a clocked signal function as an automaton,    accepting the timestamps and tags as explicit inputs.@@ -39,99 +37,18 @@   runReaderS clsf -< (timeInfo, a) {-# INLINE eraseClockClSF #-} -{- | Run a signal network as an automaton.+{- | Remove the signal network type abstraction and reveal the underlying automaton. -   Depending on the incoming clock,-   input data may need to be provided,-   and depending on the outgoing clock,-   output data may be generated.-   There are thus possible invalid inputs,-   which 'eraseClockSN' does not gracefully handle.+* To drive the network, the timestamps and tags of the clock are needed+* Since the input and output clocks are not always guaranteed to tick, the inputs and outputs are 'Maybe'. -} eraseClockSN ::-  (Monad m, Clock m cl, GetClockProxy cl) =>+  -- | Initial time   Time cl ->+  -- The original signal network   SN m cl a b ->   Automaton m (Time cl, Tag cl, Maybe a) (Maybe b)--- A synchronous signal network is run by erasing the clock from the clocked signal function.-eraseClockSN initialTime sn@(Synchronous clsf) = proc (time, tag, Just a) -> do-  b <- eraseClockClSF (toClockProxy sn) initialTime clsf -< (time, tag, a)-  returnA -< Just b---- A sequentially composed signal network may either be triggered in its first component,--- or its second component. In either case,--- the resampling buffer (which connects the two components) may be triggered,--- but only if the outgoing clock of the first component ticks,--- or the incoming clock of the second component ticks.-eraseClockSN initialTime (Sequential sn1 resBuf sn2) =-  let-    proxy1 = toClockProxy sn1-    proxy2 = toClockProxy sn2-   in-    proc (time, tag, maybeA) -> do-      resBufIn <- case tag of-        Left tagL -> do-          maybeB <- eraseClockSN initialTime sn1 -< (time, tagL, maybeA)-          returnA -< Left <$> ((time,,) <$> outTag proxy1 tagL <*> maybeB)-        Right tagR -> do-          returnA -< Right . (time,) <$> inTag proxy2 tagR-      maybeC <- mapMaybeS $ eraseClockResBuf (outProxy proxy1) (inProxy proxy2) initialTime resBuf -< resBufIn-      case tag of-        Left _ -> do-          returnA -< Nothing-        Right tagR -> do-          eraseClockSN initialTime sn2 -< (time, tagR, join maybeC)-eraseClockSN initialTime (Parallel snL snR) = proc (time, tag, maybeA) -> do-  case tag of-    Left tagL -> eraseClockSN initialTime snL -< (time, tagL, maybeA)-    Right tagR -> eraseClockSN initialTime snR -< (time, tagR, maybeA)-eraseClockSN initialTime (Postcompose sn clsf) =-  let-    proxy = toClockProxy sn-   in-    proc input@(time, tag, _) -> do-      bMaybe <- eraseClockSN initialTime sn -< input-      mapMaybeS $ eraseClockClSF (outProxy proxy) initialTime clsf -< (time,,) <$> outTag proxy tag <*> bMaybe-eraseClockSN initialTime (Precompose clsf sn) =-  let-    proxy = toClockProxy sn-   in-    proc (time, tag, aMaybe) -> do-      bMaybe <- mapMaybeS $ eraseClockClSF (inProxy proxy) initialTime clsf -< (time,,) <$> inTag proxy tag <*> aMaybe-      eraseClockSN initialTime sn -< (time, tag, bMaybe)-eraseClockSN initialTime (Feedback ResamplingBuffer {buffer, put, get} sn) =-  let-    proxy = toClockProxy sn-   in-    feedback buffer $ proc ((time, tag, aMaybe), buf) -> do-      (cMaybe, buf') <- case inTag proxy tag of-        Nothing -> do-          returnA -< (Nothing, buf)-        Just tagIn -> do-          timeInfo <- genTimeInfo (inProxy proxy) initialTime -< (time, tagIn)-          Result buf' c <- arrM $ uncurry get -< (timeInfo, buf)-          returnA -< (Just c, buf')-      bdMaybe <- eraseClockSN initialTime sn -< (time, tag, (,) <$> aMaybe <*> cMaybe)-      case (,) <$> outTag proxy tag <*> bdMaybe of-        Nothing -> do-          returnA -< (Nothing, buf')-        Just (tagOut, (b, d)) -> do-          timeInfo <- genTimeInfo (outProxy proxy) initialTime -< (time, tagOut)-          buf'' <- arrM $ uncurry $ uncurry put -< ((timeInfo, d), buf')-          returnA -< (Just b, buf'')-eraseClockSN initialTime (FirstResampling sn buf) =-  let-    proxy = toClockProxy sn-   in-    proc (time, tag, acMaybe) -> do-      bMaybe <- eraseClockSN initialTime sn -< (time, tag, fst <$> acMaybe)-      let-        resBufInput = case (inTag proxy tag, outTag proxy tag, snd <$> acMaybe) of-          (Just tagIn, _, Just c) -> Just $ Left (time, tagIn, c)-          (_, Just tagOut, _) -> Just $ Right (time, tagOut)-          _ -> Nothing-      dMaybe <- mapMaybeS $ eraseClockResBuf (inProxy proxy) (outProxy proxy) initialTime buf -< resBufInput-      returnA -< (,) <$> bMaybe <*> join dMaybe+eraseClockSN time = flip runReader time . getSN {-# INLINE eraseClockSN #-}  {- | Translate a resampling buffer into an automaton.
src/FRP/Rhine/Reactimation/Combinators.hs view
@@ -39,11 +39,14 @@ (@@) ::   ( cl ~ In cl   , cl ~ Out cl+  , Monad m+  , Clock m cl+  , GetClockProxy cl   ) =>   ClSF  m cl a b ->           cl     ->   Rhine m cl a b-(@@) = Rhine . Synchronous+(@@) = Rhine . synchronous {-# INLINE (@@) #-}  {- | A purely syntactical convenience construction@@ -82,6 +85,7 @@ (-->) ::   ( Clock m cl1   , Clock m cl2+  , Monad m   , Time cl1 ~ Time cl2   , Time (Out cl1) ~ Time cl1   , Time (In  cl2) ~ Time cl2@@ -94,7 +98,7 @@   Rhine m cl2 b c ->   Rhine m (SequentialClock cl1 cl2) a c RhineAndResamplingBuffer (Rhine sn1 cl1) rb --> (Rhine sn2 cl2) =-  Rhine (Sequential sn1 rb sn2) (SequentialClock cl1 cl2)+  Rhine (sequential sn1 rb sn2) (SequentialClock cl1 cl2)  {- | The combinators for parallel composition allow for the following syntax: @@ -177,7 +181,7 @@  -- | Postcompose a 'Rhine' with a 'ClSF'. (@>-^) ::-  ( Clock m (Out cl)+  ( Clock m (Out cl), GetClockProxy cl, Monad m   , Time cl ~ Time (Out cl)   ) =>   Rhine m      cl  a b   ->@@ -187,7 +191,7 @@  -- | Precompose a 'Rhine' with a 'ClSF'. (^->@) ::-  ( Clock m (In cl)+  ( Clock m (In cl), GetClockProxy cl, Monad m   , Time cl ~ Time (In cl)   ) =>   ClSF  m (In cl) a b   ->
src/FRP/Rhine/ResamplingBuffer.hs view
@@ -43,7 +43,8 @@ * 'a': The input type * 'b': The output type -}-data ResamplingBuffer m cla clb a b = forall s.+data ResamplingBuffer m cla clb a b+  = forall s.   ResamplingBuffer   { buffer :: s   -- ^ The internal state of the buffer.
src/FRP/Rhine/ResamplingBuffer/ClSF.hs view
@@ -7,13 +7,13 @@ import Control.Monad.Trans.Reader (ReaderT, runReaderT)  -- automaton-import Data.Automaton+import Data.Automaton hiding (toStreamT) import Data.Stream import Data.Stream.Optimized (toStreamT) import Data.Stream.Result (mapResultState)  -- rhine-import FRP.Rhine.ClSF.Core+import FRP.Rhine.ClSF.Core hiding (toStreamT) import FRP.Rhine.ResamplingBuffer  {- | Given a clocked signal function that accepts@@ -39,6 +39,6 @@     clsfBuffer' StreamT {state, step} =       ResamplingBuffer         { buffer = (state, [])-        , put = \ti1 a (s, as) -> return (s, (ti1, a) : as)+        , put = \ti1 a (s, as) -> pure (s, (ti1, a) : as)         , get = \ti2 (s, as) -> mapResultState (,[]) <$> runReaderT (runReaderT (step s) as) ti2         }
src/FRP/Rhine/ResamplingBuffer/Util.hs view
@@ -15,7 +15,7 @@ import Data.Stream.Result (Result (..), mapResultState)  -- rhine-import FRP.Rhine.ClSF hiding (step)+import FRP.Rhine.ClSF hiding (step, toStreamT) import FRP.Rhine.Clock import FRP.Rhine.ResamplingBuffer @@ -39,7 +39,7 @@       , get = \theTimeInfo (JointState b s) -> do           Result b' b <- get theTimeInfo b           Result s' c <- step s `runReaderT` b `runReaderT` theTimeInfo-          return $! Result (JointState b' s') c+          pure $! Result (JointState b' s') c       }  infix 1 ^->>@@ -58,7 +58,7 @@     , put = \theTimeInfo a (JointState buf s) -> do       Result s' b <- step s `runReaderT` a `runReaderT` theTimeInfo       buf' <- put theTimeInfo b buf-      return $! JointState buf' s'+      pure $! JointState buf' s'     , get = \theTimeInfo (JointState buf s) -> mapResultState (`JointState` s) <$> get theTimeInfo buf       } @@ -76,11 +76,11 @@   , put = \theTimeInfo (a, c) (JointState s1 s2) -> do       s1' <- put1 theTimeInfo a s1       s2' <- put2 theTimeInfo c s2-      return $! JointState s1' s2'+      pure $! JointState s1' s2'   , get = \theTimeInfo (JointState s1 s2) -> do       Result s1' b <- get1 theTimeInfo s1       Result s2' d <- get2 theTimeInfo s2-      return $! Result (JointState s1' s2') (b, d)+      pure $! Result (JointState s1' s2') (b, d)   }  infixl 4 &-&
src/FRP/Rhine/SN.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-}  {- |@@ -11,105 +12,151 @@ This module defines the 'SN' type, combinators are found in a submodule. -}-module FRP.Rhine.SN where+module FRP.Rhine.SN (+  module FRP.Rhine.SN,+  module FRP.Rhine.SN.Type,+) where +-- base+import Control.Monad (join)++-- transformers+import Control.Monad.Trans.Reader (reader)++-- automata+import Data.Stream.Result (Result (..))+ -- rhine import FRP.Rhine.ClSF.Core import FRP.Rhine.Clock import FRP.Rhine.Clock.Proxy+import FRP.Rhine.Clock.Util (genTimeInfo)+import FRP.Rhine.Reactimation.ClockErasure import FRP.Rhine.ResamplingBuffer+import FRP.Rhine.SN.Type import FRP.Rhine.Schedule -{- FOURMOLU_DISABLE -}--{- | An 'SN' is a side-effectful asynchronous /__s__ignal __n__etwork/,-where input, data processing (including side effects) and output-need not happen at the same time.--The type parameters are:--* 'm': The monad in which side effects take place.-* 'cl': The clock of the whole signal network.-        It may be sequentially or parallely composed from other clocks.-* 'a': The input type. Input arrives at the rate @In cl@.-* 'b': The output type. Output arrives at the rate @Out cl@.+{- | A synchronous automaton is the basic building block.+  For such an 'SN', data enters and leaves the system at the same rate as it is processed. -}-data SN m cl a b where-  -- | A synchronous automaton is the basic building block.-  --   For such an 'SN', data enters and leaves the system at the same rate as it is processed.-  Synchronous ::-    ( cl ~ In cl, cl ~ Out cl) =>-    ClSF m cl a b ->-    SN   m cl a b--  -- | Two 'SN's may be sequentially composed if there is a matching 'ResamplingBuffer' between them.-  Sequential ::-    ( Clock m clab, Clock m clcd-    , Clock m (Out clab), Clock m (Out clcd)-    , Clock m (In  clab), Clock m (In  clcd)-    , GetClockProxy clab, GetClockProxy clcd-    , Time clab ~ Time clcd-    , Time clab ~ Time (Out clab)-    , Time clcd ~ Time (In  clcd)-    ) =>-    SN               m      clab            a b     ->-    ResamplingBuffer m (Out clab) (In clcd)   b c   ->-    SN               m                clcd      c d ->-    SN m (SequentialClock   clab      clcd) a     d+synchronous ::+  forall cl m a b.+  (cl ~ In cl, cl ~ Out cl, Monad m, Clock m cl, GetClockProxy cl) =>+  ClSF m cl a b ->+  SN m cl a b+synchronous clsf = SN $ reader $ \initialTime -> proc (time, tag, Just a) -> do+  b <- eraseClockClSF (getClockProxy @cl) initialTime clsf -< (time, tag, a)+  returnA -< Just b+{-# INLINE synchronous #-} -  -- | Two 'SN's with the same input and output data may be parallely composed.-  Parallel ::-    ( Clock m cl1, Clock m cl2-    , Clock m (Out cl1), Clock m (Out cl2)-    , GetClockProxy cl1, GetClockProxy cl2-    , Time cl1 ~ Time (Out cl1)-    , Time cl2 ~ Time (Out cl2)-    , Time cl1 ~ Time cl2-    , Time cl1 ~ Time (In cl1)-    , Time cl2 ~ Time (In cl2)-    ) =>-    SN m                  cl1      a b ->-    SN m                      cl2  a b ->-    SN m (ParallelClock   cl1 cl2) a b+-- | Two 'SN's may be sequentially composed if there is a matching 'ResamplingBuffer' between them.+sequential ::+  ( Clock m clab+  , Clock m clcd+  , Clock m (Out clab)+  , Clock m (Out clcd)+  , Clock m (In clab)+  , Clock m (In clcd)+  , GetClockProxy clab+  , GetClockProxy clcd+  , Time clab ~ Time clcd+  , Time clab ~ Time (Out clab)+  , Time clcd ~ Time (In clcd)+  , Monad m+  ) =>+  SN m clab a b ->+  ResamplingBuffer m (Out clab) (In clcd) b c ->+  SN m clcd c d ->+  SN m (SequentialClock clab clcd) a d+-- A sequentially composed signal network may either be triggered in its first component,+-- or its second component. In either case,+-- the resampling buffer (which connects the two components) may be triggered,+-- but only if the outgoing clock of the first component ticks,+-- or the incoming clock of the second component ticks.+sequential sn1 resBuf sn2 = SN $ reader $ \initialTime ->+  let+    proxy1 = toClockProxy sn1+    proxy2 = toClockProxy sn2+   in+    proc (time, tag, maybeA) -> do+      resBufIn <- case tag of+        Left tagL -> do+          maybeB <- eraseClockSN initialTime sn1 -< (time, tagL, maybeA)+          returnA -< Left <$> ((time,,) <$> outTag proxy1 tagL <*> maybeB)+        Right tagR -> do+          returnA -< Right . (time,) <$> inTag proxy2 tagR+      maybeC <- mapMaybeS $ eraseClockResBuf (outProxy proxy1) (inProxy proxy2) initialTime resBuf -< resBufIn+      case tag of+        Left _ -> do+          returnA -< Nothing+        Right tagR -> do+          eraseClockSN initialTime sn2 -< (time, tagR, join maybeC)+{-# INLINE sequential #-} -  -- | Bypass the signal network by forwarding data in parallel through a 'ResamplingBuffer'.-  FirstResampling ::-    ( Clock m (In cl), Clock m (Out cl)-    , Time cl ~ Time (Out cl)-    , Time cl ~ Time (In cl)-    ) =>-    SN               m cl               a      b    ->-    ResamplingBuffer m (In cl) (Out cl)    c      d ->-    SN               m cl              (a, c) (b, d)+-- | Two 'SN's with the same input and output data may be parallely composed.+parallel snL snR = SN $ reader $ \initialTime -> proc (time, tag, maybeA) -> do+  case tag of+    Left tagL -> eraseClockSN initialTime snL -< (time, tagL, maybeA)+    Right tagR -> eraseClockSN initialTime snR -< (time, tagR, maybeA)+{-# INLINE parallel #-} -  -- | A 'ClSF' can always be postcomposed onto an 'SN' if the clocks match on the output.-  Postcompose ::-    ( Clock m (Out cl)-    , Time cl ~ Time (Out cl)-    ) =>-    SN    m      cl  a b   ->-    ClSF  m (Out cl)   b c ->-    SN    m      cl  a   c+-- | A 'ClSF' can always be postcomposed onto an 'SN' if the clocks match on the output.+postcompose sn clsf = SN $ reader $ \initialTime ->+  let+    proxy = toClockProxy sn+   in+    proc input@(time, tag, _) -> do+      bMaybe <- eraseClockSN initialTime sn -< input+      mapMaybeS $ eraseClockClSF (outProxy proxy) initialTime clsf -< (time,,) <$> outTag proxy tag <*> bMaybe+{-# INLINE postcompose #-} -  -- | A 'ClSF' can always be precomposed onto an 'SN' if the clocks match on the input.-  Precompose ::-    ( Clock m (In cl)-    , Time cl ~ Time (In cl)-    ) =>-    ClSF m (In cl) a b   ->-    SN   m     cl    b c ->-    SN   m     cl  a   c+-- | A 'ClSF' can always be precomposed onto an 'SN' if the clocks match on the input.+precompose clsf sn = SN $ reader $ \initialTime ->+  let+    proxy = toClockProxy sn+   in+    proc (time, tag, aMaybe) -> do+      bMaybe <- mapMaybeS $ eraseClockClSF (inProxy proxy) initialTime clsf -< (time,,) <$> inTag proxy tag <*> aMaybe+      eraseClockSN initialTime sn -< (time, tag, bMaybe)+{-# INLINE precompose #-} -  -- | Data can be looped back to the beginning of an 'SN',-  --   but it must be resampled since the 'Out' and 'In' clocks are generally different.-  Feedback ::-    ( Clock m (In cl),  Clock m (Out cl)-    , Time (In cl) ~ Time cl-    , Time (Out cl) ~ Time cl-    ) =>-    ResBuf m (Out cl) (In cl) d c ->-    SN     m cl (a, c) (b, d) ->-    SN     m cl  a      b+{- | Data can be looped back to the beginning of an 'SN',+  but it must be resampled since the 'Out' and 'In' clocks are generally different.+-}+feedbackSN ResamplingBuffer {buffer, put, get} sn = SN $ reader $ \initialTime ->+  let+    proxy = toClockProxy sn+   in+    feedback buffer $ proc ((time, tag, aMaybe), buf) -> do+      (cMaybe, buf') <- case inTag proxy tag of+        Nothing -> do+          returnA -< (Nothing, buf)+        Just tagIn -> do+          timeInfo <- genTimeInfo (inProxy proxy) initialTime -< (time, tagIn)+          Result buf' c <- arrM $ uncurry get -< (timeInfo, buf)+          returnA -< (Just c, buf')+      bdMaybe <- eraseClockSN initialTime sn -< (time, tag, (,) <$> aMaybe <*> cMaybe)+      case (,) <$> outTag proxy tag <*> bdMaybe of+        Nothing -> do+          returnA -< (Nothing, buf')+        Just (tagOut, (b, d)) -> do+          timeInfo <- genTimeInfo (outProxy proxy) initialTime -< (time, tagOut)+          buf'' <- arrM $ uncurry $ uncurry put -< ((timeInfo, d), buf')+          returnA -< (Just b, buf'')+{-# INLINE feedbackSN #-} -instance GetClockProxy cl => ToClockProxy (SN m cl a b) where-  type Cl (SN m cl a b) = cl+-- | Bypass the signal network by forwarding data in parallel through a 'ResamplingBuffer'.+firstResampling sn buf = SN $ reader $ \initialTime ->+  let+    proxy = toClockProxy sn+   in+    proc (time, tag, acMaybe) -> do+      bMaybe <- eraseClockSN initialTime sn -< (time, tag, fst <$> acMaybe)+      let+        resBufInput = case (inTag proxy tag, outTag proxy tag, snd <$> acMaybe) of+          (Just tagIn, _, Just c) -> Just $ Left (time, tagIn, c)+          (_, Just tagOut, _) -> Just $ Right (time, tagOut)+          _ -> Nothing+      dMaybe <- mapMaybeS $ eraseClockResBuf (inProxy proxy) (outProxy proxy) initialTime buf -< resBufInput+      returnA -< (,) <$> bMaybe <*> join dMaybe+{-# INLINE firstResampling #-}
src/FRP/Rhine/SN/Combinators.hs view
@@ -6,11 +6,13 @@ -} module FRP.Rhine.SN.Combinators where +-- base+import Data.Functor ((<&>))+ -- rhine import FRP.Rhine.ClSF.Core import FRP.Rhine.Clock import FRP.Rhine.Clock.Proxy-import FRP.Rhine.ResamplingBuffer.Util import FRP.Rhine.SN import FRP.Rhine.Schedule @@ -21,13 +23,7 @@   => SN m cl a b   ->          (b -> c)   -> SN m cl a      c-Synchronous clsf      >>>^ f = Synchronous $ clsf >>^ f-Sequential sn1 rb sn2 >>>^ f = Sequential sn1 rb     $ sn2 >>>^ f-Parallel   sn1    sn2 >>>^ f = Parallel  (sn1 >>>^ f) (sn2 >>>^ f)-Postcompose sn clsf >>>^ f = Postcompose sn $ clsf >>^ f-Precompose clsf sn >>>^ f = Precompose clsf $ sn >>>^ f-Feedback buf sn >>>^ f = Feedback buf $ sn >>>^ first f-firstResampling@(FirstResampling _ _) >>>^ f = Postcompose firstResampling $ arr f+SN {getSN} >>>^ f = SN $ getSN <&> (>>> arr (fmap f))  -- | Precompose a signal network with a pure function. (^>>>)@@ -35,33 +31,28 @@   =>        (a -> b)   -> SN m cl      b c   -> SN m cl a      c-f ^>>> Synchronous clsf      = Synchronous $ f ^>> clsf-f ^>>> Sequential sn1 rb sn2 = Sequential (f ^>>> sn1) rb      sn2-f ^>>> Parallel   sn1    sn2 = Parallel   (f ^>>> sn1) (f ^>>> sn2)-f ^>>> Postcompose sn clsf = Postcompose (f ^>>> sn) clsf-f ^>>> Precompose clsf sn = Precompose (f ^>> clsf) sn-f ^>>> Feedback buf sn = Feedback buf $ first f ^>>> sn-f ^>>> firstResampling@(FirstResampling _ _) = Precompose (arr f) firstResampling+f ^>>> SN {getSN} = SN $ getSN <&> (arr (fmap (fmap f)) >>>)  -- | Postcompose a signal network with a 'ClSF'. (>--^)-  :: ( Clock m (Out cl)+  :: ( GetClockProxy cl , Clock m (Out cl)      , Time cl ~ Time (Out cl)+     , Monad m      )   => SN    m      cl  a b   -> ClSF  m (Out cl)   b c   -> SN    m      cl  a   c-(>--^) = Postcompose+(>--^) = postcompose  -- | Precompose a signal network with a 'ClSF'. (^-->)-  :: ( Clock m (In cl)+  :: ( Clock m (In cl), GetClockProxy cl, Monad m      , Time cl ~ Time (In cl)      )   => ClSF m (In cl) a b   -> SN   m     cl    b c   -> SN   m     cl  a   c-(^-->) = Precompose+(^-->) = precompose  -- | Compose two signal networks on the same clock in data-parallel. --   At one tick of @cl@, both networks are stepped.@@ -70,28 +61,10 @@   => SN m cl  a      b   -> SN m cl     c      d   -> SN m cl (a, c) (b, d)-Synchronous clsf1 **** Synchronous clsf2 = Synchronous $ clsf1 *** clsf2-Sequential sn11 rb1 sn12 **** Sequential sn21 rb2 sn22 = Sequential sn1 rb sn2-  where-    sn1 = sn11 **** sn21-    sn2 = sn12 **** sn22-    rb = rb1 *-* rb2-Parallel sn11 sn12 **** Parallel sn21 sn22 =-  Parallel (sn11 **** sn21) (sn12 **** sn22)-Precompose clsf sn1 **** sn2 = Precompose (first clsf) $ sn1 **** sn2-sn1 **** Precompose clsf sn2 = Precompose (second clsf) $ sn1 **** sn2-Postcompose sn1 clsf **** sn2 = Postcompose (sn1 **** sn2) (first clsf)-sn1 **** Postcompose sn2 clsf = Postcompose (sn1 **** sn2) (second clsf)-Feedback buf sn1 **** sn2 = Feedback buf $ (\((a, c), c1) -> ((a, c1), c)) ^>>> (sn1 **** sn2) >>>^ (\((b, d1), d) -> ((b, d), d1))-sn1 **** Feedback buf sn2 = Feedback buf $ (\((a, c), c1) -> (a, (c, c1))) ^>>> (sn1 **** sn2) >>>^ (\(b, (d, d1)) -> ((b, d), d1))-FirstResampling sn1 buf **** sn2 = (\((a1, c1), c) -> ((a1, c), c1)) ^>>> FirstResampling (sn1 **** sn2) buf >>>^ (\((b1, d), d1) -> ((b1, d1), d))-sn1 **** FirstResampling sn2 buf = (\(a, (a1, c1)) -> ((a, a1), c1)) ^>>> FirstResampling (sn1 **** sn2) buf >>>^ (\((b, b1), d1) -> (b, (b1, d1)))--- Note that the patterns above are the only ones that can occur.--- This is ensured by the clock constraints in the SF constructors.-Synchronous _ **** Parallel _ _ = error "Impossible pattern: Synchronous _ **** Parallel _ _"-Parallel _ _ **** Synchronous _ = error "Impossible pattern: Parallel _ _ **** Synchronous _"-Synchronous _ **** Sequential {} = error "Impossible pattern: Synchronous _ **** Sequential {}"-Sequential {} **** Synchronous _ = error "Impossible pattern: Sequential {} **** Synchronous _"+SN sn1 **** SN sn2 = SN $ do+  sn1' <- sn1+  sn2' <- sn2+  pure $ arr (\(time, tag, mac) -> ((time, tag, fst <$> mac), (time, tag, snd <$> mac))) >>> (sn1' *** sn2') >>> arr (\(mb, md) -> (,) <$> mb <*> md)  -- | Compose two signal networks on different clocks in clock-parallel. --   At one tick of @ParClock cl1 cl2@, one of the networks is stepped,@@ -109,7 +82,7 @@   => SN m             clL      a b   -> SN m                 clR  a b   -> SN m (ParClock clL clR) a b-(||||) = Parallel+(||||) = parallel  -- | Compose two signal networks on different clocks in clock-parallel. --   At one tick of @ParClock cl1 cl2@, one of the networks is stepped,
+ src/FRP/Rhine/SN/Type.hs view
@@ -0,0 +1,30 @@+module FRP.Rhine.SN.Type where++-- transformers+import Control.Monad.Trans.Reader (Reader)++-- automaton+import Data.Automaton++-- rhine+import FRP.Rhine.Clock+import FRP.Rhine.Clock.Proxy++-- Andras Kovacs' trick: Encode in the domain++{- | An 'SN' is a side-effectful asynchronous /__s__ignal __n__etwork/,+where input, data processing (including side effects) and output+need not happen at the same time.++The type parameters are:++* 'm': The monad in which side effects take place.+* 'cl': The clock of the whole signal network.+        It may be sequentially or parallely composed from other clocks.+* 'a': The input type. Input arrives at the rate @In cl@.+* 'b': The output type. Output arrives at the rate @Out cl@.+-}+newtype SN m cl a b = SN {getSN :: Reader (Time cl) (Automaton m (Time cl, Tag cl, Maybe a) (Maybe b))}++instance (GetClockProxy cl) => ToClockProxy (SN m cl a b) where+  type Cl (SN m cl a b) = cl
src/FRP/Rhine/Schedule.hs view
@@ -24,7 +24,7 @@ import Control.Monad.Schedule.Class  -- automaton-import Data.Automaton+import Data.Automaton hiding (toStreamT) import Data.Stream.Optimized (OptimizedStreamT (..), toStreamT)  -- rhine@@ -84,7 +84,7 @@ initSchedule cl1 cl2 = do   (runningClock1, initTime) <- initClock cl1   (runningClock2, _) <- initClock cl2-  return+  pure     ( runningSchedule cl1 cl2 runningClock1 runningClock2     , initTime     )@@ -96,7 +96,8 @@ {- | Two clocks can be combined with a schedule as a clock    for an asynchronous sequential composition of signal networks. -}-data SequentialClock cl1 cl2 = (Time cl1 ~ Time cl2) =>+data SequentialClock cl1 cl2+  = (Time cl1 ~ Time cl2) =>   SequentialClock   { sequentialCl1 :: cl1   , sequentialCl2 :: cl2@@ -113,13 +114,15 @@   type Tag (SequentialClock cl1 cl2) = Either (Tag cl1) (Tag cl2)   initClock SequentialClock {..} =     initSchedule sequentialCl1 sequentialCl2+  {-# INLINE initClock #-}  -- ** Parallelly combined clocks  {- | Two clocks can be combined with a schedule as a clock    for an asynchronous parallel composition of signal networks. -}-data ParallelClock cl1 cl2 = (Time cl1 ~ Time cl2) =>+data ParallelClock cl1 cl2+  = (Time cl1 ~ Time cl2) =>   ParallelClock   { parallelCl1 :: cl1   , parallelCl2 :: cl2@@ -136,6 +139,7 @@   type Tag (ParallelClock cl1 cl2) = Either (Tag cl1) (Tag cl2)   initClock ParallelClock {..} =     initSchedule parallelCl1 parallelCl2+  {-# INLINE initClock #-}  -- * Navigating the clock tree 
src/FRP/Rhine/Schedule/Internal.hs view
@@ -29,12 +29,15 @@ -- | The result of a stream, with the type arguments swapped, so it's usable with sop-core newtype RunningResult b state = RunningResult {getRunningResult :: Result state b} +{- HLINT ignore apInjs_NPNonEmpty "Use camelCase" -}+ -- | Transform an n-ary product of at least one type into a nonempty list of all its content. apInjs_NPNonEmpty :: (SListI xs) => NP f (x ': xs) -> NonEmpty (NS f (x ': xs)) apInjs_NPNonEmpty (fx :* fxs) = Z fx :| (S <$> apInjs_NP fxs)  -- | A nonempty list of 'StreamT's, unzipped into their states and their steps.-data Streams m b = forall state (states :: [Type]).+data Streams m b+  = forall state (states :: [Type]).   (SListI states) =>   Streams   { states :: NP I (state ': states)@@ -58,7 +61,7 @@             -- Separate into finished streams and still running streams             & fmap               ( \(finished, running) ->-                  let finishedStates = finished <&> (hliftA (getRunningResult >>> resultState >>> I))+                  let finishedStates = finished <&> hliftA (getRunningResult >>> resultState >>> I)                       outputs =                         finished                           <&> (hliftA (getRunningResult >>> output >>> K) >>> hcollapse)
src/FRP/Rhine/Type.hs view
@@ -71,13 +71,15 @@   , Clock m (Out cl)   , Time (In cl) ~ Time cl   , Time (Out cl) ~ Time cl+  , GetClockProxy cl+  , Monad m   ) =>   ResamplingBuffer m (Out cl) (In cl) d c ->   Rhine m cl (a, c) (b, d) ->   Rhine m cl a b feedbackRhine buf Rhine {..} =   Rhine-    { sn = Feedback buf sn+    { sn = feedbackSN buf sn     , clock     } {-# INLINE feedbackRhine #-}
test/Clock/Except.hs view
@@ -102,6 +102,7 @@   type Time FailingClock = UTCTime   type Tag FailingClock = ()   initClock FailingClock = throwE ()+  {-# INLINE initClock #-}  instance GetClockProxy FailingClock