elerea 1.2.3 → 2.0.0
raw patch · 16 files changed
+2408/−2109 lines, 16 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- FRP.Elerea: (&&@) :: Signal Bool -> Signal Bool -> Signal Bool
- FRP.Elerea: (.@.) :: Signal a -> Signal t -> Signal a
- FRP.Elerea: (/=@) :: (Eq a) => Signal a -> Signal a -> Signal Bool
- FRP.Elerea: (<=@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
- FRP.Elerea: (<@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
- FRP.Elerea: (==@) :: (Eq a) => Signal a -> Signal a -> Signal Bool
- FRP.Elerea: (>=@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
- FRP.Elerea: (>@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
- FRP.Elerea: (||@) :: Signal Bool -> Signal Bool -> Signal Bool
- FRP.Elerea: createSignal :: SignalMonad a -> IO a
- FRP.Elerea: data Signal a
- FRP.Elerea: data SignalMonad a
- FRP.Elerea: delay :: a -> Signal a -> SignalMonad (Signal a)
- FRP.Elerea: edge :: Signal Bool -> SignalMonad (Signal Bool)
- FRP.Elerea: external :: a -> IO (Signal a, Sink a)
- FRP.Elerea: generator :: Signal Bool -> Signal (SignalMonad a) -> Signal (Maybe a)
- FRP.Elerea: keepAlive :: Signal a -> Signal t -> Signal a
- FRP.Elerea: sampler :: Signal (Signal a) -> Signal a
- FRP.Elerea: signalDebug :: (Show a) => a -> SignalMonad ()
- FRP.Elerea: stateful :: a -> (DTime -> a -> a) -> SignalMonad (Signal a)
- FRP.Elerea: storeJust :: a -> Signal (Maybe a) -> SignalMonad (Signal a)
- FRP.Elerea: superstep :: Signal a -> DTime -> IO a
- FRP.Elerea: toMaybe :: Bool -> a -> Maybe a
- FRP.Elerea: transfer :: a -> (DTime -> t -> a -> a) -> Signal t -> SignalMonad (Signal a)
- FRP.Elerea: type DTime = Double
- FRP.Elerea: type Sink a = a -> IO ()
- FRP.Elerea.Experimental: (&&@) :: Signal p Bool -> Signal p Bool -> Signal p Bool
- FRP.Elerea.Experimental: (-->) :: a -> Signal p (Maybe a) -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental: (/=@) :: (Eq a) => Signal p a -> Signal p a -> Signal p Bool
- FRP.Elerea.Experimental: (<=@) :: (Ord a) => Signal p a -> Signal p a -> Signal p Bool
- FRP.Elerea.Experimental: (<@) :: (Ord a) => Signal p a -> Signal p a -> Signal p Bool
- FRP.Elerea.Experimental: (==@) :: (Eq a) => Signal p a -> Signal p a -> Signal p Bool
- FRP.Elerea.Experimental: (>=@) :: (Ord a) => Signal p a -> Signal p a -> Signal p Bool
- FRP.Elerea.Experimental: (>@) :: (Ord a) => Signal p a -> Signal p a -> Signal p Bool
- FRP.Elerea.Experimental: (||@) :: Signal p Bool -> Signal p Bool -> Signal p Bool
- FRP.Elerea.Experimental: edge :: Signal p Bool -> SignalGen p (Signal p Bool)
- FRP.Elerea.Experimental.Delayed: data Signal p a
- FRP.Elerea.Experimental.Delayed: data SignalGen p a
- FRP.Elerea.Experimental.Delayed: debug :: String -> SignalGen p ()
- FRP.Elerea.Experimental.Delayed: delay :: a -> Signal p a -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Delayed: external :: a -> IO (Signal p a, a -> IO ())
- FRP.Elerea.Experimental.Delayed: externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ())
- FRP.Elerea.Experimental.Delayed: generator :: Signal p (SignalGen p a) -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Delayed: getRandom :: (MTRandom a) => SignalGen p a
- FRP.Elerea.Experimental.Delayed: instance (Bounded t) => Bounded (Signal p t)
- FRP.Elerea.Experimental.Delayed: instance (Enum t) => Enum (Signal p t)
- FRP.Elerea.Experimental.Delayed: instance (Floating t) => Floating (Signal p t)
- FRP.Elerea.Experimental.Delayed: instance (Fractional t) => Fractional (Signal p t)
- FRP.Elerea.Experimental.Delayed: instance (Integral t) => Integral (Signal p t)
- FRP.Elerea.Experimental.Delayed: instance (Num t) => Num (Signal p t)
- FRP.Elerea.Experimental.Delayed: instance (Ord t) => Ord (Signal p t)
- FRP.Elerea.Experimental.Delayed: instance (Real t) => Real (Signal p t)
- FRP.Elerea.Experimental.Delayed: instance Applicative (Signal p)
- FRP.Elerea.Experimental.Delayed: instance Applicative (SignalGen p)
- FRP.Elerea.Experimental.Delayed: instance Eq (Signal p a)
- FRP.Elerea.Experimental.Delayed: instance Functor (Signal p)
- FRP.Elerea.Experimental.Delayed: instance Functor (SignalGen p)
- FRP.Elerea.Experimental.Delayed: instance Monad (Signal p)
- FRP.Elerea.Experimental.Delayed: instance Monad (SignalGen p)
- FRP.Elerea.Experimental.Delayed: instance MonadFix (SignalGen p)
- FRP.Elerea.Experimental.Delayed: instance Show (Signal p a)
- FRP.Elerea.Experimental.Delayed: memo :: Signal p a -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Delayed: noise :: (MTRandom a) => SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Delayed: start :: SignalGen p (Signal p a) -> IO (p -> IO a)
- FRP.Elerea.Experimental.Delayed: stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Delayed: transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Param: data Signal p a
- FRP.Elerea.Experimental.Param: data SignalGen p a
- FRP.Elerea.Experimental.Param: debug :: String -> SignalGen p ()
- FRP.Elerea.Experimental.Param: delay :: a -> Signal p a -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Param: external :: a -> IO (Signal p a, a -> IO ())
- FRP.Elerea.Experimental.Param: externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ())
- FRP.Elerea.Experimental.Param: generator :: Signal p (SignalGen p a) -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Param: getRandom :: (MTRandom a) => SignalGen p a
- FRP.Elerea.Experimental.Param: instance (Bounded t) => Bounded (Signal p t)
- FRP.Elerea.Experimental.Param: instance (Enum t) => Enum (Signal p t)
- FRP.Elerea.Experimental.Param: instance (Floating t) => Floating (Signal p t)
- FRP.Elerea.Experimental.Param: instance (Fractional t) => Fractional (Signal p t)
- FRP.Elerea.Experimental.Param: instance (Integral t) => Integral (Signal p t)
- FRP.Elerea.Experimental.Param: instance (Num t) => Num (Signal p t)
- FRP.Elerea.Experimental.Param: instance (Ord t) => Ord (Signal p t)
- FRP.Elerea.Experimental.Param: instance (Real t) => Real (Signal p t)
- FRP.Elerea.Experimental.Param: instance Applicative (Signal p)
- FRP.Elerea.Experimental.Param: instance Applicative (SignalGen p)
- FRP.Elerea.Experimental.Param: instance Eq (Signal p a)
- FRP.Elerea.Experimental.Param: instance Functor (Signal p)
- FRP.Elerea.Experimental.Param: instance Functor (SignalGen p)
- FRP.Elerea.Experimental.Param: instance Monad (Signal p)
- FRP.Elerea.Experimental.Param: instance Monad (SignalGen p)
- FRP.Elerea.Experimental.Param: instance MonadFix (SignalGen p)
- FRP.Elerea.Experimental.Param: instance Show (Signal p a)
- FRP.Elerea.Experimental.Param: memo :: Signal p a -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Param: noise :: (MTRandom a) => SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Param: start :: SignalGen p (Signal p a) -> IO (p -> IO a)
- FRP.Elerea.Experimental.Param: stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Param: transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)
- FRP.Elerea.Experimental.Simple: data Signal a
- FRP.Elerea.Experimental.Simple: data SignalGen a
- FRP.Elerea.Experimental.Simple: delay :: a -> Signal a -> SignalGen (Signal a)
- FRP.Elerea.Experimental.Simple: external :: a -> IO (Signal a, a -> IO ())
- FRP.Elerea.Experimental.Simple: externalMulti :: IO (SignalGen (Signal [a]), a -> IO ())
- FRP.Elerea.Experimental.Simple: generator :: Signal (SignalGen a) -> SignalGen (Signal a)
- FRP.Elerea.Experimental.Simple: getRandom :: (MTRandom a) => SignalGen a
- FRP.Elerea.Experimental.Simple: instance (Bounded t) => Bounded (Signal t)
- FRP.Elerea.Experimental.Simple: instance (Enum t) => Enum (Signal t)
- FRP.Elerea.Experimental.Simple: instance (Floating t) => Floating (Signal t)
- FRP.Elerea.Experimental.Simple: instance (Fractional t) => Fractional (Signal t)
- FRP.Elerea.Experimental.Simple: instance (Integral t) => Integral (Signal t)
- FRP.Elerea.Experimental.Simple: instance (Num t) => Num (Signal t)
- FRP.Elerea.Experimental.Simple: instance (Ord t) => Ord (Signal t)
- FRP.Elerea.Experimental.Simple: instance (Real t) => Real (Signal t)
- FRP.Elerea.Experimental.Simple: instance Applicative Signal
- FRP.Elerea.Experimental.Simple: instance Applicative SignalGen
- FRP.Elerea.Experimental.Simple: instance Eq (Signal a)
- FRP.Elerea.Experimental.Simple: instance Functor Signal
- FRP.Elerea.Experimental.Simple: instance Functor SignalGen
- FRP.Elerea.Experimental.Simple: instance Monad Signal
- FRP.Elerea.Experimental.Simple: instance Monad SignalGen
- FRP.Elerea.Experimental.Simple: instance MonadFix SignalGen
- FRP.Elerea.Experimental.Simple: instance Show (Signal a)
- FRP.Elerea.Experimental.Simple: memo :: Signal a -> SignalGen (Signal a)
- FRP.Elerea.Experimental.Simple: noise :: (MTRandom a) => SignalGen (Signal a)
- FRP.Elerea.Experimental.Simple: start :: SignalGen (Signal a) -> IO (IO a)
- FRP.Elerea.Experimental.Simple: stateful :: a -> (a -> a) -> SignalGen (Signal a)
- FRP.Elerea.Experimental.Simple: transfer :: a -> (t -> a -> a) -> Signal t -> SignalGen (Signal a)
- FRP.Elerea.Graph: signalToDot :: Signal a -> IO String
- FRP.Elerea.Internal: Aged :: a -> (SignalNode a) -> SignalTrans a
- FRP.Elerea.Internal: Ready :: (SignalNode a) -> SignalTrans a
- FRP.Elerea.Internal: S :: (IORef (SignalTrans a)) -> Signal a
- FRP.Elerea.Internal: SM :: IO a -> SignalMonad a
- FRP.Elerea.Internal: SNA :: (Signal (t -> a)) -> (Signal t) -> SignalNode a
- FRP.Elerea.Internal: SND :: a -> (Signal a) -> SignalNode a
- FRP.Elerea.Internal: SNE :: (IORef a) -> SignalNode a
- FRP.Elerea.Internal: SNF1 :: (t -> a) -> (Signal t) -> SignalNode a
- FRP.Elerea.Internal: SNF2 :: (t1 -> t2 -> a) -> (Signal t1) -> (Signal t2) -> SignalNode a
- FRP.Elerea.Internal: SNF3 :: (t1 -> t2 -> t3 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> SignalNode a
- FRP.Elerea.Internal: SNF4 :: (t1 -> t2 -> t3 -> t4 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> (Signal t4) -> SignalNode a
- FRP.Elerea.Internal: SNF5 :: (t1 -> t2 -> t3 -> t4 -> t5 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> (Signal t4) -> (Signal t5) -> SignalNode a
- FRP.Elerea.Internal: SNH :: (Signal (Signal a)) -> (IORef (Signal a)) -> SignalNode a
- FRP.Elerea.Internal: SNK :: a -> SignalNode a
- FRP.Elerea.Internal: SNKA :: (Signal a) -> (Signal t) -> SignalNode a
- FRP.Elerea.Internal: SNM :: (Signal Bool) -> (Signal (SignalMonad a)) -> SignalNode a
- FRP.Elerea.Internal: SNS :: a -> (DTime -> a -> a) -> SignalNode a
- FRP.Elerea.Internal: SNT :: (Signal t) -> a -> (DTime -> t -> a -> a) -> SignalNode a
- FRP.Elerea.Internal: Sampled :: a -> (SignalNode a) -> SignalTrans a
- FRP.Elerea.Internal: Sampling :: (SignalNode a) -> SignalTrans a
- FRP.Elerea.Internal: advance :: SignalNode a -> a -> DTime -> IO (SignalNode a)
- FRP.Elerea.Internal: age :: Signal a -> DTime -> IO ()
- FRP.Elerea.Internal: commit :: Signal a -> IO ()
- FRP.Elerea.Internal: createSignal :: SignalMonad a -> IO a
- FRP.Elerea.Internal: data SignalNode a
- FRP.Elerea.Internal: data SignalTrans a
- FRP.Elerea.Internal: debugLog :: String -> IO a -> IO a
- FRP.Elerea.Internal: delay :: a -> Signal a -> SignalMonad (Signal a)
- FRP.Elerea.Internal: external :: a -> IO (Signal a, Sink a)
- FRP.Elerea.Internal: generator :: Signal Bool -> Signal (SignalMonad a) -> Signal (Maybe a)
- FRP.Elerea.Internal: instance (Bounded t) => Bounded (Signal t)
- FRP.Elerea.Internal: instance (Enum t) => Enum (Signal t)
- FRP.Elerea.Internal: instance (Floating t) => Floating (Signal t)
- FRP.Elerea.Internal: instance (Fractional t) => Fractional (Signal t)
- FRP.Elerea.Internal: instance (Integral t) => Integral (Signal t)
- FRP.Elerea.Internal: instance (Num t) => Num (Signal t)
- FRP.Elerea.Internal: instance (Ord t) => Ord (Signal t)
- FRP.Elerea.Internal: instance (Real t) => Real (Signal t)
- FRP.Elerea.Internal: instance Applicative Signal
- FRP.Elerea.Internal: instance Applicative SignalMonad
- FRP.Elerea.Internal: instance Eq (Signal a)
- FRP.Elerea.Internal: instance Functor Signal
- FRP.Elerea.Internal: instance Functor SignalMonad
- FRP.Elerea.Internal: instance Monad SignalMonad
- FRP.Elerea.Internal: instance MonadFix SignalMonad
- FRP.Elerea.Internal: instance Show (Signal a)
- FRP.Elerea.Internal: keepAlive :: Signal a -> Signal t -> Signal a
- FRP.Elerea.Internal: makeSignal :: SignalNode a -> SignalMonad (Signal a)
- FRP.Elerea.Internal: makeSignalUnsafe :: SignalNode a -> Signal a
- FRP.Elerea.Internal: newtype Signal a
- FRP.Elerea.Internal: newtype SignalMonad a
- FRP.Elerea.Internal: sample :: SignalNode a -> DTime -> IO a
- FRP.Elerea.Internal: sampleDelayed :: SignalNode a -> DTime -> IO a
- FRP.Elerea.Internal: sampler :: Signal (Signal a) -> Signal a
- FRP.Elerea.Internal: signalDebug :: (Show a) => a -> SignalMonad ()
- FRP.Elerea.Internal: signalValue :: Signal a -> DTime -> IO a
- FRP.Elerea.Internal: stateful :: a -> (DTime -> a -> a) -> SignalMonad (Signal a)
- FRP.Elerea.Internal: superstep :: Signal a -> DTime -> IO a
- FRP.Elerea.Internal: toMaybe :: Bool -> a -> Maybe a
- FRP.Elerea.Internal: transfer :: a -> (DTime -> t -> a -> a) -> Signal t -> SignalMonad (Signal a)
- FRP.Elerea.Internal: type DTime = Double
- FRP.Elerea.Internal: type Sink a = a -> IO ()
- FRP.Elerea.Internal: unimp :: String -> a
+ FRP.Elerea.Clocked: data Signal a
+ FRP.Elerea.Clocked: data SignalGen a
+ FRP.Elerea.Clocked: delay :: a -> Signal a -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: external :: a -> IO (Signal a, a -> IO ())
+ FRP.Elerea.Clocked: externalMulti :: IO (SignalGen (Signal [a]), a -> IO ())
+ FRP.Elerea.Clocked: generator :: Signal (SignalGen a) -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: getRandom :: (MTRandom a) => SignalGen a
+ FRP.Elerea.Clocked: instance (Bounded t) => Bounded (Signal t)
+ FRP.Elerea.Clocked: instance (Enum t) => Enum (Signal t)
+ FRP.Elerea.Clocked: instance (Floating t) => Floating (Signal t)
+ FRP.Elerea.Clocked: instance (Fractional t) => Fractional (Signal t)
+ FRP.Elerea.Clocked: instance (Integral t) => Integral (Signal t)
+ FRP.Elerea.Clocked: instance (Num t) => Num (Signal t)
+ FRP.Elerea.Clocked: instance (Ord t) => Ord (Signal t)
+ FRP.Elerea.Clocked: instance (Real t) => Real (Signal t)
+ FRP.Elerea.Clocked: instance Applicative Signal
+ FRP.Elerea.Clocked: instance Applicative SignalGen
+ FRP.Elerea.Clocked: instance Eq (Signal a)
+ FRP.Elerea.Clocked: instance Functor Signal
+ FRP.Elerea.Clocked: instance Functor SignalGen
+ FRP.Elerea.Clocked: instance Monad Signal
+ FRP.Elerea.Clocked: instance Monad SignalGen
+ FRP.Elerea.Clocked: instance MonadFix SignalGen
+ FRP.Elerea.Clocked: instance Show (Signal a)
+ FRP.Elerea.Clocked: memo :: Signal a -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: noise :: (MTRandom a) => SignalGen (Signal a)
+ FRP.Elerea.Clocked: start :: SignalGen (Signal a) -> IO (IO a)
+ FRP.Elerea.Clocked: stateful :: a -> (a -> a) -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: transfer :: a -> (t -> a -> a) -> Signal t -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: withClock :: Signal Bool -> SignalGen a -> SignalGen a
+ FRP.Elerea.Delayed: data Signal p a
+ FRP.Elerea.Delayed: data SignalGen p a
+ FRP.Elerea.Delayed: debug :: String -> SignalGen p ()
+ FRP.Elerea.Delayed: delay :: a -> Signal p a -> SignalGen p (Signal p a)
+ FRP.Elerea.Delayed: external :: a -> IO (Signal p a, a -> IO ())
+ FRP.Elerea.Delayed: externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ())
+ FRP.Elerea.Delayed: generator :: Signal p (SignalGen p a) -> SignalGen p (Signal p a)
+ FRP.Elerea.Delayed: getRandom :: (MTRandom a) => SignalGen p a
+ FRP.Elerea.Delayed: instance (Bounded t) => Bounded (Signal p t)
+ FRP.Elerea.Delayed: instance (Enum t) => Enum (Signal p t)
+ FRP.Elerea.Delayed: instance (Floating t) => Floating (Signal p t)
+ FRP.Elerea.Delayed: instance (Fractional t) => Fractional (Signal p t)
+ FRP.Elerea.Delayed: instance (Integral t) => Integral (Signal p t)
+ FRP.Elerea.Delayed: instance (Num t) => Num (Signal p t)
+ FRP.Elerea.Delayed: instance (Ord t) => Ord (Signal p t)
+ FRP.Elerea.Delayed: instance (Real t) => Real (Signal p t)
+ FRP.Elerea.Delayed: instance Applicative (Signal p)
+ FRP.Elerea.Delayed: instance Applicative (SignalGen p)
+ FRP.Elerea.Delayed: instance Eq (Signal p a)
+ FRP.Elerea.Delayed: instance Functor (Signal p)
+ FRP.Elerea.Delayed: instance Functor (SignalGen p)
+ FRP.Elerea.Delayed: instance Monad (Signal p)
+ FRP.Elerea.Delayed: instance Monad (SignalGen p)
+ FRP.Elerea.Delayed: instance MonadFix (SignalGen p)
+ FRP.Elerea.Delayed: instance Show (Signal p a)
+ FRP.Elerea.Delayed: memo :: Signal p a -> SignalGen p (Signal p a)
+ FRP.Elerea.Delayed: noise :: (MTRandom a) => SignalGen p (Signal p a)
+ FRP.Elerea.Delayed: start :: SignalGen p (Signal p a) -> IO (p -> IO a)
+ FRP.Elerea.Delayed: stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)
+ FRP.Elerea.Delayed: transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)
+ FRP.Elerea.Legacy: (&&@) :: Signal Bool -> Signal Bool -> Signal Bool
+ FRP.Elerea.Legacy: (.@.) :: Signal a -> Signal t -> Signal a
+ FRP.Elerea.Legacy: (/=@) :: (Eq a) => Signal a -> Signal a -> Signal Bool
+ FRP.Elerea.Legacy: (<=@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
+ FRP.Elerea.Legacy: (<@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
+ FRP.Elerea.Legacy: (==@) :: (Eq a) => Signal a -> Signal a -> Signal Bool
+ FRP.Elerea.Legacy: (>=@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
+ FRP.Elerea.Legacy: (>@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
+ FRP.Elerea.Legacy: (||@) :: Signal Bool -> Signal Bool -> Signal Bool
+ FRP.Elerea.Legacy: createSignal :: SignalMonad a -> IO a
+ FRP.Elerea.Legacy: data Signal a
+ FRP.Elerea.Legacy: data SignalMonad a
+ FRP.Elerea.Legacy: delay :: a -> Signal a -> SignalMonad (Signal a)
+ FRP.Elerea.Legacy: edge :: Signal Bool -> SignalMonad (Signal Bool)
+ FRP.Elerea.Legacy: external :: a -> IO (Signal a, Sink a)
+ FRP.Elerea.Legacy: generator :: Signal Bool -> Signal (SignalMonad a) -> Signal (Maybe a)
+ FRP.Elerea.Legacy: keepAlive :: Signal a -> Signal t -> Signal a
+ FRP.Elerea.Legacy: sampler :: Signal (Signal a) -> Signal a
+ FRP.Elerea.Legacy: signalDebug :: (Show a) => a -> SignalMonad ()
+ FRP.Elerea.Legacy: stateful :: a -> (DTime -> a -> a) -> SignalMonad (Signal a)
+ FRP.Elerea.Legacy: storeJust :: a -> Signal (Maybe a) -> SignalMonad (Signal a)
+ FRP.Elerea.Legacy: superstep :: Signal a -> DTime -> IO a
+ FRP.Elerea.Legacy: toMaybe :: Bool -> a -> Maybe a
+ FRP.Elerea.Legacy: transfer :: a -> (DTime -> t -> a -> a) -> Signal t -> SignalMonad (Signal a)
+ FRP.Elerea.Legacy: type DTime = Double
+ FRP.Elerea.Legacy: type Sink a = a -> IO ()
+ FRP.Elerea.Legacy.Graph: signalToDot :: Signal a -> IO String
+ FRP.Elerea.Legacy.Internal: Aged :: a -> (SignalNode a) -> SignalTrans a
+ FRP.Elerea.Legacy.Internal: Ready :: (SignalNode a) -> SignalTrans a
+ FRP.Elerea.Legacy.Internal: S :: (IORef (SignalTrans a)) -> Signal a
+ FRP.Elerea.Legacy.Internal: SM :: IO a -> SignalMonad a
+ FRP.Elerea.Legacy.Internal: SNA :: (Signal (t -> a)) -> (Signal t) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SND :: a -> (Signal a) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNE :: (IORef a) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNF1 :: (t -> a) -> (Signal t) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNF2 :: (t1 -> t2 -> a) -> (Signal t1) -> (Signal t2) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNF3 :: (t1 -> t2 -> t3 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNF4 :: (t1 -> t2 -> t3 -> t4 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> (Signal t4) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNF5 :: (t1 -> t2 -> t3 -> t4 -> t5 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> (Signal t4) -> (Signal t5) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNH :: (Signal (Signal a)) -> (IORef (Signal a)) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNK :: a -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNKA :: (Signal a) -> (Signal t) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNM :: (Signal Bool) -> (Signal (SignalMonad a)) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNS :: a -> (DTime -> a -> a) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: SNT :: (Signal t) -> a -> (DTime -> t -> a -> a) -> SignalNode a
+ FRP.Elerea.Legacy.Internal: Sampled :: a -> (SignalNode a) -> SignalTrans a
+ FRP.Elerea.Legacy.Internal: Sampling :: (SignalNode a) -> SignalTrans a
+ FRP.Elerea.Legacy.Internal: advance :: SignalNode a -> a -> DTime -> IO (SignalNode a)
+ FRP.Elerea.Legacy.Internal: age :: Signal a -> DTime -> IO ()
+ FRP.Elerea.Legacy.Internal: commit :: Signal a -> IO ()
+ FRP.Elerea.Legacy.Internal: createSignal :: SignalMonad a -> IO a
+ FRP.Elerea.Legacy.Internal: data SignalNode a
+ FRP.Elerea.Legacy.Internal: data SignalTrans a
+ FRP.Elerea.Legacy.Internal: debugLog :: String -> IO a -> IO a
+ FRP.Elerea.Legacy.Internal: delay :: a -> Signal a -> SignalMonad (Signal a)
+ FRP.Elerea.Legacy.Internal: external :: a -> IO (Signal a, Sink a)
+ FRP.Elerea.Legacy.Internal: generator :: Signal Bool -> Signal (SignalMonad a) -> Signal (Maybe a)
+ FRP.Elerea.Legacy.Internal: instance (Bounded t) => Bounded (Signal t)
+ FRP.Elerea.Legacy.Internal: instance (Enum t) => Enum (Signal t)
+ FRP.Elerea.Legacy.Internal: instance (Floating t) => Floating (Signal t)
+ FRP.Elerea.Legacy.Internal: instance (Fractional t) => Fractional (Signal t)
+ FRP.Elerea.Legacy.Internal: instance (Integral t) => Integral (Signal t)
+ FRP.Elerea.Legacy.Internal: instance (Num t) => Num (Signal t)
+ FRP.Elerea.Legacy.Internal: instance (Ord t) => Ord (Signal t)
+ FRP.Elerea.Legacy.Internal: instance (Real t) => Real (Signal t)
+ FRP.Elerea.Legacy.Internal: instance Applicative Signal
+ FRP.Elerea.Legacy.Internal: instance Applicative SignalMonad
+ FRP.Elerea.Legacy.Internal: instance Eq (Signal a)
+ FRP.Elerea.Legacy.Internal: instance Functor Signal
+ FRP.Elerea.Legacy.Internal: instance Functor SignalMonad
+ FRP.Elerea.Legacy.Internal: instance Monad SignalMonad
+ FRP.Elerea.Legacy.Internal: instance MonadFix SignalMonad
+ FRP.Elerea.Legacy.Internal: instance Show (Signal a)
+ FRP.Elerea.Legacy.Internal: keepAlive :: Signal a -> Signal t -> Signal a
+ FRP.Elerea.Legacy.Internal: makeSignal :: SignalNode a -> SignalMonad (Signal a)
+ FRP.Elerea.Legacy.Internal: makeSignalUnsafe :: SignalNode a -> Signal a
+ FRP.Elerea.Legacy.Internal: newtype Signal a
+ FRP.Elerea.Legacy.Internal: newtype SignalMonad a
+ FRP.Elerea.Legacy.Internal: sample :: SignalNode a -> DTime -> IO a
+ FRP.Elerea.Legacy.Internal: sampleDelayed :: SignalNode a -> DTime -> IO a
+ FRP.Elerea.Legacy.Internal: sampler :: Signal (Signal a) -> Signal a
+ FRP.Elerea.Legacy.Internal: signalDebug :: (Show a) => a -> SignalMonad ()
+ FRP.Elerea.Legacy.Internal: signalValue :: Signal a -> DTime -> IO a
+ FRP.Elerea.Legacy.Internal: stateful :: a -> (DTime -> a -> a) -> SignalMonad (Signal a)
+ FRP.Elerea.Legacy.Internal: superstep :: Signal a -> DTime -> IO a
+ FRP.Elerea.Legacy.Internal: toMaybe :: Bool -> a -> Maybe a
+ FRP.Elerea.Legacy.Internal: transfer :: a -> (DTime -> t -> a -> a) -> Signal t -> SignalMonad (Signal a)
+ FRP.Elerea.Legacy.Internal: type DTime = Double
+ FRP.Elerea.Legacy.Internal: type Sink a = a -> IO ()
+ FRP.Elerea.Legacy.Internal: unimp :: String -> a
+ FRP.Elerea.Param: data Signal p a
+ FRP.Elerea.Param: data SignalGen p a
+ FRP.Elerea.Param: debug :: String -> SignalGen p ()
+ FRP.Elerea.Param: delay :: a -> Signal p a -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: external :: a -> IO (Signal p a, a -> IO ())
+ FRP.Elerea.Param: externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ())
+ FRP.Elerea.Param: generator :: Signal p (SignalGen p a) -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: getRandom :: (MTRandom a) => SignalGen p a
+ FRP.Elerea.Param: instance (Bounded t) => Bounded (Signal p t)
+ FRP.Elerea.Param: instance (Enum t) => Enum (Signal p t)
+ FRP.Elerea.Param: instance (Floating t) => Floating (Signal p t)
+ FRP.Elerea.Param: instance (Fractional t) => Fractional (Signal p t)
+ FRP.Elerea.Param: instance (Integral t) => Integral (Signal p t)
+ FRP.Elerea.Param: instance (Num t) => Num (Signal p t)
+ FRP.Elerea.Param: instance (Ord t) => Ord (Signal p t)
+ FRP.Elerea.Param: instance (Real t) => Real (Signal p t)
+ FRP.Elerea.Param: instance Applicative (Signal p)
+ FRP.Elerea.Param: instance Applicative (SignalGen p)
+ FRP.Elerea.Param: instance Eq (Signal p a)
+ FRP.Elerea.Param: instance Functor (Signal p)
+ FRP.Elerea.Param: instance Functor (SignalGen p)
+ FRP.Elerea.Param: instance Monad (Signal p)
+ FRP.Elerea.Param: instance Monad (SignalGen p)
+ FRP.Elerea.Param: instance MonadFix (SignalGen p)
+ FRP.Elerea.Param: instance Show (Signal p a)
+ FRP.Elerea.Param: memo :: Signal p a -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: noise :: (MTRandom a) => SignalGen p (Signal p a)
+ FRP.Elerea.Param: start :: SignalGen p (Signal p a) -> IO (p -> IO a)
+ FRP.Elerea.Param: stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)
+ FRP.Elerea.Simple: data Signal a
+ FRP.Elerea.Simple: data SignalGen a
+ FRP.Elerea.Simple: delay :: a -> Signal a -> SignalGen (Signal a)
+ FRP.Elerea.Simple: external :: a -> IO (Signal a, a -> IO ())
+ FRP.Elerea.Simple: externalMulti :: IO (SignalGen (Signal [a]), a -> IO ())
+ FRP.Elerea.Simple: generator :: Signal (SignalGen a) -> SignalGen (Signal a)
+ FRP.Elerea.Simple: getRandom :: (MTRandom a) => SignalGen a
+ FRP.Elerea.Simple: instance (Bounded t) => Bounded (Signal t)
+ FRP.Elerea.Simple: instance (Enum t) => Enum (Signal t)
+ FRP.Elerea.Simple: instance (Floating t) => Floating (Signal t)
+ FRP.Elerea.Simple: instance (Fractional t) => Fractional (Signal t)
+ FRP.Elerea.Simple: instance (Integral t) => Integral (Signal t)
+ FRP.Elerea.Simple: instance (Num t) => Num (Signal t)
+ FRP.Elerea.Simple: instance (Ord t) => Ord (Signal t)
+ FRP.Elerea.Simple: instance (Real t) => Real (Signal t)
+ FRP.Elerea.Simple: instance Applicative Signal
+ FRP.Elerea.Simple: instance Applicative SignalGen
+ FRP.Elerea.Simple: instance Eq (Signal a)
+ FRP.Elerea.Simple: instance Functor Signal
+ FRP.Elerea.Simple: instance Functor SignalGen
+ FRP.Elerea.Simple: instance Monad Signal
+ FRP.Elerea.Simple: instance Monad SignalGen
+ FRP.Elerea.Simple: instance MonadFix SignalGen
+ FRP.Elerea.Simple: instance Show (Signal a)
+ FRP.Elerea.Simple: memo :: Signal a -> SignalGen (Signal a)
+ FRP.Elerea.Simple: noise :: (MTRandom a) => SignalGen (Signal a)
+ FRP.Elerea.Simple: start :: SignalGen (Signal a) -> IO (IO a)
+ FRP.Elerea.Simple: stateful :: a -> (a -> a) -> SignalGen (Signal a)
+ FRP.Elerea.Simple: transfer :: a -> (t -> a -> a) -> Signal t -> SignalGen (Signal a)
Files
- CHANGES +4/−0
- FRP/Elerea.hs +0/−122
- FRP/Elerea/Clocked.hs +399/−0
- FRP/Elerea/Delayed.hs +368/−0
- FRP/Elerea/Experimental.hs +0/−102
- FRP/Elerea/Experimental/Delayed.hs +0/−368
- FRP/Elerea/Experimental/Param.hs +0/−357
- FRP/Elerea/Experimental/Simple.hs +0/−435
- FRP/Elerea/Graph.hs +0/−171
- FRP/Elerea/Internal.hs +0/−546
- FRP/Elerea/Legacy.hs +122/−0
- FRP/Elerea/Legacy/Graph.hs +169/−0
- FRP/Elerea/Legacy/Internal.hs +546/−0
- FRP/Elerea/Param.hs +357/−0
- FRP/Elerea/Simple.hs +435/−0
- elerea.cabal +8/−8
CHANGES view
@@ -1,3 +1,7 @@+2.0.0 - 100718+* moved experimental branch to the top (version 1 went into legacy status)+* added the clocked version+ 1.2.3 - 100131 * added externalMulti to handle events that can fire several times within a superstep * added a cache to the noise signal for safety reasons, so it lives in SignalGen now
− FRP/Elerea.hs
@@ -1,122 +0,0 @@-{-|--Elerea (Eventless Reactivity) is a simplistic FRP implementation that-parts with the concept of events, and introduces various constructs-that can be used to define completely dynamic higher-order dataflow-networks. The user sees the functionality through a hybrid-monadic-applicative interface, where stateful signals can only be-created through a specialised monad, while most combinators are purely-applicative. The combinators build up a network of interconnected-mutable references in the background. The network is executed-iteratively, where each superstep consists of three phases: sampling,-aging, and finalisation. As an example, the following code is a-possible way to define an approximation of our beloved trig functions:--@- (sine,cosine) <- mdo- s <- integral 0 c- c <- integral 1 (-s)- return (s,c)-@--Note that @integral@ is not a primitive, it can be defined by the user-as a transfer function. A possible implementation that can be used on-any 'Fractional' signal looks like this:--@- integral x0 s = transfer x0 (\\dt x x0 -> x0+x*realToFrac dt) s-@--Head to "FRP.Elerea.Internal" for the implementation details. To get-a general idea how to use the library, check out the sources in the-@elerea-examples@ package.--The "FRP.Elerea.Experimental" branch provides a similar interface with-a rather different underlying structure, which is likely to be more-efficient.---}--module FRP.Elerea- ( DTime, Sink, Signal, SignalMonad- , createSignal, superstep- , external- , stateful, transfer, delay- , sampler, generator- , storeJust, toMaybe- , edge- , keepAlive, (.@.)- , (==@), (/=@), (<@), (<=@), (>=@), (>@)- , (&&@), (||@)- , signalDebug-) where--import Control.Applicative-import FRP.Elerea.Internal--infix 4 ==@, /=@, <@, <=@, >=@, >@-infixr 3 &&@-infixr 2 ||@--{-| A short alternative name for 'keepAlive'. -}--(.@.) :: Signal a -> Signal t -> Signal a-(.@.) = keepAlive--{-| The `edge` transfer function takes a bool signal and emits another-bool signal that turns true only at the moment when there is a rising-edge on the input. -}--edge :: Signal Bool -> SignalMonad (Signal Bool)-edge b = delay True b >>= \db -> return $ (not <$> db) &&@ b--{-| The `storeJust` transfer function behaves as a latch on a 'Maybe'-input: it keeps its state when the input is 'Nothing', and replaces it-with the input otherwise. -}--storeJust :: a -- ^ Initial output- -> Signal (Maybe a) -- ^ Maybe signal to latch on- -> SignalMonad (Signal a)-storeJust x0 s = transfer x0 store s- where store _ Nothing x = x- store _ (Just x) _ = x--{-| Point-wise equality of two signals. -}--(==@) :: Eq a => Signal a -> Signal a -> Signal Bool-(==@) = liftA2 (==)--{-| Point-wise inequality of two signals. -}--(/=@) :: Eq a => Signal a -> Signal a -> Signal Bool-(/=@) = liftA2 (/=)--{-| Point-wise comparison of two signals. -}--(<@) :: Ord a => Signal a -> Signal a -> Signal Bool-(<@) = liftA2 (<)--{-| Point-wise comparison of two signals. -}--(<=@) :: Ord a => Signal a -> Signal a -> Signal Bool-(<=@) = liftA2 (<=)--{-| Point-wise comparison of two signals. -}--(>=@) :: Ord a => Signal a -> Signal a -> Signal Bool-(>=@) = liftA2 (>=)--{-| Point-wise comparison of two signals. -}--(>@) :: Ord a => Signal a -> Signal a -> Signal Bool-(>@) = liftA2 (>)--{-| Point-wise OR of two boolean signals. -}--(||@) :: Signal Bool -> Signal Bool -> Signal Bool-(||@) = liftA2 (||)--{-| Point-wise AND of two boolean signals. -}--(&&@) :: Signal Bool -> Signal Bool -> Signal Bool-(&&@) = liftA2 (&&)
+ FRP/Elerea/Clocked.hs view
@@ -0,0 +1,399 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{-|++This version differs from the simple one in adding associated freeze+control signals ("clocks") to stateful entities to be able to pause+entire subnetworks without having to write all the low-level logic+explicitly. The clocks are fixed to signals upon their creation, and+the 'withClock' function can be used to specify the common clock for+the signals created in a given generator.++A clock signal affects 'delay' elements the following way: if the+clock signal is true, the delay works as usual, otherwise it remembers+its current output and throws away its current input. If we consider+signals to be functions of time (natural numbers), the behaviour of+delay can be described by the following function:++@+ delay x0 s (t_start,clk) t_sample+ | t_start == t_sample = x0+ | t_start \< t_sample = if clk t_sample+ then s (t_sample-1)+ else delay x0 s (t_start (t_sample-1)+ | otherwise = error \"stream doesn't exist yet\"+@++A simple example to create counters operating at different rates using+the same generator:++@+ divisibleBy n x = x \`mod\` n == 0+@++@+ counter = stateful 0 (+1)+@++@+ drift = do+ time \<- counter+ c1 \<- withClock (divisibleBy 2 \<$\> time) counter+ c2 \<- withClock (divisibleBy 3 \<$\> time) counter+ return ((,) \<$\> c1 \<*\> c2)+@++Note that if you want to slow down the drift system defined above, the+naive approach might lead to surprising results:++@+ slowDrift = do+ time \<- counter+ withClock (divisibleBy 2 \<$\> time) drift+@++The problem is that the clocks are also slowed down, and their spikes+double in length. This may or may not be what you want. To overcome+this problem, we can define a clock oblivious edge detector to be used+within the definition of @drift@:++@+ edge = withClock (pure True) . transfer False (\\b b' -> b && not b')+@++@+ drift = do+ time \<- counter+ t2 \<- edge (divisibleBy 2 \<$\> time)+ t3 \<- edge (divisibleBy 3 \<$\> time)+ c1 \<- withClock t2 counter+ c2 \<- withClock t3 counter+ return ((,) \<$\> c1 \<*\> c2)+@++This works because the 'withClock' function overrides any clock+imposed on the generator from outside.++-}++module FRP.Elerea.Clocked+ ( Signal+ , SignalGen+ , start+ , external+ , externalMulti+ , delay+ , generator+ , withClock+ , memo+ , stateful+ , transfer+ , noise+ , getRandom+ ) where++import Control.Applicative+import Control.Concurrent.MVar+import Control.Monad+import Control.Monad.Fix+import Data.IORef+import Data.Maybe+import System.Mem.Weak+import System.Random.Mersenne++-- | A signal can be thought of as a function of type @Nat -> a@,+-- where the argument is the sampling time, and the 'Monad' instance+-- agrees with the intuition (bind corresponds to extracting the+-- current sample).+newtype Signal a = S (IO a) deriving (Functor, Applicative, Monad)++-- | A dynamic set of actions to update a network without breaking+-- consistency.+type UpdatePool = [Weak (IO (),IO ())]++-- | A signal generator is the only source of stateful signals. It+-- can be thought of as a function of type @Nat -> a@, where the+-- result is an arbitrary data structure that can potentially contain+-- new signals, and the argument is the creation time of these new+-- signals. It exposes the 'MonadFix' interface, which makes it+-- possible to define signals in terms of each other.+newtype SignalGen a = SG { unSG :: IORef UpdatePool -> Signal Bool -> 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++instance Applicative SignalGen where+ 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++instance MonadFix SignalGen where+ mfix f = SG $ \p c -> mfix (($c).($p).unSG.f)++-- | 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+-- result. This is the only way to extract a signal generator outside+-- the network, and it is equivalent to passing zero to the function+-- representing the generator.+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++-- | Auxiliary function used by all the primitives that create a+-- mutable variable.+addSignal :: (a -> IO a) -- ^ sampling function+ -> (a -> IO ()) -- ^ aging function+ -> IORef (Phase a) -- ^ the mutable variable behind the signal+ -> 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 ()++ 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++ updateActions <- mkWeak sig (upd,fin) Nothing+ modifyIORef pool (updateActions:)+ return sig++-- | The 'delay' transfer function emits the value of a signal from+-- the previous superstep, starting with the filler value given in the+-- first argument. It can be thought of as the following function+-- (which should also make it clear why the return value is+-- 'SignalGen'):+--+-- @+-- delay x0 s t_start t_sample+-- | t_start == t_sample = x0+-- | t_start < t_sample = s (t_sample-1)+-- | otherwise = error \"Premature sample!\"+-- @+--+-- The way signal generators are extracted ensures that the error can+-- never happen.+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)++ let update x = do x' <- s+ c <- clk+ x' `seq` writeIORef ref (Updated (if c then x' else x) x)++ 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+-- the time of generation. It is possible to create new signals in+-- the monad. It can be thought of as the following function:+--+-- @+-- generator g t_start t_sample = g t_sample 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+-- for efficiency reasons. However, this state is not carried between+-- samples, therefore starting time doesn't matter and can be ignored.+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++ addSignal (const sample) (const (sample >> return ())) ref pool++-- | 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.+withClock :: Signal Bool -> SignalGen a -> SignalGen a+withClock clk (SG g) = SG $ \pool _ -> g pool clk++-- | 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.+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)++ let sample = s >>= \x -> writeIORef ref (Updated undefined x) >> return x++ addSignal (const sample) (const (sample >> return ())) ref pool++-- | A signal that can be directly fed through the sink function+-- returned. This can be used to attach the network to the outer+-- world.+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)++-- | 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.+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)+ )++-- | A pure stateful signal. The initial state is the first output,+-- and every subsequent state is derived from the preceding one by+-- applying a pure transformation. It is equivalent to the following+-- expression:+--+-- @+-- stateful x0 f = 'mfix' $ \sig -> 'delay' x0 (f '<$>' sig)+-- @+stateful :: a -- ^ initial state+ -> (a -> a) -- ^ state transformation+ -> 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 equivalent to the following+-- expression:+--+-- @+-- transfer x0 f s = 'mfix' $ \sig -> 'liftA2' f s '<$>' 'delay' x0 sig+-- @+transfer :: a -- ^ initial internal state+ -> (t -> a -> a) -- ^ state updater function+ -> Signal t -- ^ input signal+ -> SignalGen (Signal a)+transfer x0 f s = mfix $ \sig -> liftA2 f s <$> delay x0 sig++-- | A random signal.+noise :: MTRandom a => SignalGen (Signal a)+noise = memo (S randomIO)++-- | A random source within the 'SignalGen' monad.+getRandom :: MTRandom a => SignalGen a+getRandom = SG (const (const randomIO))++-- The Show instance is only defined for the sake of Num...+instance Show (Signal a) where+ showsPrec _ _ s = "<SIGNAL>" ++ s++-- Equality test is impossible.+instance Eq (Signal a) where+ _ == _ = 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++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"++instance Bounded t => Bounded (Signal t) where+ 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++instance Real t => Real (Signal t) where+ 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"++instance Fractional t => Fractional (Signal t) where+ (/) = 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
+ FRP/Elerea/Delayed.hs view
@@ -0,0 +1,368 @@+{-|++This version differs from the parametric one in introducing autmatic+delays. In practice, if a dependency loop involves a 'transfer'+primitive, it will be resolved during runtime even if transfer+functions are not delayed by default.++The interface of this module differs from the old Elerea in the+following ways:++* the delta time argument is generalised to an arbitrary type, so it+ is possible to do without 'external' altogether in case someone+ wants to do so;++* there is no 'sampler' any more, it is substituted by 'join', as+ signals are monads;++* 'generator' has been conceptually simplified, so it's a more basic+ primitive now;++* all signals are aged regardless of whether they are sampled+ (i.e. their behaviour doesn't depend on the context any more);++* the user needs to cache the results of applicative operations to be+ reused in multiple places explicitly using the 'memo' combinator.++-}++module FRP.Elerea.Delayed+ ( Signal+ , SignalGen+ , start+ , external+ , externalMulti+ , delay+ , stateful+ , transfer+ , memo+ , generator+ , noise+ , getRandom+ , debug+ ) where++import Control.Applicative+import Control.Concurrent.MVar+import Control.Monad+import Control.Monad.Fix+import Data.IORef+import Data.Maybe+import System.Mem.Weak+import System.Random.Mersenne++-- | A signal can be thought of as a function of type @Nat -> a@, and+-- its 'Monad' instance agrees with that intuition. Internally, is+-- represented by a sampling computation.+newtype Signal p a = S { unS :: p -> IO a }++-- | A dynamic set of actions to update a network without breaking+-- consistency.+type UpdatePool p = [Weak (p -> IO (), IO ())]++-- | A signal generator is the only source of stateful signals.+-- Internally, computes a signal structure and adds the new variables+-- to an existing update pool.+newtype SignalGen p a = SG { unSG :: IORef (UpdatePool p) -> IO a }++-- | The phases every signal goes through during a superstep: before+-- or after sampling.+data Phase s a = Ready s | Sampling s | Aged s a++instance Functor (Signal p) where+ fmap = liftM++instance Applicative (Signal p) where+ pure = return+ (<*>) = ap++instance Monad (Signal p) where+ return = S . const . return+ S g >>= f = S $ \p -> g p >>= \x -> unS (f x) p++instance Functor (SignalGen p) where+ fmap = liftM++instance Applicative (SignalGen p) where+ pure = return+ (<*>) = ap++instance Monad (SignalGen p) where+ return = SG . const . return+ SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p++instance MonadFix (SignalGen p) where+ mfix f = SG $ \p -> mfix (($p).unSG.f)++-- | 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 result.+-- The computation accepts a global parameter that will be distributed+-- to all signals. For instance, this can be the time step, if we+-- want to model continuous-time signals.+start :: SignalGen p (Signal p a) -- ^ the generator of the top-level signal+ -> IO (p -> IO a) -- ^ the computation to sample the signal+start (SG gen) = do+ pool <- newIORef []+ (S sample) <- gen pool++ ptrs0 <- readIORef pool+ writeIORef pool []+ (as0,cs0) <- unzip . map fromJust <$> mapM deRefWeak ptrs0+ let ageStatic param = mapM_ ($param) as0+ commitStatic = sequence_ cs0++ return $ \param -> do+ let update [] ptrs age commit = do+ writeIORef pool ptrs+ ageStatic param >> age+ commitStatic >> commit+ update (p:ps) ptrs age commit = do+ r <- deRefWeak p+ case r of+ Nothing -> update ps ptrs age commit+ Just (a,c) -> update ps (p:ptrs) (age >> a param) (commit >> c)++ res <- sample param+ ptrs <- readIORef pool+ update ptrs [] (return ()) (return ())+ return res++-- | Auxiliary function used by all the primitives that create a+-- mutable variable.+addSignal :: (p -> Phase s a -> IO a) -- ^ sampling function+ -> (p -> Phase s a -> IO ()) -- ^ aging function+ -> IORef (Phase s a) -- ^ the mutable variable behind the signal+ -> IORef (UpdatePool p) -- ^ the pool of update actions+ -> IO (Signal p a)+addSignal sample age ref pool = do+ let commit (Aged s _) = Ready s+ commit _ = error "commit error: signal not aged"++ sig = S $ \p -> readIORef ref >>= sample p++ update <- mkWeak sig (\p -> readIORef ref >>= age p, modifyIORef ref commit) Nothing+ modifyIORef pool (update:)+ return sig++-- | The 'delay' transfer function emits the value of a signal from+-- the previous superstep, starting with the filler value given in the+-- first argument.+delay :: a -- ^ initial output+ -> Signal p a -- ^ the signal to delay+ -> SignalGen p (Signal p a)+delay x0 (S s) = SG $ \pool -> do+ ref <- newIORef (Ready x0)++ let sample _ (Ready x) = return x+ sample _ (Aged _ x) = return x+ sample _ _ = error "sampling eror: delay"++ age p (Ready x) = s p >>= \x' -> x' `seq` writeIORef ref (Aged x' x)+ age _ _ = return ()++ addSignal sample age ref pool++-- | Memoising combinator. It can be used to cache results of+-- applicative combinators in case they are used in several places.+-- Other than that, it is equivalent to 'return'.+memo :: Signal p a -- ^ signal to memoise+ -> SignalGen p (Signal p a)+memo (S s) = SG $ \pool -> do+ ref <- newIORef (Ready undefined)++ let sample p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x) >> return x+ sample _ (Aged _ x) = return x+ sample _ _ = error "sampling eror: memo"++ age p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x)+ age _ _ = return ()++ addSignal sample age ref pool++-- | A reactive signal that takes the value to output from a monad+-- carried by its input. It is possible to create new signals in the+-- monad.+generator :: Signal p (SignalGen p a) -- ^ a stream of generators to potentially run+ -> SignalGen p (Signal p a)+generator (S gen) = SG $ \pool -> do+ ref <- newIORef (Ready undefined)++ let next p = ($pool).unSG =<< gen p++ sample p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x') >> return x'+ sample _ (Aged _ x) = return x+ sample _ _ = error "sampling eror: generator"++ age p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x')+ age _ _ = return ()++ addSignal sample age ref pool++-- | A signal that can be directly fed through the sink function+-- returned. This can be used to attach the network to the outer+-- world. Note that this is optional, as all the input of the network+-- can be fed in through the global parameter, although that is not+-- really convenient for many signals.+external :: a -- ^ initial value+ -> IO (Signal p a, a -> IO ()) -- ^ the signal and an IO function to feed it+external x = do+ ref <- newIORef x+ return (S (const (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.+externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ()) -- ^ a generator for the event signal and the associated sink+externalMulti = do+ var <- newMVar []+ return (SG $ \pool -> do+ let sig = S $ const (readMVar var)+ update <- mkWeak sig (const (return ()),takeMVar var >> putMVar var []) Nothing+ modifyIORef pool (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 following output is calculated from the previous one and+-- the value of the global parameter.+stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)+stateful x0 f = SG $ \pool -> do+ ref <- newIORef (Ready x0)++ let sample _ (Ready x) = return x+ sample _ (Aged _ x) = return x+ sample _ _ = error "sampling eror: stateful"++ age p (Ready x) = let x' = f p x in x' `seq` writeIORef ref (Aged x' x)+ age _ _ = return ()++ addSignal sample age ref pool++-- | 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. Every output is derived from the current value of the+-- input signal, the global parameter and the previous output. The+-- only exception is when a transfer function sits in a loop without a+-- delay. In this case, a delay will be inserted at a single place+-- during runtime (i.e. the previous output of the node affected will+-- be reused) to resolve the circular dependency.+transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)+transfer x0 f (S s) = SG $ \pool -> do+ ref <- newIORef (Ready x0)++ let sample p (Ready x) = do+ writeIORef ref (Sampling x)+ y <- s p+ let x' = f p y x+ x' `seq` writeIORef ref (Aged x' x')+ return x'+ sample _ (Sampling x) = return x -- Reusing previous output: automatic delay+ sample _ (Aged _ x) = return x++ age p (Ready x) = do+ y <- s p+ let x' = f p y x+ x' `seq` writeIORef ref (Aged x' x')+ age _ _ = return () -- If it is Sampling, we'll error out later++ addSignal sample age ref pool++-- | A random signal.+noise :: MTRandom a => SignalGen p (Signal p a)+noise = memo (S (const randomIO))++-- | A random source within the 'SignalGen' monad.+getRandom :: MTRandom a => SignalGen p a+getRandom = SG (const randomIO)++-- | A printing action within the 'SignalGen' monad.+debug :: String -> SignalGen p ()+debug = SG . const . putStrLn++-- | The @Show@ instance is only defined for the sake of 'Num'...+instance Show (Signal p a) where+ showsPrec _ _ s = "<SIGNAL>" ++ s++-- | Equality test is impossible.+instance Eq (Signal p a) where+ _ == _ = False++-- | Error message for unimplemented instance functions.+unimp :: String -> a+unimp = error . ("Signal: "++)++instance Ord t => Ord (Signal p t) where+ compare = unimp "compare"+ min = liftA2 min+ max = liftA2 max++instance Enum t => Enum (Signal p 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"++instance Bounded t => Bounded (Signal p t) where+ minBound = pure minBound+ maxBound = pure maxBound++instance Num t => Num (Signal p t) where+ (+) = liftA2 (+)+ (-) = liftA2 (-)+ (*) = liftA2 (*)+ signum = fmap signum+ abs = fmap abs+ negate = fmap negate+ fromInteger = pure . fromInteger++instance Real t => Real (Signal p t) where+ toRational = unimp "toRational"++instance Integral t => Integral (Signal p 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"++instance Fractional t => Fractional (Signal p t) where+ (/) = liftA2 (/)+ recip = fmap recip+ fromRational = pure . fromRational++instance Floating t => Floating (Signal p 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
− FRP/Elerea/Experimental.hs
@@ -1,102 +0,0 @@-{-|--This branch is an experimental version of Elerea that does not build-an actual graph of the dataflow network, just maintains a list of-actions to update signals. Each signal consists of a mutable-variable, an aging action and a finalising action. The variables can-only be accessed through a sampling action, and they are only referred-to in the corresponding aging and finalising action. These actions-can be accessed through weak pointers that get invalidated when all-other references to the corresponding variable are lost.--This approach has both advantages and disadvantages. On the plus-side, we don't have to create nodes for the applicative operations any-more, because they can be encoded in the sampling actions in an-efficient way. Also, since we have a list of independent actions to-update the network, we can achieve nearly perfect parallelism, just-like with a raytracer: all we need is a clever way of assigning-actions to processing units. The downside is that we have to-explicitly memoise the results of applicative operations in case they-are used more than once.--The modules below implement the basic idea in two variations:--* "FRP.Elerea.Experimental.Simple" provides discrete signals,- i.e. streams;--* "FRP.Elerea.Experimental.Param" adds an extra parameter that's- accessible to every node during the update, which can be used to- provide a time step between samplings, or any other input necessary;--* "FRP.Elerea.Experimental.Delayed" adds automatic delays, which- violates referential transparency in a limited way, but improves the- usability of the API when this doesn't matter.--This module exports the delayed version along with a few utility-functions.---}--module FRP.Elerea.Experimental- ( module FRP.Elerea.Experimental.Delayed- , (-->)- , edge- , (==@), (/=@), (<@), (<=@), (>=@), (>@)- , (&&@), (||@)- ) where--import Control.Applicative-import FRP.Elerea.Experimental.Delayed--infix 4 ==@, /=@, <@, <=@, >=@, >@-infixr 3 &&@-infixr 2 ||@-infix 2 -->---- | The 'edge' transfer function takes a bool signal and emits--- another bool signal that turns true only at the moment when there--- is a rising edge on the input.-edge :: Signal p Bool -> SignalGen p (Signal p Bool)-edge b = delay True b >>= \db -> return $ (not <$> db) &&@ b---- | The '-->' transfer function behaves as a latch on a 'Maybe'--- input: it keeps its state when the input is 'Nothing', and replaces--- it with the input otherwise.-(-->) :: a -- ^ Initial output- -> Signal p (Maybe a) -- ^ Maybe signal to latch on- -> SignalGen p (Signal p a)-x0 --> s = transfer x0 store s- where store _ Nothing x = x- store _ (Just x) _ = x---- | Point-wise equality of two signals.-(==@) :: Eq a => Signal p a -> Signal p a -> Signal p Bool-(==@) = liftA2 (==)---- | Point-wise inequality of two signals.-(/=@) :: Eq a => Signal p a -> Signal p a -> Signal p Bool-(/=@) = liftA2 (/=)---- | Point-wise comparison of two signals.-(<@) :: Ord a => Signal p a -> Signal p a -> Signal p Bool-(<@) = liftA2 (<)---- | Point-wise comparison of two signals.-(<=@) :: Ord a => Signal p a -> Signal p a -> Signal p Bool-(<=@) = liftA2 (<=)---- | Point-wise comparison of two signals.-(>=@) :: Ord a => Signal p a -> Signal p a -> Signal p Bool-(>=@) = liftA2 (>=)---- | Point-wise comparison of two signals.-(>@) :: Ord a => Signal p a -> Signal p a -> Signal p Bool-(>@) = liftA2 (>)---- | Point-wise OR of two boolean signals.-(||@) :: Signal p Bool -> Signal p Bool -> Signal p Bool-s1 ||@ s2 = s1 >>= \b -> if b then return True else s2---- | Point-wise AND of two boolean signals.-(&&@) :: Signal p Bool -> Signal p Bool -> Signal p Bool-s1 &&@ s2 = s1 >>= \b -> if b then s2 else return False
− FRP/Elerea/Experimental/Delayed.hs
@@ -1,368 +0,0 @@-{-|--This version differs from the parametric one in introducing autmatic-delays. In practice, if a dependency loop involves a 'transfer'-primitive, it will be resolved during runtime even if transfer-functions are not delayed by default.--The interface of this module differs from the old Elerea in the-following ways:--* the delta time argument is generalised to an arbitrary type, so it- is possible to do without 'external' altogether in case someone- wants to do so;--* there is no 'sampler' any more, it is substituted by 'join', as- signals are monads;--* 'generator' has been conceptually simplified, so it's a more basic- primitive now;--* all signals are aged regardless of whether they are sampled- (i.e. their behaviour doesn't depend on the context any more);--* the user needs to cache the results of applicative operations to be- reused in multiple places explicitly using the 'memo' combinator.---}--module FRP.Elerea.Experimental.Delayed- ( Signal- , SignalGen- , start- , external- , externalMulti- , delay- , stateful- , transfer- , memo- , generator- , noise- , getRandom- , debug- ) where--import Control.Applicative-import Control.Concurrent.MVar-import Control.Monad-import Control.Monad.Fix-import Data.IORef-import Data.Maybe-import System.Mem.Weak-import System.Random.Mersenne---- | A signal can be thought of as a function of type @Nat -> a@, and--- its 'Monad' instance agrees with that intuition. Internally, is--- represented by a sampling computation.-newtype Signal p a = S { unS :: p -> IO a }---- | A dynamic set of actions to update a network without breaking--- consistency.-type UpdatePool p = [Weak (p -> IO (), IO ())]---- | A signal generator is the only source of stateful signals.--- Internally, computes a signal structure and adds the new variables--- to an existing update pool.-newtype SignalGen p a = SG { unSG :: IORef (UpdatePool p) -> IO a }---- | The phases every signal goes through during a superstep: before--- or after sampling.-data Phase s a = Ready s | Sampling s | Aged s a--instance Functor (Signal p) where- fmap = liftM--instance Applicative (Signal p) where- pure = return- (<*>) = ap--instance Monad (Signal p) where- return = S . const . return- S g >>= f = S $ \p -> g p >>= \x -> unS (f x) p--instance Functor (SignalGen p) where- fmap = liftM--instance Applicative (SignalGen p) where- pure = return- (<*>) = ap--instance Monad (SignalGen p) where- return = SG . const . return- SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p--instance MonadFix (SignalGen p) where- mfix f = SG $ \p -> mfix (($p).unSG.f)---- | 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 result.--- The computation accepts a global parameter that will be distributed--- to all signals. For instance, this can be the time step, if we--- want to model continuous-time signals.-start :: SignalGen p (Signal p a) -- ^ the generator of the top-level signal- -> IO (p -> IO a) -- ^ the computation to sample the signal-start (SG gen) = do- pool <- newIORef []- (S sample) <- gen pool-- ptrs0 <- readIORef pool- writeIORef pool []- (as0,cs0) <- unzip . map fromJust <$> mapM deRefWeak ptrs0- let ageStatic param = mapM_ ($param) as0- commitStatic = sequence_ cs0-- return $ \param -> do- let update [] ptrs age commit = do- writeIORef pool ptrs- ageStatic param >> age- commitStatic >> commit- update (p:ps) ptrs age commit = do- r <- deRefWeak p- case r of- Nothing -> update ps ptrs age commit- Just (a,c) -> update ps (p:ptrs) (age >> a param) (commit >> c)-- res <- sample param- ptrs <- readIORef pool- update ptrs [] (return ()) (return ())- return res---- | Auxiliary function used by all the primitives that create a--- mutable variable.-addSignal :: (p -> Phase s a -> IO a) -- ^ sampling function- -> (p -> Phase s a -> IO ()) -- ^ aging function- -> IORef (Phase s a) -- ^ the mutable variable behind the signal- -> IORef (UpdatePool p) -- ^ the pool of update actions- -> IO (Signal p a)-addSignal sample age ref pool = do- let commit (Aged s _) = Ready s- commit _ = error "commit error: signal not aged"-- sig = S $ \p -> readIORef ref >>= sample p-- update <- mkWeak sig (\p -> readIORef ref >>= age p, modifyIORef ref commit) Nothing- modifyIORef pool (update:)- return sig---- | The 'delay' transfer function emits the value of a signal from--- the previous superstep, starting with the filler value given in the--- first argument.-delay :: a -- ^ initial output- -> Signal p a -- ^ the signal to delay- -> SignalGen p (Signal p a)-delay x0 (S s) = SG $ \pool -> do- ref <- newIORef (Ready x0)-- let sample _ (Ready x) = return x- sample _ (Aged _ x) = return x- sample _ _ = error "sampling eror: delay"-- age p (Ready x) = s p >>= \x' -> x' `seq` writeIORef ref (Aged x' x)- age _ _ = return ()-- addSignal sample age ref pool---- | Memoising combinator. It can be used to cache results of--- applicative combinators in case they are used in several places.--- Other than that, it is equivalent to 'return'.-memo :: Signal p a -- ^ signal to memoise- -> SignalGen p (Signal p a)-memo (S s) = SG $ \pool -> do- ref <- newIORef (Ready undefined)-- let sample p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x) >> return x- sample _ (Aged _ x) = return x- sample _ _ = error "sampling eror: memo"-- age p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x)- age _ _ = return ()-- addSignal sample age ref pool---- | A reactive signal that takes the value to output from a monad--- carried by its input. It is possible to create new signals in the--- monad.-generator :: Signal p (SignalGen p a) -- ^ a stream of generators to potentially run- -> SignalGen p (Signal p a)-generator (S gen) = SG $ \pool -> do- ref <- newIORef (Ready undefined)-- let next p = ($pool).unSG =<< gen p-- sample p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x') >> return x'- sample _ (Aged _ x) = return x- sample _ _ = error "sampling eror: generator"-- age p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x')- age _ _ = return ()-- addSignal sample age ref pool---- | A signal that can be directly fed through the sink function--- returned. This can be used to attach the network to the outer--- world. Note that this is optional, as all the input of the network--- can be fed in through the global parameter, although that is not--- really convenient for many signals.-external :: a -- ^ initial value- -> IO (Signal p a, a -> IO ()) -- ^ the signal and an IO function to feed it-external x = do- ref <- newIORef x- return (S (const (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.-externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ()) -- ^ a generator for the event signal and the associated sink-externalMulti = do- var <- newMVar []- return (SG $ \pool -> do- let sig = S $ const (readMVar var)- update <- mkWeak sig (const (return ()),takeMVar var >> putMVar var []) Nothing- modifyIORef pool (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 following output is calculated from the previous one and--- the value of the global parameter.-stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)-stateful x0 f = SG $ \pool -> do- ref <- newIORef (Ready x0)-- let sample _ (Ready x) = return x- sample _ (Aged _ x) = return x- sample _ _ = error "sampling eror: stateful"-- age p (Ready x) = let x' = f p x in x' `seq` writeIORef ref (Aged x' x)- age _ _ = return ()-- addSignal sample age ref pool---- | 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. Every output is derived from the current value of the--- input signal, the global parameter and the previous output. The--- only exception is when a transfer function sits in a loop without a--- delay. In this case, a delay will be inserted at a single place--- during runtime (i.e. the previous output of the node affected will--- be reused) to resolve the circular dependency.-transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)-transfer x0 f (S s) = SG $ \pool -> do- ref <- newIORef (Ready x0)-- let sample p (Ready x) = do- writeIORef ref (Sampling x)- y <- s p- let x' = f p y x- x' `seq` writeIORef ref (Aged x' x')- return x'- sample _ (Sampling x) = return x -- Reusing previous output: automatic delay- sample _ (Aged _ x) = return x-- age p (Ready x) = do- y <- s p- let x' = f p y x- x' `seq` writeIORef ref (Aged x' x')- age _ _ = return () -- If it is Sampling, we'll error out later-- addSignal sample age ref pool---- | A random signal.-noise :: MTRandom a => SignalGen p (Signal p a)-noise = memo (S (const randomIO))---- | A random source within the 'SignalGen' monad.-getRandom :: MTRandom a => SignalGen p a-getRandom = SG (const randomIO)---- | A printing action within the 'SignalGen' monad.-debug :: String -> SignalGen p ()-debug = SG . const . putStrLn---- | The @Show@ instance is only defined for the sake of 'Num'...-instance Show (Signal p a) where- showsPrec _ _ s = "<SIGNAL>" ++ s---- | Equality test is impossible.-instance Eq (Signal p a) where- _ == _ = False---- | Error message for unimplemented instance functions.-unimp :: String -> a-unimp = error . ("Signal: "++)--instance Ord t => Ord (Signal p t) where- compare = unimp "compare"- min = liftA2 min- max = liftA2 max--instance Enum t => Enum (Signal p 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"--instance Bounded t => Bounded (Signal p t) where- minBound = pure minBound- maxBound = pure maxBound--instance Num t => Num (Signal p t) where- (+) = liftA2 (+)- (-) = liftA2 (-)- (*) = liftA2 (*)- signum = fmap signum- abs = fmap abs- negate = fmap negate- fromInteger = pure . fromInteger--instance Real t => Real (Signal p t) where- toRational = unimp "toRational"--instance Integral t => Integral (Signal p 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"--instance Fractional t => Fractional (Signal p t) where- (/) = liftA2 (/)- recip = fmap recip- fromRational = pure . fromRational--instance Floating t => Floating (Signal p 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
− FRP/Elerea/Experimental/Param.hs
@@ -1,357 +0,0 @@-{-|--This version differs from the simple one in providing an extra-argument to the sampling action that will be globally distributed to-every node and can be used to update the state. For instance, it can-hold the time step between the two samplings, but it could also encode-all the external input to the system.--The interface of this module differs from the old Elerea in the-following ways:--* the delta time argument is generalised to an arbitrary type, so it- is possible to do without 'external' altogether in case someone- wants to do so;--* there is no 'sampler' any more, it is substituted by 'join', as- signals are monads;--* 'generator' has been conceptually simplified, so it's a more basic- primitive now;--* there is no automatic delay in order to preserve semantic soundness- (e.g. the monad laws for signals);--* all signals are aged regardless of whether they are sampled- (i.e. their behaviour doesn't depend on the context any more);--* the user needs to cache the results of applicative operations to be- reused in multiple places explicitly using the 'memo' combinator.---}--module FRP.Elerea.Experimental.Param- ( Signal- , SignalGen- , start- , external- , externalMulti- , delay- , stateful- , transfer- , memo- , generator- , noise- , getRandom- , debug- ) where--import Control.Applicative-import Control.Concurrent.MVar-import Control.Monad-import Control.Monad.Fix-import Data.IORef-import Data.Maybe-import System.Mem.Weak-import System.Random.Mersenne---- | A signal can be thought of as a function of type @Nat -> a@, and--- its 'Monad' instance agrees with that intuition. Internally, is--- represented by a sampling computation.-newtype Signal p a = S { unS :: p -> IO a }---- | A dynamic set of actions to update a network without breaking--- consistency.-type UpdatePool p = [Weak (p -> IO (), IO ())]---- | A signal generator is the only source of stateful signals.--- Internally, computes a signal structure and adds the new variables--- to an existing update pool.-newtype SignalGen p a = SG { unSG :: IORef (UpdatePool p) -> IO a }---- | The phases every signal goes through during a superstep: before--- or after sampling.-data Phase s a = Ready s | Aged s a--instance Functor (Signal p) where- fmap = liftM--instance Applicative (Signal p) where- pure = return- (<*>) = ap--instance Monad (Signal p) where- return = S . const . return- S g >>= f = S $ \p -> g p >>= \x -> unS (f x) p--instance Functor (SignalGen p) where- fmap = liftM--instance Applicative (SignalGen p) where- pure = return- (<*>) = ap--instance Monad (SignalGen p) where- return = SG . const . return- SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p--instance MonadFix (SignalGen p) where- mfix f = SG $ \p -> mfix (($p).unSG.f)---- | 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--- result. The computation accepts a global parameter that will be--- distributed to all signals. For instance, this can be the time--- step, if we want to model continuous-time signals.-start :: SignalGen p (Signal p a) -- ^ the generator of the top-level signal- -> IO (p -> IO a) -- ^ the computation to sample the signal-start (SG gen) = do- pool <- newIORef []- (S sample) <- gen pool-- ptrs0 <- readIORef pool- writeIORef pool []- (as0,cs0) <- unzip . map fromJust <$> mapM deRefWeak ptrs0- let ageStatic param = mapM_ ($param) as0- commitStatic = sequence_ cs0-- return $ \param -> do- let update [] ptrs age commit = do- writeIORef pool ptrs- ageStatic param >> age- commitStatic >> commit- update (p:ps) ptrs age commit = do- r <- deRefWeak p- case r of- Nothing -> update ps ptrs age commit- Just (a,c) -> update ps (p:ptrs) (age >> a param) (commit >> c)-- res <- sample param- ptrs <- readIORef pool- update ptrs [] (return ()) (return ())- return res---- | Auxiliary function used by all the primitives that create a--- mutable variable.-addSignal :: (p -> Phase s a -> IO a) -- ^ sampling function- -> (p -> Phase s a -> IO ()) -- ^ aging function- -> IORef (Phase s a) -- ^ the mutable variable behind the signal- -> IORef (UpdatePool p) -- ^ the pool of update actions- -> IO (Signal p a)-addSignal sample age ref pool = do- let commit (Aged s _) = Ready s- commit _ = error "commit error: signal not aged"-- sig = S $ \p -> readIORef ref >>= sample p-- update <- mkWeak sig (\p -> readIORef ref >>= age p, modifyIORef ref commit) Nothing- modifyIORef pool (update:)- return sig---- | The 'delay' transfer function emits the value of a signal from--- the previous superstep, starting with the filler value given in the--- first argument.-delay :: a -- ^ initial output- -> Signal p a -- ^ the signal to delay- -> SignalGen p (Signal p a)-delay x0 (S s) = SG $ \pool -> do- ref <- newIORef (Ready x0)-- let sample _ (Ready x) = return x- sample _ (Aged _ x) = return x-- age p (Ready x) = s p >>= \x' -> x' `seq` writeIORef ref (Aged x' x)- age _ _ = return ()-- addSignal sample age ref pool---- | Memoising combinator. It can be used to cache results of--- applicative combinators in case they are used in several--- places. Other than that, it is equivalent to 'return'.-memo :: Signal p a -- ^ signal to memoise- -> SignalGen p (Signal p a)-memo (S s) = SG $ \pool -> do- ref <- newIORef (Ready undefined)-- let sample p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x) >> return x- sample _ (Aged _ x) = return x-- age p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x)- age _ _ = return ()-- addSignal sample age ref pool---- | A reactive signal that takes the value to output from a monad--- carried by its input. It is possible to create new signals in the--- monad.-generator :: Signal p (SignalGen p a) -- ^ a stream of generators to potentially run- -> SignalGen p (Signal p a)-generator (S gen) = SG $ \pool -> do- ref <- newIORef (Ready undefined)-- let next p = ($pool).unSG =<< gen p-- sample p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x') >> return x'- sample _ (Aged _ x) = return x-- age p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x')- age _ _ = return ()-- addSignal sample age ref pool---- | A signal that can be directly fed through the sink function--- returned. This can be used to attach the network to the outer--- world. Note that this is optional, as all the input of the network--- can be fed in through the global parameter, although that is not--- really convenient for many signals.-external :: a -- ^ initial value- -> IO (Signal p a, a -> IO ()) -- ^ the signal and an IO function to feed it-external x = do- ref <- newIORef x- return (S (const (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.-externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ()) -- ^ a generator for the event signal and the associated sink-externalMulti = do- var <- newMVar []- return (SG $ \pool -> do- let sig = S $ const (readMVar var)- update <- mkWeak sig (const (return ()),takeMVar var >> putMVar var []) Nothing- modifyIORef pool (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 following output is calculated from the previous one and--- the value of the global parameter.-stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)-stateful x0 f = SG $ \pool -> do- ref <- newIORef (Ready x0)-- let sample _ (Ready x) = return x- sample _ (Aged _ x) = return x-- age p (Ready x) = let x' = f p x in x' `seq` writeIORef ref (Aged x' x)- age _ _ = return ()-- addSignal sample age ref pool---- | 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. Every output is derived from the current value of the--- input signal, the global parameter and the previous output.-transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)-transfer x0 f (S s) = SG $ \pool -> do- ref <- newIORef (Ready x0)-- let sample p (Ready x) = s p >>= \y -> let x' = f p y x in- x' `seq` writeIORef ref (Aged x' x') >> return x'- sample _ (Aged _ x) = return x-- age p (Ready x) = s p >>= \y -> let x' = f p y x in- x' `seq` writeIORef ref (Aged x' x')- age _ _ = return ()-- addSignal sample age ref pool---- | A random signal.-noise :: MTRandom a => SignalGen p (Signal p a)-noise = memo (S (const randomIO))---- | A random source within the 'SignalGen' monad.-getRandom :: MTRandom a => SignalGen p a-getRandom = SG (const randomIO)---- | A printing action within the 'SignalGen' monad.-debug :: String -> SignalGen p ()-debug = SG . const . putStrLn---- | The @Show@ instance is only defined for the sake of 'Num'...-instance Show (Signal p a) where- showsPrec _ _ s = "<SIGNAL>" ++ s---- | Equality test is impossible.-instance Eq (Signal p a) where- _ == _ = False---- | Error message for unimplemented instance functions.-unimp :: String -> a-unimp = error . ("Signal: "++)--instance Ord t => Ord (Signal p t) where- compare = unimp "compare"- min = liftA2 min- max = liftA2 max--instance Enum t => Enum (Signal p 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"--instance Bounded t => Bounded (Signal p t) where- minBound = pure minBound- maxBound = pure maxBound--instance Num t => Num (Signal p t) where- (+) = liftA2 (+)- (-) = liftA2 (-)- (*) = liftA2 (*)- signum = fmap signum- abs = fmap abs- negate = fmap negate- fromInteger = pure . fromInteger--instance Real t => Real (Signal p t) where- toRational = unimp "toRational"--instance Integral t => Integral (Signal p 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"--instance Fractional t => Fractional (Signal p t) where- (/) = liftA2 (/)- recip = fmap recip- fromRational = pure . fromRational--instance Floating t => Floating (Signal p 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
− FRP/Elerea/Experimental/Simple.hs
@@ -1,435 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--{-|--This module provides leak-free and referentially transparent-higher-order discrete signals. For a not entirely trivial example,-let's create a dynamic collection of countdown timers, where each-expired timer is removed from the collection. First of all, we'll-need a simple tester function:--@- sigtest gen = 'replicateM' 15 '=<<' 'start' gen-@--We can try it with a trivial example:--@- \> sigtest $ 'stateful' 2 (+3)- [2,5,8,11,14,17,20,23,26,29,32,35,38,41,44,47]-@--Our first definition will be a signal representing a simple named-timer:--@- countdown :: String -\> Int -\> SignalGen (Signal (String,Maybe Int))- countdown name t = do- let tick prev = do { t \<- prev ; 'guard' (t \> 0) ; 'return' (t-1) }- timer \<- 'stateful' (Just t) tick- 'return' ((,) name '<$>' timer)-@--Let's see if it works:--@- \> sigtest $ countdown \"foo\" 4- [(\"foo\",Just 4),(\"foo\",Just 3),(\"foo\",Just 2),(\"foo\",Just 1),(\"foo\",Just 0),- (\"foo\",Nothing),(\"foo\",Nothing),(\"foo\",Nothing),...]-@--Next, we will define a timer source that takes a list of timer names,-starting values and start times and creates a signal that delivers the-list of new timers at every point:--@- timerSource :: [(String, Int, Int)] -\> SignalGen (Signal [Signal (String, Maybe Int)])- timerSource ts = do- let gen t = 'mapM' ('uncurry' countdown) newTimers- where newTimers = [(n,v) | (n,v,st) \<- ts, st == t]- cnt \<- 'stateful' 0 (+1)- 'generator' (gen '<$>' cnt)-@--Now we need to encapsulate the timer source signal in another signal-expression that takes care of maintaining the list of live timers.-Since working with dynamic collections is a recurring task, let's-define a generic combinator that maintains a dynamic list of signals-given a source and a test that tells from the output of each signal-whether it should be kept. We can use @mdo@ expressions (a variant of-@do@ expressions allowing forward references) as syntactic sugar for-'mfix' to make life easier:--@- collection :: Signal [Signal a] -\> (a -\> Bool) -\> SignalGen (Signal [a])- collection source isAlive = mdo- sig \<- 'delay' [] ('map' 'snd' '<$>' collWithVals')- coll \<- 'memo' ('liftA2' (++) source sig)- let collWithVals = 'zip' '<$>' ('sequence' '=<<' coll) '<*>' coll- collWithVals' \<- 'memo' ('filter' (isAlive . 'fst') '<$>' collWithVals)- 'return' $ 'map' 'fst' '<$>' collWithVals'-@--We need recursion to define the @coll@ signal as a delayed version of-its continuation, which does not contain signals that need to be-removed in the current sample. At every point of time the running-collection is concatenated with the source. We define @collWithVals@,-which simply pairs up every signal with its current output. The-output is obtained by extracting the current value of the signal-container and sampling each element with 'sequence'. We can then-derive @collWithVals'@, which contains only the signals that must be-kept for the next round along with their output. Both @coll@ and-@collWithVals'@ have to be memoised, because they are used more than-once (the program would work without that, but it would recalculate-both signals each time they are used). By throwing out the respective-parts, we can get both the final output and the collection for the-next step (@coll'@).--Now we can easily finish the original task:--@- timers :: [(String, Int, Int)] -\> SignalGen (Signal [(String, Int)])- timers timerData = do- src \<- timerSource timerData- getOutput '<$>' collection src ('isJust' . 'snd')- where getOutput = 'fmap' ('map' (\\(name,Just val) -> (name,val)))-@--As a test, we can start four timers: /a/ at t=0 with value 3, /b/ and-/c/ at t=1 with values 5 and 3, and /d/ at t=3 with value 4:--@- \> sigtest $ timers [(\"a\",3,0),(\"b\",5,1),(\"c\",3,1),(\"d\",4,3)]- [[(\"a\",3)],[(\"b\",5),(\"c\",3),(\"a\",2)],[(\"b\",4),(\"c\",2),(\"a\",1)],- [(\"d\",4),(\"b\",3),(\"c\",1),(\"a\",0)],[(\"d\",3),(\"b\",2),(\"c\",0)],- [(\"d\",2),(\"b\",1)],[(\"d\",1),(\"b\",0)],[(\"d\",0)],[],[],[],[],[],[],[]]-@--If the noise of the applicative lifting operators feels annoying, she-(<http://personal.cis.strath.ac.uk/~conor/pub/she/>) comes to the-save. Among other features it provides idiom brackets, which can-substitute the explicit lifting. For instance, it allows us to define-@collection@ this way:--@- collection :: Stream [Stream a] -> (a -> Bool) -> StreamGen (Stream [a])- collection source isAlive = mdo- sig \<- 'delay' [] (|'map' ~'snd' collWithVals'|)- coll \<- 'memo' (|source ++ sig|)- collWithVals' \<- 'memo' (|'filter' ~(isAlive . 'fst') (|'zip' ('sequence' '=<<' coll) coll|)|)- 'return' (|'map' ~'fst' collWithVals'|)-@---}--module FRP.Elerea.Experimental.Simple- ( Signal- , SignalGen- , start- , external- , externalMulti- , delay- , generator- , memo- , stateful- , transfer- , noise- , getRandom- ) where--import Control.Applicative-import Control.Concurrent.MVar-import Control.Monad-import Control.Monad.Fix-import Data.IORef-import Data.Maybe-import System.Mem.Weak-import System.Random.Mersenne---- | A signal can be thought of as a function of type @Nat -> a@,--- where the argument is the sampling time, and the 'Monad' instance--- agrees with the intuition (bind corresponds to extracting the--- current sample).-newtype Signal a = S (IO a) deriving (Functor, Applicative, Monad)---- | A dynamic set of actions to update a network without breaking--- consistency.-type UpdatePool = [Weak (IO (),IO ())]---- | A signal generator is the only source of stateful signals. It--- can be thought of as a function of type @Nat -> a@, where the--- result is an arbitrary data structure that can potentially contain--- new signals, and the argument is the creation time of these new--- signals. It exposes the 'MonadFix' interface, which makes it--- possible to define signals in terms of each other.-newtype SignalGen a = SG { unSG :: 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--instance Applicative SignalGen where- pure = return- (<*>) = ap--instance Monad SignalGen where- return = SG . const . return- SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p--instance MonadFix SignalGen where- mfix f = SG $ \p -> mfix (($p).unSG.f)---- | 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--- result. This is the only way to extract a signal generator outside--- the network, and it is equivalent to passing zero to the function--- representing the generator.-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- 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---- | Auxiliary function used by all the primitives that create a--- mutable variable.-addSignal :: (a -> IO a) -- ^ sampling function- -> (a -> IO ()) -- ^ aging function- -> IORef (Phase a) -- ^ the mutable variable behind the signal- -> 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 ()-- 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-- updateActions <- mkWeak sig (upd,fin) Nothing- modifyIORef pool (updateActions:)- return sig---- | The 'delay' transfer function emits the value of a signal from--- the previous superstep, starting with the filler value given in the--- first argument. It can be thought of as the following function--- (which should also make it clear why the return value is--- 'SignalGen'):------ @--- delay x0 s t_start t_sample--- | t_start == t_sample = x0--- | t_start < t_sample = s (t_sample-1)--- | otherwise = error \"Premature sample!\"--- @------ The way signal generators are extracted ensures that the error can--- never happen.-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 -> do- ref <- newIORef (Ready x0)-- let update x = s >>= \x' -> x' `seq` writeIORef ref (Updated x' x)-- 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--- the time of generation. It is possible to create new signals in--- the monad. It can be thought of as the following function:------ @--- generator g t_start t_sample = g t_sample 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--- for efficiency reasons. However, this state is not carried between--- samples, therefore starting time doesn't matter and can be ignored.-generator :: Signal (SignalGen a) -- ^ the signal of generators to run- -> SignalGen (Signal a) -- ^ the signal of generated structures-generator (S s) = SG $ \pool -> do- ref <- newIORef (Ready undefined)-- let sample = do SG g <- s- x <- g pool- writeIORef ref (Updated undefined x)- return x-- addSignal (const sample) (const (sample >> return ())) ref pool---- | 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.-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)-- let sample = s >>= \x -> writeIORef ref (Updated undefined x) >> return x-- addSignal (const sample) (const (sample >> return ())) ref pool---- | A signal that can be directly fed through the sink function--- returned. This can be used to attach the network to the outer--- world.-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)---- | 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.-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)- )---- | A pure stateful signal. The initial state is the first output,--- and every subsequent state is derived from the preceding one by--- applying a pure transformation. It is equivalent to the following--- expression:------ @--- stateful x0 f = 'mfix' $ \sig -> 'delay' x0 (f '<$>' sig)--- @-stateful :: a -- ^ initial state- -> (a -> a) -- ^ state transformation- -> 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 equivalent to the following--- expression:------ @--- transfer x0 f s = 'mfix' $ \sig -> 'liftA2' f s '<$>' 'delay' x0 sig--- @-transfer :: a -- ^ initial internal state- -> (t -> a -> a) -- ^ state updater function- -> Signal t -- ^ input signal- -> SignalGen (Signal a)-transfer x0 f s = mfix $ \sig -> liftA2 f s <$> delay x0 sig---- | A random signal.-noise :: MTRandom a => SignalGen (Signal a)-noise = memo (S randomIO)---- | A random source within the 'SignalGen' monad.-getRandom :: MTRandom a => SignalGen a-getRandom = SG (const randomIO)---- The Show instance is only defined for the sake of Num...-instance Show (Signal a) where- showsPrec _ _ s = "<SIGNAL>" ++ s---- Equality test is impossible.-instance Eq (Signal a) where- _ == _ = 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--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"--instance Bounded t => Bounded (Signal t) where- 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--instance Real t => Real (Signal t) where- 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"--instance Fractional t => Fractional (Signal t) where- (/) = 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
− FRP/Elerea/Graph.hs
@@ -1,171 +0,0 @@-{-# LANGUAGE ExistentialQuantification #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}--{-|--This module provides some means to visualise the signal structure.---}--module FRP.Elerea.Graph (signalToDot) where--import Control.Monad-import Data.IORef-import Data.Maybe-import qualified Data.Map as Map-import Foreign.Ptr-import Foreign.StablePtr-import FRP.Elerea.Internal--type Id = Int--type SignalStore = Map.Map Id SignalInfo--data SignalInfo- = Const- | Stateful- | Transfer Id- | App Id Id- | Sampler Id- | Generator Id Id- | External- | Delay Id- | Lift1 Id- | Lift2 Id Id- | Lift3 Id Id Id- | Lift4 Id Id Id Id- | Lift5 Id Id Id Id Id- | None--getPtr :: a -> IO Id-getPtr x = fmap (fromIntegral . ptrToIntPtr . castStablePtrToPtr) (newStablePtr x)--buildStore :: SignalStore -> Signal a -> IO (Id,SignalStore)-buildStore st (S r) = do- p <- getPtr r- case Map.lookup p st of- Just _ -> return (p,st)- Nothing -> do Ready s <- readIORef r- st' <- insertSignal st p s- return (p,st')--insertSignal :: SignalStore -> Id -> SignalNode a -> IO SignalStore-insertSignal st p (SNK _) = return (Map.insert p Const st)-insertSignal st p (SNS _ _) = return (Map.insert p Stateful st)-insertSignal st p (SNT s _ _) = do- (s',st') <- buildStore (Map.insert p None st) s- return (Map.insert p (Transfer s') st')-insertSignal st p (SNA sf sx) = do- (sf',st') <- buildStore (Map.insert p None st) sf- (sx',st'') <- buildStore st' sx- return (Map.insert p (App sf' sx') st'')-insertSignal st p (SNH ss _) = do- (ss',st') <- buildStore (Map.insert p None st) ss- return (Map.insert p (Sampler ss') st')-insertSignal st p (SNM b sm) = do- (b',st') <- buildStore (Map.insert p None st) b- (sm',st'') <- buildStore st' sm- return (Map.insert p (Generator b' sm') st'')-insertSignal st p (SNE _) = return (Map.insert p External st)-insertSignal st p (SND _ s) = do- (s',st') <- buildStore (Map.insert p None st) s- return (Map.insert p (Delay s') st')-insertSignal st p (SNKA (S r) _) = do- Ready s <- readIORef r- insertSignal st p s-insertSignal st p (SNF1 _ s1) = do- (s1',st') <- buildStore (Map.insert p None st) s1- return (Map.insert p (Lift1 s1') st')-insertSignal st p (SNF2 _ s1 s2) = do- (s1',st') <- buildStore (Map.insert p None st) s1- (s2',st'') <- buildStore st' s2- return (Map.insert p (Lift2 s1' s2') st'')-insertSignal st p (SNF3 _ s1 s2 s3) = do- (s1',st') <- buildStore (Map.insert p None st) s1- (s2',st'') <- buildStore st' s2- (s3',st''') <- buildStore st'' s3- return (Map.insert p (Lift3 s1' s2' s3') st''')-insertSignal st p (SNF4 _ s1 s2 s3 s4) = do- (s1',st') <- buildStore (Map.insert p None st) s1- (s2',st'') <- buildStore st' s2- (s3',st''') <- buildStore st'' s3- (s4',st'''') <- buildStore st''' s4- return (Map.insert p (Lift4 s1' s2' s3' s4') st'''')-insertSignal st p (SNF5 _ s1 s2 s3 s4 s5) = do- (s1',st') <- buildStore (Map.insert p None st) s1- (s2',st'') <- buildStore st' s2- (s3',st''') <- buildStore st'' s3- (s4',st'''') <- buildStore st''' s4- (s5',st''''') <- buildStore st'''' s5- return (Map.insert p (Lift5 s1' s2' s3' s4' s5') st''''')--nodeLabel :: Maybe Id -> SignalInfo -> [Char]-nodeLabel id node = case node of- Const -> "const"- Stateful -> "stateful"- Transfer _ -> "transfer"- App _ _ -> "app"- Sampler _ -> "sampler"- Generator _ _ -> "generator"- External -> "external"- Delay _ -> "delay"- Lift1 _ -> "fun1"- Lift2 _ _ -> "fun2"- Lift3 _ _ _ -> "fun3"- Lift4 _ _ _ _ -> "fun4"- Lift5 _ _ _ _ _ -> "fun5"- None -> "NONE"- ++ (maybe "" show id)--{-|--Traversing the network starting from the given signal and converting-it into a string containing the graph in Graphviz-(<http://www.graphviz.org/>) dot format. Stateful nodes are coloured-according to their type.--The results might differ depending on whether this function is called-before or after sampling (this also affects the actual network!), but-the networks should be still equivalent.---}--signalToDot :: Signal a -> IO String-signalToDot s = do- (_,st) <- buildStore Map.empty s- let rules = map mkRule (Map.assocs st)- mkRule (id,n) = " " ++ name ++ attrs ++ edges- where name = nodeLabel (Just id) n- attrs = mkLabel (nodeLabel Nothing n) ("style=filled,fillcolor=\"#" ++ nodeCol ++ "\",shape=" ++ nodeShape)- edges = case n of- Transfer s -> mkEdge s "\"\""- App sf sx -> mkEdge sf "f" ++ mkEdge sx "x"- Sampler ss -> mkEdge ss "\"\""- Generator b sm -> mkEdge b "ctl" ++ mkEdge sm "gen"- Delay s -> mkEdge s "\"\""- Lift1 s1 -> mkEdge s1 "x1"- Lift2 s1 s2 -> mkEdge s1 "x1" ++ mkEdge s2 "x2"- Lift3 s1 s2 s3 -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3"- Lift4 s1 s2 s3 s4 -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3" ++ mkEdge s4 "x4"- Lift5 s1 s2 s3 s4 s5 -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3" ++ mkEdge s4 "x4" ++ mkEdge s5 "x5"- _ -> ""- mkEdge endId label = " " ++ name ++ " -> " ++- nodeLabel (Just endId) (st Map.! endId) ++- mkLabel label "dir=back"- mkLabel name attrs = " [label=" ++ name ++ "," ++ attrs ++ "];\n"- nodeCol = case n of- Transfer _ -> "ffcc99"- Sampler _ -> "99ccff"- Generator _ _ -> "ccffff"- External -> "ccff99"- Stateful -> "ffffcc"- Delay _ -> "ffccff"- _ -> "ffffff"- nodeShape = case n of- Transfer _ -> "diamond"- Sampler _ -> "hexagon"- Generator _ _ -> "house"- External -> "invtriangle"- Delay _ -> "box"- _ -> "ellipse"- return $ "digraph G {\n" ++ concat rules ++ "}\n"
− FRP/Elerea/Internal.hs
@@ -1,546 +0,0 @@-{-# LANGUAGE ExistentialQuantification, GeneralizedNewtypeDeriving #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}--{-|--This is the core module of Elerea, which contains the signal-implementation and the atomic constructors.--The basic idea is to create a dataflow network whose structure closely-resembles the user's definitions by turning each combinator into a-mutable variable (an 'IORef'). In other words, each signal is-represented by a variable. Such a variable contains information about-the operation to perform and (depending on the operation) references-to other signals. For instance, a pointwise function application-created by the '<*>' operator contains an 'SNA' node, which holds two-references: one to the function signal and another to the argument-signal.--In order to have a pure(-looking) applicative interface for the most-part, the library relies on 'unsafePerformIO' to create the references-of stateless signals, while stateful signals have to be obtained from-a special 'SignalMonad', which is just a wrapping of 'IO' that doesn't-allow any other action to be performed.--The execution of the network is explicitly marked as an IO operation.-The core library exposes a single function to animate the network-called 'superstep', which takes a signal and a time interval, and-mutates all the variables the signal depends on. It is supposed to be-called repeatedly in a loop that also takes care of user input.--To ensure consistency, a superstep has three phases: sampling, aging-and finalisation. Each signal reachable from the top-level signal-passed to 'superstep' is sampled at the current point of time-('sample'), and the sample is stored along with the old signal in its-reference. If the value of a signal is requested multiple times, the-sample is simply reused. After successfully sampling the top-level-signal, the network is traversed again to advance by the desired time-('advance'), and when that's completed, the finalisation process-throws away the intermediate samples and marks the aged signals as the-current ones, ready to be sampled again. If there is a dependency-loop, the system tries to use the 'sampleDelayed' function instead of-'sample' to get a useful value at the problematic spot instead of-entering an infinite loop. Evaluation is initiated by the-'signalValue' function (which is used in both the sampling and the-aging phase to calculate samples and retrieve the cached values if-they are requested again), aging is performed by 'age', while-finalisation is done by 'commit'. Since these functions are invoked-recursively on a data structure with existential types, their types-also need to be explicity quantified.--As a bonus, applicative nodes are automatically collapsed into lifted-functions of up to five arguments. This optimisation significantly-reduces the number of nodes in the network.---}--module FRP.Elerea.Internal where--import Control.Applicative-import Control.Monad-import Control.Monad.Fix-import Data.IORef-import System.IO.Unsafe---- * Implementation---- ** Some type synonyms--{-| Time is continuous. Nothing fancy. -}--type DTime = Double--{-| Sinks are used when feeding input into peripheral-bound signals. -}--type Sink a = a -> IO ()---- ** The data structures behind signals--{-| A restricted monad to create stateful signals in. -}--newtype SignalMonad a = SM { createSignal :: IO a } deriving (Monad,Applicative,Functor,MonadFix)--{-| A printing function that can be used in the 'SignalMonad'.-Provided for debugging purposes. -}--signalDebug :: Show a => a -> SignalMonad ()-signalDebug = SM . print--{-| A signal is conceptually a time-varying value. -}--newtype Signal a = S (IORef (SignalTrans a))--{-| A node can have four states that distinguish various stages of-sampling and aging. -}--data SignalTrans a- -- | @Ready s@ is simply the signal @s@ that was not sampled yet- = Ready (SignalNode a)- -- | @Sampling s@ is signal @s@ after its current value was- -- requested, but not yet delivered- | Sampling (SignalNode a)- -- | @Sampled x s@ is signal @s@ paired with its current value @x@- | Sampled a (SignalNode a)- -- | @Aged x s@ is the aged version of signal @s@ paired with its- -- current value @x@- | Aged a (SignalNode a)--{-| The possible structures of a node are defined by the 'SignalNode'-type. Note that the @SNFx@ nodes are only needed to optimise-applicatives, they can all be expressed in terms of @SNK@ and-@SNA@. -}--data SignalNode a- -- | @SNK x@: constantly @x@- = SNK a- -- | @SNS x t@: stateful generator, where @x@ is current state and- -- @t@ is the update function- | SNS a (DTime -> a -> a)- -- | @SNT s x t@: stateful transfer function, which also depends- -- on an input signal @s@- | forall t . SNT (Signal t) a (DTime -> t -> a -> a)- -- | @SNA sf sx@: pointwise function application- | forall t . SNA (Signal (t -> a)) (Signal t)- -- | @SNH ss r@: the higher-order signal @ss@ collapsed into a- -- signal cached in reference @r@; @r@ is used during the aging- -- phase- | SNH (Signal (Signal a)) (IORef (Signal a))- -- | @SNM b sm@: signal generator that executes the monad carried- -- by @sm@ whenever @b@ is true, and outputs the result (or- -- undefined when @b@ is false)- | SNM (Signal Bool) (Signal (SignalMonad a))- -- | @SNE r@: opaque reference to connect peripherals- | SNE (IORef a)- -- | @SND s@: the @s@ signal delayed by one superstep- | SND a (Signal a)- -- | @SNKA s l@: equivalent to @s@ while aging signal @l@- | forall t . SNKA (Signal a) (Signal t)- -- | @SNF1 f@: @fmap f@- | forall t . SNF1 (t -> a) (Signal t)- -- | @SNF2 f@: @liftA2 f@- | forall t1 t2 . SNF2 (t1 -> t2 -> a) (Signal t1) (Signal t2)- -- | @SNF3 f@: @liftA3 f@- | forall t1 t2 t3 . SNF3 (t1 -> t2 -> t3 -> a) (Signal t1) (Signal t2) (Signal t3)- -- | @SNF4 f@: @liftA4 f@- | forall t1 t2 t3 t4 . SNF4 (t1 -> t2 -> t3 -> t4 -> a) (Signal t1) (Signal t2) (Signal t3) (Signal t4)- -- | @SNF5 f@: @liftA5 f@- | forall t1 t2 t3 t4 t5 . SNF5 (t1 -> t2 -> t3 -> t4 -> t5 -> a) (Signal t1) (Signal t2) (Signal t3) (Signal t4) (Signal t5)--{-| You can uncomment the verbose version of this function to see the-applicative optimisations in action. -}--debugLog :: String -> IO a -> IO a---debugLog s io = putStrLn s >> io-debugLog _ io = io--instance Functor Signal where- fmap = (<*>) . pure--{-| The 'Applicative' instance with run-time optimisation. The '<*>'-operator tries to move all the pure parts to its left side in order to-flatten the structure, hence cutting down on book-keeping costs. Since-applicatives are used with pure functions and lifted values most of-the time, one can gain a lot by merging these nodes. -}--instance Applicative Signal where- -- | A constant signal- pure = makeSignalUnsafe . SNK- -- | Point-wise application of a function and a data signal (like @ZipList@)---- --mf <*> mx = sampler (fmap (\f -> sampler (fmap (pure . f) mx)) mf)--- sf <*> sx = sampler (makeSignalUnsafe (SNF1 (\f -> sampler (makeSignalUnsafe (SNF1 (pure . f) sx))) sf))-- f@(S rf) <*> x@(S rx) = unsafePerformIO $ do- -- General fall-back case- c <- newIORef (Ready (SNA f x))-- let opt s = writeIORef c (Ready s)-- -- Optimisations might go haywire in the presence of loops,- -- so we need to prepare to meeting undefined references by- -- wrapping reads into exception handlers.-- flip catch (const (debugLog "no_fun" $ return ())) $ do- Ready nf <- readIORef rf-- merged <- flip catch (const (debugLog "no_arg" $ return False)) $ do- -- Merging constant branches from the two sides- Ready nx <- readIORef rx- case (nf,nx) of- (SNK g,SNK y) -> debugLog "merge_00" $ opt (SNK (g y))- (SNK g,SNF1 h y1) -> debugLog "merge_01" $ opt (SNF1 (g.h) y1)- (SNK g,SNF2 h y1 y2) -> debugLog "merge_02" $ opt (SNF2 (\y1 y2 -> g (h y1 y2)) y1 y2)- (SNK g,SNF3 h y1 y2 y3) -> debugLog "merge_03" $ opt (SNF3 (\y1 y2 y3 -> g (h y1 y2 y3)) y1 y2 y3)- (SNK g,SNF4 h y1 y2 y3 y4) -> debugLog "merge_04" $ opt (SNF4 (\y1 y2 y3 y4 -> g (h y1 y2 y3 y4)) y1 y2 y3 y4)- (SNK g,SNF5 h y1 y2 y3 y4 y5) -> debugLog "merge_05" $ opt (SNF5 (\y1 y2 y3 y4 y5 -> g (h y1 y2 y3 y4 y5)) y1 y2 y3 y4 y5)- (SNK g,_) -> debugLog "lift_1x" $ opt (SNF1 g x)- (SNF1 g x1,SNK y) -> debugLog "merge_10" $ opt (SNF1 (\x1 -> g x1 y) x1)- (SNF1 g x1,SNF1 h y1) -> debugLog "merge_11" $ opt (SNF2 (\x1 y1 -> g x1 (h y1)) x1 y1)- (SNF1 g x1,SNF2 h y1 y2) -> debugLog "merge_12" $ opt (SNF3 (\x1 y1 y2 -> g x1 (h y1 y2)) x1 y1 y2)- (SNF1 g x1,SNF3 h y1 y2 y3) -> debugLog "merge_13" $ opt (SNF4 (\x1 y1 y2 y3 -> g x1 (h y1 y2 y3)) x1 y1 y2 y3)- (SNF1 g x1,SNF4 h y1 y2 y3 y4) -> debugLog "merge_14" $ opt (SNF5 (\x1 y1 y2 y3 y4 -> g x1 (h y1 y2 y3 y4)) x1 y1 y2 y3 y4)- (SNF1 g x1,_) -> debugLog "lift_2x" $ opt (SNF2 g x1 x)- (SNF2 g x1 x2,SNK y) -> debugLog "merge_20" $ opt (SNF2 (\x1 x2 -> g x1 x2 y) x1 x2)- (SNF2 g x1 x2,SNF1 h y1) -> debugLog "merge_21" $ opt (SNF3 (\x1 x2 y1 -> g x1 x2 (h y1)) x1 x2 y1)- (SNF2 g x1 x2,SNF2 h y1 y2) -> debugLog "merge_22" $ opt (SNF4 (\x1 x2 y1 y2 -> g x1 x2 (h y1 y2)) x1 x2 y1 y2)- (SNF2 g x1 x2,SNF3 h y1 y2 y3) -> debugLog "merge_23" $ opt (SNF5 (\x1 x2 y1 y2 y3 -> g x1 x2 (h y1 y2 y3)) x1 x2 y1 y2 y3)- (SNF2 g x1 x2,_) -> debugLog "lift_3x" $ opt (SNF3 g x1 x2 x)- (SNF3 g x1 x2 x3,SNK y) -> debugLog "merge_30" $ opt (SNF3 (\x1 x2 x3 -> g x1 x2 x3 y) x1 x2 x3)- (SNF3 g x1 x2 x3,SNF1 h y1) -> debugLog "merge_31" $ opt (SNF4 (\x1 x2 x3 y1 -> g x1 x2 x3 (h y1)) x1 x2 x3 y1)- (SNF3 g x1 x2 x3,SNF2 h y1 y2) -> debugLog "merge_32" $ opt (SNF5 (\x1 x2 x3 y1 y2 -> g x1 x2 x3 (h y1 y2)) x1 x2 x3 y1 y2)- (SNF3 g x1 x2 x3,_) -> debugLog "lift_4x" $ opt (SNF4 g x1 x2 x3 x)- (SNF4 g x1 x2 x3 x4,SNK y) -> debugLog "merge_40" $ opt (SNF4 (\x1 x2 x3 x4 -> g x1 x2 x3 x4 y) x1 x2 x3 x4)- (SNF4 g x1 x2 x3 x4,SNF1 h y1) -> debugLog "merge_41" $ opt (SNF5 (\x1 x2 x3 x4 y1 -> g x1 x2 x3 x4 (h y1)) x1 x2 x3 x4 y1)- (SNF4 g x1 x2 x3 x4,_) -> debugLog "lift_5x" $ opt (SNF5 g x1 x2 x3 x4 x)- (SNF5 g x1 x2 x3 x4 x5,SNK y) -> debugLog "merge_50" $ opt (SNF5 (\x1 x2 x3 x4 x5 -> g x1 x2 x3 x4 x5 y) x1 x2 x3 x4 x5)- _ -> return ()- return True-- -- Lifting into higher arity not knowing the argument- when (not merged) $ case nf of- SNK g -> debugLog "lift_1" $ opt (SNF1 g x)- SNF1 g x1 -> debugLog "lift_2" $ opt (SNF2 g x1 x)- SNF2 g x1 x2 -> debugLog "lift_3" $ opt (SNF3 g x1 x2 x)- SNF3 g x1 x2 x3 -> debugLog "lift_4" $ opt (SNF4 g x1 x2 x3 x)- SNF4 g x1 x2 x3 x4 -> debugLog "lift_5" $ opt (SNF5 g x1 x2 x3 x4 x)- _ -> return ()-- -- The final version- return (S c)--{-| The @Show@ instance is only defined for the sake of 'Num'... -}--instance Show (Signal a) where- showsPrec _ _ s = "<SIGNAL>" ++ s--{-| The equality test checks whether two signals are physically the same. -}--instance Eq (Signal a) where- S s1 == S s2 = s1 == s2--{-| 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--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"--instance Bounded t => Bounded (Signal t) where- 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--instance Real t => Real (Signal t) where- 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"--instance Fractional t => Fractional (Signal t) where- (/) = 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---- ** Internal functions to run the network--{-| Creating a reference within the 'SignalMonad'. Used for stateful-signals. -}--makeSignal :: SignalNode a -> SignalMonad (Signal a)-makeSignal node = SM $ do- ref <- newIORef (Ready node)- return (S ref)--{-| Creating a reference as a pure value. Used for stateless-signals. -}--makeSignalUnsafe :: SignalNode a -> Signal a-makeSignalUnsafe = S . unsafePerformIO . newIORef . Ready--{-| Sampling the signal and all of its dependencies, at the same time.-We don't need the aged signal in the current superstep, only the-current value, so we sample before propagating the changes, which-might require the fresh sample because of recursive definitions. -}--signalValue :: forall a . Signal a -> DTime -> IO a-signalValue (S r) dt = do- t <- readIORef r- case t of- Ready s -> do writeIORef r (Sampling s)- -- TODO: advance can be evaluated in a separate- -- thread, since we don't need its result right- -- away, only in the next superstep.- v <- sample s dt- -- We memorise the sample to handle loops- -- nicely. The undefined future signal cannot- -- bite us, because we don't need it during the- -- evaluation phase.- writeIORef r (Sampled v s)- return v- Sampling s -> do -- We started sampling this already, so there is- -- a dependency cycle we have to resolve by- -- adding a delay to stateful signals. Stateless- -- signals should not form a loop, which is- -- obvious...- v <- sampleDelayed s dt- writeIORef r (Sampled v s)- -- Since we are sampling it already, this node- -- will be overwritten by the case above when- -- the loop is closed.- return v- Sampled v _ -> return v- Aged v _ -> return v--{-| Aging the network of signals the given signal depends on. -}--age :: forall a . Signal a -> DTime -> IO ()-age (S r) dt = do- t <- readIORef r- case t of- Sampled v s -> do s' <- advance s v dt- writeIORef r (Aged v s')- -- TODO: branching can be trivially parallelised- case s' of- SNT s _ _ -> age s dt- SNA sf sx -> age sf dt >> age sx dt- SNH ss r -> age ss dt >> readIORef r >>= \s -> age s dt- SNM b sm -> age b dt >> age sm dt- SND _ s -> age s dt- SNKA s l -> age s dt >> age l dt- SNF1 _ s -> age s dt- SNF2 _ s1 s2 -> age s1 dt >> age s2 dt- SNF3 _ s1 s2 s3 -> age s1 dt >> age s2 dt >> age s3 dt- SNF4 _ s1 s2 s3 s4 -> age s1 dt >> age s2 dt >> age s3 dt >> age s4 dt- SNF5 _ s1 s2 s3 s4 s5 -> age s1 dt >> age s2 dt >> age s3 dt >> age s4 dt >> age s5 dt- _ -> return ()- Aged _ _ -> return ()- _ -> error "Inconsistent state: signal not sampled properly!"--{-| Finalising aged signals for the next round. -}--commit :: forall a . Signal a -> IO ()-commit (S r) = do- t <- readIORef r- case t of- Aged _ s -> do writeIORef r (Ready s)- -- TODO: branching can be trivially parallelised- case s of- SNT s _ _ -> commit s- SNA sf sx -> commit sf >> commit sx- SNH ss r -> commit ss >> readIORef r >>= \s -> commit s- SNM b sm -> commit b >> commit sm- SND _ s -> commit s- SNKA s l -> commit s >> commit l- SNF1 _ s -> commit s- SNF2 _ s1 s2 -> commit s1 >> commit s2- SNF3 _ s1 s2 s3 -> commit s1 >> commit s2 >> commit s3- SNF4 _ s1 s2 s3 s4 -> commit s1 >> commit s2 >> commit s3 >> commit s4- SNF5 _ s1 s2 s3 s4 s5 -> commit s1 >> commit s2 >> commit s3 >> commit s4 >> commit s5- _ -> return ()- Ready _ -> return ()- _ -> error "Inconsistent state: signal not aged properly!"--{-| Aging the signal. Stateful signals have their state forced to-prevent building up big thunks. The other nodes are structurally-static. -}--advance :: SignalNode a -> a -> DTime -> IO (SignalNode a)-advance (SNS x f) _ dt = x `seq` return (SNS (f dt x) f)-advance (SNT s _ f) v _ = v `seq` return (SNT s v f)-advance (SND _ s) _ dt = do x <- signalValue s dt- return (SND x s)-advance s _ _ = return s--{-| Sampling the signal at the current moment. This is where static-nodes propagate changes to those they depend on. Transfer functions-('SNT') work without delay, i.e. the effects of their input signals-can be observed in the same superstep. -}--sample :: SignalNode a -> DTime -> IO a-sample (SNK x) _ = return x-sample (SNS x _) _ = return x-sample (SNT s x f) dt = do t <- signalValue s dt- return $! f dt t x-sample (SNA sf sx) dt = signalValue sf dt <*> signalValue sx dt-sample (SNH ss r) dt = do s <- signalValue ss dt- writeIORef r s- signalValue s dt-sample (SNM b sm) dt = do c <- signalValue b dt- SM m <- signalValue sm dt- if c then m else return undefined-sample (SNE r) _ = readIORef r-sample (SND v _) _ = return v-sample (SNKA s l) dt = do signalValue l dt- signalValue s dt-sample (SNF1 f s) dt = f <$> signalValue s dt-sample (SNF2 f s1 s2) dt = liftM2 f (signalValue s1 dt) (signalValue s2 dt)-sample (SNF3 f s1 s2 s3) dt = liftM3 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt)-sample (SNF4 f s1 s2 s3 s4) dt = liftM4 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt) (signalValue s4 dt)-sample (SNF5 f s1 s2 s3 s4 s5) dt = liftM5 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt) (signalValue s4 dt) (signalValue s5 dt)--{-| Sampling the signal with some kind of delay in order to resolve-dependency loops. Transfer functions simply return their previous-output (delays can be considered a special case, because they always-do that, so 'sampleDelayed' is never called with them), while other-types of signals are always handled by the 'sample' function, so it is-not possible to create a working stateful loop composed of solely-stateless combinators. -}--sampleDelayed :: SignalNode a -> DTime -> IO a-sampleDelayed (SNT _ x _) _ = return x-sampleDelayed sn dt = sample sn dt---- ** Userland combinators--{-| Advancing the whole network that the given signal depends on by-the amount of time given in the second argument. -}--superstep :: Signal a -- ^ the top-level signal- -> DTime -- ^ the amount of time to advance- -> IO a -- ^ the current value of the signal-superstep world dt = do- snapshot <- signalValue world dt- age world dt- commit world- return snapshot--{-| A pure stateful signal. The initial state is the first output. -}--stateful :: a -- ^ initial state- -> (DTime -> a -> a) -- ^ state transformation- -> SignalMonad (Signal a)-stateful x0 f = makeSignal (SNS x0 f)--{-| 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 only be directly-observed by the `sampleDelayed` function. -}--transfer :: a -- ^ initial internal state- -> (DTime -> t -> a -> a) -- ^ state updater function- -> Signal t -- ^ input signal- -> SignalMonad (Signal a)-transfer x0 f s = makeSignal (SNT s x0 f)--{-| A continuous sampler that flattens a higher-order signal by-outputting its current snapshots. -}--sampler :: Signal (Signal a) -- ^ signal to flatten- -> Signal a-sampler ss = makeSignalUnsafe (SNH ss (unsafePerformIO (newIORef undefined)))--{-| A reactive signal that takes the value to output from a monad-carried by its input when a boolean control signal is true, otherwise-it outputs 'Nothing'. It is possible to create new signals in the-monad and also to print debug messages. -}--generator :: Signal Bool -- ^ control (trigger) signal- -> Signal (SignalMonad a) -- ^ a stream of monads to potentially run- -> Signal (Maybe a)-generator b sm = toMaybe <$> b <*> makeSignalUnsafe (SNM b sm)--{-| A helper function to wrap any value in a 'Maybe' depending on a-boolean condition. -}--toMaybe :: Bool -> a -> Maybe a-toMaybe c v = if c then Just v else Nothing--{-| A signal that can be directly fed through the sink function-returned. This can be used to attach the network to the outer-world. -}--external :: a -- ^ initial value- -> IO (Signal a, Sink a) -- ^ the signal and an IO function to feed it-external x0 = do- ref <- newIORef x0- snr <- newIORef (Ready (SNE ref))- return (S snr,writeIORef ref)--{-| The `delay` transfer function emits the value of a signal from the-previous superstep, starting with the filler value given in the first-argument. It has to be a primitive, otherwise it could not be used to-prevent automatic delays. -}--delay :: a -- ^ initial output- -> Signal a -- ^ the signal to delay- -> SignalMonad (Signal a)-delay x0 s = makeSignal (SND x0 s)--{-| Dependency injection to allow aging signals whose output is not-necessarily needed to produce the current sample of the first-argument. It's equivalent to @(flip . liftA2 . flip) const@, as it-evaluates its second argument first. -}--keepAlive :: Signal a -- ^ the actual output- -> Signal t -- ^ a signal guaranteed to age when this one is sampled- -> Signal a-keepAlive s l = makeSignalUnsafe (SNKA s l)
+ FRP/Elerea/Legacy.hs view
@@ -0,0 +1,122 @@+{-|++Elerea (Eventless Reactivity) is a simplistic FRP implementation that+parts with the concept of events, and introduces various constructs+that can be used to define completely dynamic higher-order dataflow+networks. The user sees the functionality through a hybrid+monadic-applicative interface, where stateful signals can only be+created through a specialised monad, while most combinators are purely+applicative. The combinators build up a network of interconnected+mutable references in the background. The network is executed+iteratively, where each superstep consists of three phases: sampling,+aging, and finalisation. As an example, the following code is a+possible way to define an approximation of our beloved trig functions:++@+ (sine,cosine) <- mdo+ s <- integral 0 c+ c <- integral 1 (-s)+ return (s,c)+@++Note that @integral@ is not a primitive, it can be defined by the user+as a transfer function. A possible implementation that can be used on+any 'Fractional' signal looks like this:++@+ integral x0 s = transfer x0 (\\dt x x0 -> x0+x*realToFrac dt) s+@++Head to "FRP.Elerea.Internal" for the implementation details. To get+a general idea how to use the library, check out the sources in the+@elerea-examples@ package.++The "FRP.Elerea.Experimental" branch provides a similar interface with+a rather different underlying structure, which is likely to be more+efficient.++-}++module FRP.Elerea.Legacy+ ( DTime, Sink, Signal, SignalMonad+ , createSignal, superstep+ , external+ , stateful, transfer, delay+ , sampler, generator+ , storeJust, toMaybe+ , edge+ , keepAlive, (.@.)+ , (==@), (/=@), (<@), (<=@), (>=@), (>@)+ , (&&@), (||@)+ , signalDebug+) where++import Control.Applicative+import FRP.Elerea.Legacy.Internal++infix 4 ==@, /=@, <@, <=@, >=@, >@+infixr 3 &&@+infixr 2 ||@++{-| A short alternative name for 'keepAlive'. -}++(.@.) :: Signal a -> Signal t -> Signal a+(.@.) = keepAlive++{-| The `edge` transfer function takes a bool signal and emits another+bool signal that turns true only at the moment when there is a rising+edge on the input. -}++edge :: Signal Bool -> SignalMonad (Signal Bool)+edge b = delay True b >>= \db -> return $ (not <$> db) &&@ b++{-| The `storeJust` transfer function behaves as a latch on a 'Maybe'+input: it keeps its state when the input is 'Nothing', and replaces it+with the input otherwise. -}++storeJust :: a -- ^ Initial output+ -> Signal (Maybe a) -- ^ Maybe signal to latch on+ -> SignalMonad (Signal a)+storeJust x0 s = transfer x0 store s+ where store _ Nothing x = x+ store _ (Just x) _ = x++{-| Point-wise equality of two signals. -}++(==@) :: Eq a => Signal a -> Signal a -> Signal Bool+(==@) = liftA2 (==)++{-| Point-wise inequality of two signals. -}++(/=@) :: Eq a => Signal a -> Signal a -> Signal Bool+(/=@) = liftA2 (/=)++{-| Point-wise comparison of two signals. -}++(<@) :: Ord a => Signal a -> Signal a -> Signal Bool+(<@) = liftA2 (<)++{-| Point-wise comparison of two signals. -}++(<=@) :: Ord a => Signal a -> Signal a -> Signal Bool+(<=@) = liftA2 (<=)++{-| Point-wise comparison of two signals. -}++(>=@) :: Ord a => Signal a -> Signal a -> Signal Bool+(>=@) = liftA2 (>=)++{-| Point-wise comparison of two signals. -}++(>@) :: Ord a => Signal a -> Signal a -> Signal Bool+(>@) = liftA2 (>)++{-| Point-wise OR of two boolean signals. -}++(||@) :: Signal Bool -> Signal Bool -> Signal Bool+(||@) = liftA2 (||)++{-| Point-wise AND of two boolean signals. -}++(&&@) :: Signal Bool -> Signal Bool -> Signal Bool+(&&@) = liftA2 (&&)
+ FRP/Elerea/Legacy/Graph.hs view
@@ -0,0 +1,169 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}++{-|++This module provides some means to visualise the signal structure.++-}++module FRP.Elerea.Legacy.Graph (signalToDot) where++import Data.IORef+import qualified Data.Map as Map+import Foreign.Ptr+import Foreign.StablePtr+import FRP.Elerea.Legacy.Internal++type Id = Int++type SignalStore = Map.Map Id SignalInfo++data SignalInfo+ = Const+ | Stateful+ | Transfer Id+ | App Id Id+ | Sampler Id+ | Generator Id Id+ | External+ | Delay Id+ | Lift1 Id+ | Lift2 Id Id+ | Lift3 Id Id Id+ | Lift4 Id Id Id Id+ | Lift5 Id Id Id Id Id+ | None++getPtr :: a -> IO Id+getPtr x = fmap (fromIntegral . ptrToIntPtr . castStablePtrToPtr) (newStablePtr x)++buildStore :: SignalStore -> Signal a -> IO (Id,SignalStore)+buildStore st (S r) = do+ p <- getPtr r+ case Map.lookup p st of+ Just _ -> return (p,st)+ Nothing -> do Ready s <- readIORef r+ st' <- insertSignal st p s+ return (p,st')++insertSignal :: SignalStore -> Id -> SignalNode a -> IO SignalStore+insertSignal st p (SNK _) = return (Map.insert p Const st)+insertSignal st p (SNS _ _) = return (Map.insert p Stateful st)+insertSignal st p (SNT s _ _) = do+ (s',st') <- buildStore (Map.insert p None st) s+ return (Map.insert p (Transfer s') st')+insertSignal st p (SNA sf sx) = do+ (sf',st') <- buildStore (Map.insert p None st) sf+ (sx',st'') <- buildStore st' sx+ return (Map.insert p (App sf' sx') st'')+insertSignal st p (SNH ss _) = do+ (ss',st') <- buildStore (Map.insert p None st) ss+ return (Map.insert p (Sampler ss') st')+insertSignal st p (SNM b sm) = do+ (b',st') <- buildStore (Map.insert p None st) b+ (sm',st'') <- buildStore st' sm+ return (Map.insert p (Generator b' sm') st'')+insertSignal st p (SNE _) = return (Map.insert p External st)+insertSignal st p (SND _ s) = do+ (s',st') <- buildStore (Map.insert p None st) s+ return (Map.insert p (Delay s') st')+insertSignal st p (SNKA (S r) _) = do+ Ready s <- readIORef r+ insertSignal st p s+insertSignal st p (SNF1 _ s1) = do+ (s1',st') <- buildStore (Map.insert p None st) s1+ return (Map.insert p (Lift1 s1') st')+insertSignal st p (SNF2 _ s1 s2) = do+ (s1',st') <- buildStore (Map.insert p None st) s1+ (s2',st'') <- buildStore st' s2+ return (Map.insert p (Lift2 s1' s2') st'')+insertSignal st p (SNF3 _ s1 s2 s3) = do+ (s1',st') <- buildStore (Map.insert p None st) s1+ (s2',st'') <- buildStore st' s2+ (s3',st''') <- buildStore st'' s3+ return (Map.insert p (Lift3 s1' s2' s3') st''')+insertSignal st p (SNF4 _ s1 s2 s3 s4) = do+ (s1',st') <- buildStore (Map.insert p None st) s1+ (s2',st'') <- buildStore st' s2+ (s3',st''') <- buildStore st'' s3+ (s4',st'''') <- buildStore st''' s4+ return (Map.insert p (Lift4 s1' s2' s3' s4') st'''')+insertSignal st p (SNF5 _ s1 s2 s3 s4 s5) = do+ (s1',st') <- buildStore (Map.insert p None st) s1+ (s2',st'') <- buildStore st' s2+ (s3',st''') <- buildStore st'' s3+ (s4',st'''') <- buildStore st''' s4+ (s5',st''''') <- buildStore st'''' s5+ return (Map.insert p (Lift5 s1' s2' s3' s4' s5') st''''')++nodeLabel :: Maybe Id -> SignalInfo -> [Char]+nodeLabel id node = case node of+ Const -> "const"+ Stateful -> "stateful"+ Transfer _ -> "transfer"+ App _ _ -> "app"+ Sampler _ -> "sampler"+ Generator _ _ -> "generator"+ External -> "external"+ Delay _ -> "delay"+ Lift1 _ -> "fun1"+ Lift2 _ _ -> "fun2"+ Lift3 _ _ _ -> "fun3"+ Lift4 _ _ _ _ -> "fun4"+ Lift5 _ _ _ _ _ -> "fun5"+ None -> "NONE"+ ++ (maybe "" show id)++{-|++Traversing the network starting from the given signal and converting+it into a string containing the graph in Graphviz+(<http://www.graphviz.org/>) dot format. Stateful nodes are coloured+according to their type.++The results might differ depending on whether this function is called+before or after sampling (this also affects the actual network!), but+the networks should be still equivalent.++-}++signalToDot :: Signal a -> IO String+signalToDot s = do+ (_,st) <- buildStore Map.empty s+ let rules = map mkRule (Map.assocs st)+ mkRule (id,n) = " " ++ name ++ attrs ++ edges+ where name = nodeLabel (Just id) n+ attrs = mkLabel (nodeLabel Nothing n) ("style=filled,fillcolor=\"#" ++ nodeCol ++ "\",shape=" ++ nodeShape)+ edges = case n of+ Transfer s -> mkEdge s "\"\""+ App sf sx -> mkEdge sf "f" ++ mkEdge sx "x"+ Sampler ss -> mkEdge ss "\"\""+ Generator b sm -> mkEdge b "ctl" ++ mkEdge sm "gen"+ Delay s -> mkEdge s "\"\""+ Lift1 s1 -> mkEdge s1 "x1"+ Lift2 s1 s2 -> mkEdge s1 "x1" ++ mkEdge s2 "x2"+ Lift3 s1 s2 s3 -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3"+ Lift4 s1 s2 s3 s4 -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3" ++ mkEdge s4 "x4"+ Lift5 s1 s2 s3 s4 s5 -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3" ++ mkEdge s4 "x4" ++ mkEdge s5 "x5"+ _ -> ""+ mkEdge endId label = " " ++ name ++ " -> " +++ nodeLabel (Just endId) (st Map.! endId) +++ mkLabel label "dir=back"+ mkLabel name attrs = " [label=" ++ name ++ "," ++ attrs ++ "];\n"+ nodeCol = case n of+ Transfer _ -> "ffcc99"+ Sampler _ -> "99ccff"+ Generator _ _ -> "ccffff"+ External -> "ccff99"+ Stateful -> "ffffcc"+ Delay _ -> "ffccff"+ _ -> "ffffff"+ nodeShape = case n of+ Transfer _ -> "diamond"+ Sampler _ -> "hexagon"+ Generator _ _ -> "house"+ External -> "invtriangle"+ Delay _ -> "box"+ _ -> "ellipse"+ return $ "digraph G {\n" ++ concat rules ++ "}\n"
+ FRP/Elerea/Legacy/Internal.hs view
@@ -0,0 +1,546 @@+{-# LANGUAGE ExistentialQuantification, GeneralizedNewtypeDeriving #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}++{-|++This is the core module of Elerea, which contains the signal+implementation and the atomic constructors.++The basic idea is to create a dataflow network whose structure closely+resembles the user's definitions by turning each combinator into a+mutable variable (an 'IORef'). In other words, each signal is+represented by a variable. Such a variable contains information about+the operation to perform and (depending on the operation) references+to other signals. For instance, a pointwise function application+created by the '<*>' operator contains an 'SNA' node, which holds two+references: one to the function signal and another to the argument+signal.++In order to have a pure(-looking) applicative interface for the most+part, the library relies on 'unsafePerformIO' to create the references+of stateless signals, while stateful signals have to be obtained from+a special 'SignalMonad', which is just a wrapping of 'IO' that doesn't+allow any other action to be performed.++The execution of the network is explicitly marked as an IO operation.+The core library exposes a single function to animate the network+called 'superstep', which takes a signal and a time interval, and+mutates all the variables the signal depends on. It is supposed to be+called repeatedly in a loop that also takes care of user input.++To ensure consistency, a superstep has three phases: sampling, aging+and finalisation. Each signal reachable from the top-level signal+passed to 'superstep' is sampled at the current point of time+('sample'), and the sample is stored along with the old signal in its+reference. If the value of a signal is requested multiple times, the+sample is simply reused. After successfully sampling the top-level+signal, the network is traversed again to advance by the desired time+('advance'), and when that's completed, the finalisation process+throws away the intermediate samples and marks the aged signals as the+current ones, ready to be sampled again. If there is a dependency+loop, the system tries to use the 'sampleDelayed' function instead of+'sample' to get a useful value at the problematic spot instead of+entering an infinite loop. Evaluation is initiated by the+'signalValue' function (which is used in both the sampling and the+aging phase to calculate samples and retrieve the cached values if+they are requested again), aging is performed by 'age', while+finalisation is done by 'commit'. Since these functions are invoked+recursively on a data structure with existential types, their types+also need to be explicity quantified.++As a bonus, applicative nodes are automatically collapsed into lifted+functions of up to five arguments. This optimisation significantly+reduces the number of nodes in the network.++-}++module FRP.Elerea.Legacy.Internal where++import Control.Applicative+import Control.Monad+import Control.Monad.Fix+import Data.IORef+import System.IO.Unsafe++-- * Implementation++-- ** Some type synonyms++{-| Time is continuous. Nothing fancy. -}++type DTime = Double++{-| Sinks are used when feeding input into peripheral-bound signals. -}++type Sink a = a -> IO ()++-- ** The data structures behind signals++{-| A restricted monad to create stateful signals in. -}++newtype SignalMonad a = SM { createSignal :: IO a } deriving (Monad,Applicative,Functor,MonadFix)++{-| A printing function that can be used in the 'SignalMonad'.+Provided for debugging purposes. -}++signalDebug :: Show a => a -> SignalMonad ()+signalDebug = SM . print++{-| A signal is conceptually a time-varying value. -}++newtype Signal a = S (IORef (SignalTrans a))++{-| A node can have four states that distinguish various stages of+sampling and aging. -}++data SignalTrans a+ -- | @Ready s@ is simply the signal @s@ that was not sampled yet+ = Ready (SignalNode a)+ -- | @Sampling s@ is signal @s@ after its current value was+ -- requested, but not yet delivered+ | Sampling (SignalNode a)+ -- | @Sampled x s@ is signal @s@ paired with its current value @x@+ | Sampled a (SignalNode a)+ -- | @Aged x s@ is the aged version of signal @s@ paired with its+ -- current value @x@+ | Aged a (SignalNode a)++{-| The possible structures of a node are defined by the 'SignalNode'+type. Note that the @SNFx@ nodes are only needed to optimise+applicatives, they can all be expressed in terms of @SNK@ and+@SNA@. -}++data SignalNode a+ -- | @SNK x@: constantly @x@+ = SNK a+ -- | @SNS x t@: stateful generator, where @x@ is current state and+ -- @t@ is the update function+ | SNS a (DTime -> a -> a)+ -- | @SNT s x t@: stateful transfer function, which also depends+ -- on an input signal @s@+ | forall t . SNT (Signal t) a (DTime -> t -> a -> a)+ -- | @SNA sf sx@: pointwise function application+ | forall t . SNA (Signal (t -> a)) (Signal t)+ -- | @SNH ss r@: the higher-order signal @ss@ collapsed into a+ -- signal cached in reference @r@; @r@ is used during the aging+ -- phase+ | SNH (Signal (Signal a)) (IORef (Signal a))+ -- | @SNM b sm@: signal generator that executes the monad carried+ -- by @sm@ whenever @b@ is true, and outputs the result (or+ -- undefined when @b@ is false)+ | SNM (Signal Bool) (Signal (SignalMonad a))+ -- | @SNE r@: opaque reference to connect peripherals+ | SNE (IORef a)+ -- | @SND s@: the @s@ signal delayed by one superstep+ | SND a (Signal a)+ -- | @SNKA s l@: equivalent to @s@ while aging signal @l@+ | forall t . SNKA (Signal a) (Signal t)+ -- | @SNF1 f@: @fmap f@+ | forall t . SNF1 (t -> a) (Signal t)+ -- | @SNF2 f@: @liftA2 f@+ | forall t1 t2 . SNF2 (t1 -> t2 -> a) (Signal t1) (Signal t2)+ -- | @SNF3 f@: @liftA3 f@+ | forall t1 t2 t3 . SNF3 (t1 -> t2 -> t3 -> a) (Signal t1) (Signal t2) (Signal t3)+ -- | @SNF4 f@: @liftA4 f@+ | forall t1 t2 t3 t4 . SNF4 (t1 -> t2 -> t3 -> t4 -> a) (Signal t1) (Signal t2) (Signal t3) (Signal t4)+ -- | @SNF5 f@: @liftA5 f@+ | forall t1 t2 t3 t4 t5 . SNF5 (t1 -> t2 -> t3 -> t4 -> t5 -> a) (Signal t1) (Signal t2) (Signal t3) (Signal t4) (Signal t5)++{-| You can uncomment the verbose version of this function to see the+applicative optimisations in action. -}++debugLog :: String -> IO a -> IO a+--debugLog s io = putStrLn s >> io+debugLog _ io = io++instance Functor Signal where+ fmap = (<*>) . pure++{-| The 'Applicative' instance with run-time optimisation. The '<*>'+operator tries to move all the pure parts to its left side in order to+flatten the structure, hence cutting down on book-keeping costs. Since+applicatives are used with pure functions and lifted values most of+the time, one can gain a lot by merging these nodes. -}++instance Applicative Signal where+ -- | A constant signal+ pure = makeSignalUnsafe . SNK+ -- | Point-wise application of a function and a data signal (like @ZipList@)++-- --mf <*> mx = sampler (fmap (\f -> sampler (fmap (pure . f) mx)) mf)+-- sf <*> sx = sampler (makeSignalUnsafe (SNF1 (\f -> sampler (makeSignalUnsafe (SNF1 (pure . f) sx))) sf))++ f@(S rf) <*> x@(S rx) = unsafePerformIO $ do+ -- General fall-back case+ c <- newIORef (Ready (SNA f x))++ let opt s = writeIORef c (Ready s)++ -- Optimisations might go haywire in the presence of loops,+ -- so we need to prepare to meeting undefined references by+ -- wrapping reads into exception handlers.++ flip catch (const (debugLog "no_fun" $ return ())) $ do+ Ready nf <- readIORef rf++ merged <- flip catch (const (debugLog "no_arg" $ return False)) $ do+ -- Merging constant branches from the two sides+ Ready nx <- readIORef rx+ case (nf,nx) of+ (SNK g,SNK y) -> debugLog "merge_00" $ opt (SNK (g y))+ (SNK g,SNF1 h y1) -> debugLog "merge_01" $ opt (SNF1 (g.h) y1)+ (SNK g,SNF2 h y1 y2) -> debugLog "merge_02" $ opt (SNF2 (\y1 y2 -> g (h y1 y2)) y1 y2)+ (SNK g,SNF3 h y1 y2 y3) -> debugLog "merge_03" $ opt (SNF3 (\y1 y2 y3 -> g (h y1 y2 y3)) y1 y2 y3)+ (SNK g,SNF4 h y1 y2 y3 y4) -> debugLog "merge_04" $ opt (SNF4 (\y1 y2 y3 y4 -> g (h y1 y2 y3 y4)) y1 y2 y3 y4)+ (SNK g,SNF5 h y1 y2 y3 y4 y5) -> debugLog "merge_05" $ opt (SNF5 (\y1 y2 y3 y4 y5 -> g (h y1 y2 y3 y4 y5)) y1 y2 y3 y4 y5)+ (SNK g,_) -> debugLog "lift_1x" $ opt (SNF1 g x)+ (SNF1 g x1,SNK y) -> debugLog "merge_10" $ opt (SNF1 (\x1 -> g x1 y) x1)+ (SNF1 g x1,SNF1 h y1) -> debugLog "merge_11" $ opt (SNF2 (\x1 y1 -> g x1 (h y1)) x1 y1)+ (SNF1 g x1,SNF2 h y1 y2) -> debugLog "merge_12" $ opt (SNF3 (\x1 y1 y2 -> g x1 (h y1 y2)) x1 y1 y2)+ (SNF1 g x1,SNF3 h y1 y2 y3) -> debugLog "merge_13" $ opt (SNF4 (\x1 y1 y2 y3 -> g x1 (h y1 y2 y3)) x1 y1 y2 y3)+ (SNF1 g x1,SNF4 h y1 y2 y3 y4) -> debugLog "merge_14" $ opt (SNF5 (\x1 y1 y2 y3 y4 -> g x1 (h y1 y2 y3 y4)) x1 y1 y2 y3 y4)+ (SNF1 g x1,_) -> debugLog "lift_2x" $ opt (SNF2 g x1 x)+ (SNF2 g x1 x2,SNK y) -> debugLog "merge_20" $ opt (SNF2 (\x1 x2 -> g x1 x2 y) x1 x2)+ (SNF2 g x1 x2,SNF1 h y1) -> debugLog "merge_21" $ opt (SNF3 (\x1 x2 y1 -> g x1 x2 (h y1)) x1 x2 y1)+ (SNF2 g x1 x2,SNF2 h y1 y2) -> debugLog "merge_22" $ opt (SNF4 (\x1 x2 y1 y2 -> g x1 x2 (h y1 y2)) x1 x2 y1 y2)+ (SNF2 g x1 x2,SNF3 h y1 y2 y3) -> debugLog "merge_23" $ opt (SNF5 (\x1 x2 y1 y2 y3 -> g x1 x2 (h y1 y2 y3)) x1 x2 y1 y2 y3)+ (SNF2 g x1 x2,_) -> debugLog "lift_3x" $ opt (SNF3 g x1 x2 x)+ (SNF3 g x1 x2 x3,SNK y) -> debugLog "merge_30" $ opt (SNF3 (\x1 x2 x3 -> g x1 x2 x3 y) x1 x2 x3)+ (SNF3 g x1 x2 x3,SNF1 h y1) -> debugLog "merge_31" $ opt (SNF4 (\x1 x2 x3 y1 -> g x1 x2 x3 (h y1)) x1 x2 x3 y1)+ (SNF3 g x1 x2 x3,SNF2 h y1 y2) -> debugLog "merge_32" $ opt (SNF5 (\x1 x2 x3 y1 y2 -> g x1 x2 x3 (h y1 y2)) x1 x2 x3 y1 y2)+ (SNF3 g x1 x2 x3,_) -> debugLog "lift_4x" $ opt (SNF4 g x1 x2 x3 x)+ (SNF4 g x1 x2 x3 x4,SNK y) -> debugLog "merge_40" $ opt (SNF4 (\x1 x2 x3 x4 -> g x1 x2 x3 x4 y) x1 x2 x3 x4)+ (SNF4 g x1 x2 x3 x4,SNF1 h y1) -> debugLog "merge_41" $ opt (SNF5 (\x1 x2 x3 x4 y1 -> g x1 x2 x3 x4 (h y1)) x1 x2 x3 x4 y1)+ (SNF4 g x1 x2 x3 x4,_) -> debugLog "lift_5x" $ opt (SNF5 g x1 x2 x3 x4 x)+ (SNF5 g x1 x2 x3 x4 x5,SNK y) -> debugLog "merge_50" $ opt (SNF5 (\x1 x2 x3 x4 x5 -> g x1 x2 x3 x4 x5 y) x1 x2 x3 x4 x5)+ _ -> return ()+ return True++ -- Lifting into higher arity not knowing the argument+ when (not merged) $ case nf of+ SNK g -> debugLog "lift_1" $ opt (SNF1 g x)+ SNF1 g x1 -> debugLog "lift_2" $ opt (SNF2 g x1 x)+ SNF2 g x1 x2 -> debugLog "lift_3" $ opt (SNF3 g x1 x2 x)+ SNF3 g x1 x2 x3 -> debugLog "lift_4" $ opt (SNF4 g x1 x2 x3 x)+ SNF4 g x1 x2 x3 x4 -> debugLog "lift_5" $ opt (SNF5 g x1 x2 x3 x4 x)+ _ -> return ()++ -- The final version+ return (S c)++{-| The @Show@ instance is only defined for the sake of 'Num'... -}++instance Show (Signal a) where+ showsPrec _ _ s = "<SIGNAL>" ++ s++{-| The equality test checks whether two signals are physically the same. -}++instance Eq (Signal a) where+ S s1 == S s2 = s1 == s2++{-| 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++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"++instance Bounded t => Bounded (Signal t) where+ 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++instance Real t => Real (Signal t) where+ 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"++instance Fractional t => Fractional (Signal t) where+ (/) = 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++-- ** Internal functions to run the network++{-| Creating a reference within the 'SignalMonad'. Used for stateful+signals. -}++makeSignal :: SignalNode a -> SignalMonad (Signal a)+makeSignal node = SM $ do+ ref <- newIORef (Ready node)+ return (S ref)++{-| Creating a reference as a pure value. Used for stateless+signals. -}++makeSignalUnsafe :: SignalNode a -> Signal a+makeSignalUnsafe = S . unsafePerformIO . newIORef . Ready++{-| Sampling the signal and all of its dependencies, at the same time.+We don't need the aged signal in the current superstep, only the+current value, so we sample before propagating the changes, which+might require the fresh sample because of recursive definitions. -}++signalValue :: forall a . Signal a -> DTime -> IO a+signalValue (S r) dt = do+ t <- readIORef r+ case t of+ Ready s -> do writeIORef r (Sampling s)+ -- TODO: advance can be evaluated in a separate+ -- thread, since we don't need its result right+ -- away, only in the next superstep.+ v <- sample s dt+ -- We memorise the sample to handle loops+ -- nicely. The undefined future signal cannot+ -- bite us, because we don't need it during the+ -- evaluation phase.+ writeIORef r (Sampled v s)+ return v+ Sampling s -> do -- We started sampling this already, so there is+ -- a dependency cycle we have to resolve by+ -- adding a delay to stateful signals. Stateless+ -- signals should not form a loop, which is+ -- obvious...+ v <- sampleDelayed s dt+ writeIORef r (Sampled v s)+ -- Since we are sampling it already, this node+ -- will be overwritten by the case above when+ -- the loop is closed.+ return v+ Sampled v _ -> return v+ Aged v _ -> return v++{-| Aging the network of signals the given signal depends on. -}++age :: forall a . Signal a -> DTime -> IO ()+age (S r) dt = do+ t <- readIORef r+ case t of+ Sampled v s -> do s' <- advance s v dt+ writeIORef r (Aged v s')+ -- TODO: branching can be trivially parallelised+ case s' of+ SNT s _ _ -> age s dt+ SNA sf sx -> age sf dt >> age sx dt+ SNH ss r -> age ss dt >> readIORef r >>= \s -> age s dt+ SNM b sm -> age b dt >> age sm dt+ SND _ s -> age s dt+ SNKA s l -> age s dt >> age l dt+ SNF1 _ s -> age s dt+ SNF2 _ s1 s2 -> age s1 dt >> age s2 dt+ SNF3 _ s1 s2 s3 -> age s1 dt >> age s2 dt >> age s3 dt+ SNF4 _ s1 s2 s3 s4 -> age s1 dt >> age s2 dt >> age s3 dt >> age s4 dt+ SNF5 _ s1 s2 s3 s4 s5 -> age s1 dt >> age s2 dt >> age s3 dt >> age s4 dt >> age s5 dt+ _ -> return ()+ Aged _ _ -> return ()+ _ -> error "Inconsistent state: signal not sampled properly!"++{-| Finalising aged signals for the next round. -}++commit :: forall a . Signal a -> IO ()+commit (S r) = do+ t <- readIORef r+ case t of+ Aged _ s -> do writeIORef r (Ready s)+ -- TODO: branching can be trivially parallelised+ case s of+ SNT s _ _ -> commit s+ SNA sf sx -> commit sf >> commit sx+ SNH ss r -> commit ss >> readIORef r >>= \s -> commit s+ SNM b sm -> commit b >> commit sm+ SND _ s -> commit s+ SNKA s l -> commit s >> commit l+ SNF1 _ s -> commit s+ SNF2 _ s1 s2 -> commit s1 >> commit s2+ SNF3 _ s1 s2 s3 -> commit s1 >> commit s2 >> commit s3+ SNF4 _ s1 s2 s3 s4 -> commit s1 >> commit s2 >> commit s3 >> commit s4+ SNF5 _ s1 s2 s3 s4 s5 -> commit s1 >> commit s2 >> commit s3 >> commit s4 >> commit s5+ _ -> return ()+ Ready _ -> return ()+ _ -> error "Inconsistent state: signal not aged properly!"++{-| Aging the signal. Stateful signals have their state forced to+prevent building up big thunks. The other nodes are structurally+static. -}++advance :: SignalNode a -> a -> DTime -> IO (SignalNode a)+advance (SNS x f) _ dt = x `seq` return (SNS (f dt x) f)+advance (SNT s _ f) v _ = v `seq` return (SNT s v f)+advance (SND _ s) _ dt = do x <- signalValue s dt+ return (SND x s)+advance s _ _ = return s++{-| Sampling the signal at the current moment. This is where static+nodes propagate changes to those they depend on. Transfer functions+('SNT') work without delay, i.e. the effects of their input signals+can be observed in the same superstep. -}++sample :: SignalNode a -> DTime -> IO a+sample (SNK x) _ = return x+sample (SNS x _) _ = return x+sample (SNT s x f) dt = do t <- signalValue s dt+ return $! f dt t x+sample (SNA sf sx) dt = signalValue sf dt <*> signalValue sx dt+sample (SNH ss r) dt = do s <- signalValue ss dt+ writeIORef r s+ signalValue s dt+sample (SNM b sm) dt = do c <- signalValue b dt+ SM m <- signalValue sm dt+ if c then m else return undefined+sample (SNE r) _ = readIORef r+sample (SND v _) _ = return v+sample (SNKA s l) dt = do _ <- signalValue l dt+ signalValue s dt+sample (SNF1 f s) dt = f <$> signalValue s dt+sample (SNF2 f s1 s2) dt = liftM2 f (signalValue s1 dt) (signalValue s2 dt)+sample (SNF3 f s1 s2 s3) dt = liftM3 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt)+sample (SNF4 f s1 s2 s3 s4) dt = liftM4 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt) (signalValue s4 dt)+sample (SNF5 f s1 s2 s3 s4 s5) dt = liftM5 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt) (signalValue s4 dt) (signalValue s5 dt)++{-| Sampling the signal with some kind of delay in order to resolve+dependency loops. Transfer functions simply return their previous+output (delays can be considered a special case, because they always+do that, so 'sampleDelayed' is never called with them), while other+types of signals are always handled by the 'sample' function, so it is+not possible to create a working stateful loop composed of solely+stateless combinators. -}++sampleDelayed :: SignalNode a -> DTime -> IO a+sampleDelayed (SNT _ x _) _ = return x+sampleDelayed sn dt = sample sn dt++-- ** Userland combinators++{-| Advancing the whole network that the given signal depends on by+the amount of time given in the second argument. -}++superstep :: Signal a -- ^ the top-level signal+ -> DTime -- ^ the amount of time to advance+ -> IO a -- ^ the current value of the signal+superstep world dt = do+ snapshot <- signalValue world dt+ age world dt+ commit world+ return snapshot++{-| A pure stateful signal. The initial state is the first output. -}++stateful :: a -- ^ initial state+ -> (DTime -> a -> a) -- ^ state transformation+ -> SignalMonad (Signal a)+stateful x0 f = makeSignal (SNS x0 f)++{-| 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 only be directly+observed by the `sampleDelayed` function. -}++transfer :: a -- ^ initial internal state+ -> (DTime -> t -> a -> a) -- ^ state updater function+ -> Signal t -- ^ input signal+ -> SignalMonad (Signal a)+transfer x0 f s = makeSignal (SNT s x0 f)++{-| A continuous sampler that flattens a higher-order signal by+outputting its current snapshots. -}++sampler :: Signal (Signal a) -- ^ signal to flatten+ -> Signal a+sampler ss = makeSignalUnsafe (SNH ss (unsafePerformIO (newIORef undefined)))++{-| A reactive signal that takes the value to output from a monad+carried by its input when a boolean control signal is true, otherwise+it outputs 'Nothing'. It is possible to create new signals in the+monad and also to print debug messages. -}++generator :: Signal Bool -- ^ control (trigger) signal+ -> Signal (SignalMonad a) -- ^ a stream of monads to potentially run+ -> Signal (Maybe a)+generator b sm = toMaybe <$> b <*> makeSignalUnsafe (SNM b sm)++{-| A helper function to wrap any value in a 'Maybe' depending on a+boolean condition. -}++toMaybe :: Bool -> a -> Maybe a+toMaybe c v = if c then Just v else Nothing++{-| A signal that can be directly fed through the sink function+returned. This can be used to attach the network to the outer+world. -}++external :: a -- ^ initial value+ -> IO (Signal a, Sink a) -- ^ the signal and an IO function to feed it+external x0 = do+ ref <- newIORef x0+ snr <- newIORef (Ready (SNE ref))+ return (S snr,writeIORef ref)++{-| The `delay` transfer function emits the value of a signal from the+previous superstep, starting with the filler value given in the first+argument. It has to be a primitive, otherwise it could not be used to+prevent automatic delays. -}++delay :: a -- ^ initial output+ -> Signal a -- ^ the signal to delay+ -> SignalMonad (Signal a)+delay x0 s = makeSignal (SND x0 s)++{-| Dependency injection to allow aging signals whose output is not+necessarily needed to produce the current sample of the first+argument. It's equivalent to @(flip . liftA2 . flip) const@, as it+evaluates its second argument first. -}++keepAlive :: Signal a -- ^ the actual output+ -> Signal t -- ^ a signal guaranteed to age when this one is sampled+ -> Signal a+keepAlive s l = makeSignalUnsafe (SNKA s l)
+ FRP/Elerea/Param.hs view
@@ -0,0 +1,357 @@+{-|++This version differs from the simple one in providing an extra+argument to the sampling action that will be globally distributed to+every node and can be used to update the state. For instance, it can+hold the time step between the two samplings, but it could also encode+all the external input to the system.++The interface of this module differs from the old Elerea in the+following ways:++* the delta time argument is generalised to an arbitrary type, so it+ is possible to do without 'external' altogether in case someone+ wants to do so;++* there is no 'sampler' any more, it is substituted by 'join', as+ signals are monads;++* 'generator' has been conceptually simplified, so it's a more basic+ primitive now;++* there is no automatic delay in order to preserve semantic soundness+ (e.g. the monad laws for signals);++* all signals are aged regardless of whether they are sampled+ (i.e. their behaviour doesn't depend on the context any more);++* the user needs to cache the results of applicative operations to be+ reused in multiple places explicitly using the 'memo' combinator.++-}++module FRP.Elerea.Param+ ( Signal+ , SignalGen+ , start+ , external+ , externalMulti+ , delay+ , stateful+ , transfer+ , memo+ , generator+ , noise+ , getRandom+ , debug+ ) where++import Control.Applicative+import Control.Concurrent.MVar+import Control.Monad+import Control.Monad.Fix+import Data.IORef+import Data.Maybe+import System.Mem.Weak+import System.Random.Mersenne++-- | A signal can be thought of as a function of type @Nat -> a@, and+-- its 'Monad' instance agrees with that intuition. Internally, is+-- represented by a sampling computation.+newtype Signal p a = S { unS :: p -> IO a }++-- | A dynamic set of actions to update a network without breaking+-- consistency.+type UpdatePool p = [Weak (p -> IO (), IO ())]++-- | A signal generator is the only source of stateful signals.+-- Internally, computes a signal structure and adds the new variables+-- to an existing update pool.+newtype SignalGen p a = SG { unSG :: IORef (UpdatePool p) -> IO a }++-- | The phases every signal goes through during a superstep: before+-- or after sampling.+data Phase s a = Ready s | Aged s a++instance Functor (Signal p) where+ fmap = liftM++instance Applicative (Signal p) where+ pure = return+ (<*>) = ap++instance Monad (Signal p) where+ return = S . const . return+ S g >>= f = S $ \p -> g p >>= \x -> unS (f x) p++instance Functor (SignalGen p) where+ fmap = liftM++instance Applicative (SignalGen p) where+ pure = return+ (<*>) = ap++instance Monad (SignalGen p) where+ return = SG . const . return+ SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p++instance MonadFix (SignalGen p) where+ mfix f = SG $ \p -> mfix (($p).unSG.f)++-- | 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+-- result. The computation accepts a global parameter that will be+-- distributed to all signals. For instance, this can be the time+-- step, if we want to model continuous-time signals.+start :: SignalGen p (Signal p a) -- ^ the generator of the top-level signal+ -> IO (p -> IO a) -- ^ the computation to sample the signal+start (SG gen) = do+ pool <- newIORef []+ (S sample) <- gen pool++ ptrs0 <- readIORef pool+ writeIORef pool []+ (as0,cs0) <- unzip . map fromJust <$> mapM deRefWeak ptrs0+ let ageStatic param = mapM_ ($param) as0+ commitStatic = sequence_ cs0++ return $ \param -> do+ let update [] ptrs age commit = do+ writeIORef pool ptrs+ ageStatic param >> age+ commitStatic >> commit+ update (p:ps) ptrs age commit = do+ r <- deRefWeak p+ case r of+ Nothing -> update ps ptrs age commit+ Just (a,c) -> update ps (p:ptrs) (age >> a param) (commit >> c)++ res <- sample param+ ptrs <- readIORef pool+ update ptrs [] (return ()) (return ())+ return res++-- | Auxiliary function used by all the primitives that create a+-- mutable variable.+addSignal :: (p -> Phase s a -> IO a) -- ^ sampling function+ -> (p -> Phase s a -> IO ()) -- ^ aging function+ -> IORef (Phase s a) -- ^ the mutable variable behind the signal+ -> IORef (UpdatePool p) -- ^ the pool of update actions+ -> IO (Signal p a)+addSignal sample age ref pool = do+ let commit (Aged s _) = Ready s+ commit _ = error "commit error: signal not aged"++ sig = S $ \p -> readIORef ref >>= sample p++ update <- mkWeak sig (\p -> readIORef ref >>= age p, modifyIORef ref commit) Nothing+ modifyIORef pool (update:)+ return sig++-- | The 'delay' transfer function emits the value of a signal from+-- the previous superstep, starting with the filler value given in the+-- first argument.+delay :: a -- ^ initial output+ -> Signal p a -- ^ the signal to delay+ -> SignalGen p (Signal p a)+delay x0 (S s) = SG $ \pool -> do+ ref <- newIORef (Ready x0)++ let sample _ (Ready x) = return x+ sample _ (Aged _ x) = return x++ age p (Ready x) = s p >>= \x' -> x' `seq` writeIORef ref (Aged x' x)+ age _ _ = return ()++ addSignal sample age ref pool++-- | Memoising combinator. It can be used to cache results of+-- applicative combinators in case they are used in several+-- places. Other than that, it is equivalent to 'return'.+memo :: Signal p a -- ^ signal to memoise+ -> SignalGen p (Signal p a)+memo (S s) = SG $ \pool -> do+ ref <- newIORef (Ready undefined)++ let sample p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x) >> return x+ sample _ (Aged _ x) = return x++ age p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x)+ age _ _ = return ()++ addSignal sample age ref pool++-- | A reactive signal that takes the value to output from a monad+-- carried by its input. It is possible to create new signals in the+-- monad.+generator :: Signal p (SignalGen p a) -- ^ a stream of generators to potentially run+ -> SignalGen p (Signal p a)+generator (S gen) = SG $ \pool -> do+ ref <- newIORef (Ready undefined)++ let next p = ($pool).unSG =<< gen p++ sample p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x') >> return x'+ sample _ (Aged _ x) = return x++ age p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x')+ age _ _ = return ()++ addSignal sample age ref pool++-- | A signal that can be directly fed through the sink function+-- returned. This can be used to attach the network to the outer+-- world. Note that this is optional, as all the input of the network+-- can be fed in through the global parameter, although that is not+-- really convenient for many signals.+external :: a -- ^ initial value+ -> IO (Signal p a, a -> IO ()) -- ^ the signal and an IO function to feed it+external x = do+ ref <- newIORef x+ return (S (const (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.+externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ()) -- ^ a generator for the event signal and the associated sink+externalMulti = do+ var <- newMVar []+ return (SG $ \pool -> do+ let sig = S $ const (readMVar var)+ update <- mkWeak sig (const (return ()),takeMVar var >> putMVar var []) Nothing+ modifyIORef pool (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 following output is calculated from the previous one and+-- the value of the global parameter.+stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)+stateful x0 f = SG $ \pool -> do+ ref <- newIORef (Ready x0)++ let sample _ (Ready x) = return x+ sample _ (Aged _ x) = return x++ age p (Ready x) = let x' = f p x in x' `seq` writeIORef ref (Aged x' x)+ age _ _ = return ()++ addSignal sample age ref pool++-- | 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. Every output is derived from the current value of the+-- input signal, the global parameter and the previous output.+transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)+transfer x0 f (S s) = SG $ \pool -> do+ ref <- newIORef (Ready x0)++ let sample p (Ready x) = s p >>= \y -> let x' = f p y x in+ x' `seq` writeIORef ref (Aged x' x') >> return x'+ sample _ (Aged _ x) = return x++ age p (Ready x) = s p >>= \y -> let x' = f p y x in+ x' `seq` writeIORef ref (Aged x' x')+ age _ _ = return ()++ addSignal sample age ref pool++-- | A random signal.+noise :: MTRandom a => SignalGen p (Signal p a)+noise = memo (S (const randomIO))++-- | A random source within the 'SignalGen' monad.+getRandom :: MTRandom a => SignalGen p a+getRandom = SG (const randomIO)++-- | A printing action within the 'SignalGen' monad.+debug :: String -> SignalGen p ()+debug = SG . const . putStrLn++-- | The @Show@ instance is only defined for the sake of 'Num'...+instance Show (Signal p a) where+ showsPrec _ _ s = "<SIGNAL>" ++ s++-- | Equality test is impossible.+instance Eq (Signal p a) where+ _ == _ = False++-- | Error message for unimplemented instance functions.+unimp :: String -> a+unimp = error . ("Signal: "++)++instance Ord t => Ord (Signal p t) where+ compare = unimp "compare"+ min = liftA2 min+ max = liftA2 max++instance Enum t => Enum (Signal p 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"++instance Bounded t => Bounded (Signal p t) where+ minBound = pure minBound+ maxBound = pure maxBound++instance Num t => Num (Signal p t) where+ (+) = liftA2 (+)+ (-) = liftA2 (-)+ (*) = liftA2 (*)+ signum = fmap signum+ abs = fmap abs+ negate = fmap negate+ fromInteger = pure . fromInteger++instance Real t => Real (Signal p t) where+ toRational = unimp "toRational"++instance Integral t => Integral (Signal p 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"++instance Fractional t => Fractional (Signal p t) where+ (/) = liftA2 (/)+ recip = fmap recip+ fromRational = pure . fromRational++instance Floating t => Floating (Signal p 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
+ FRP/Elerea/Simple.hs view
@@ -0,0 +1,435 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{-|++This module provides leak-free and referentially transparent+higher-order discrete signals. For a not entirely trivial example,+let's create a dynamic collection of countdown timers, where each+expired timer is removed from the collection. First of all, we'll+need a simple tester function:++@+ sigtest gen = 'replicateM' 15 '=<<' 'start' gen+@++We can try it with a trivial example:++@+ \> sigtest $ 'stateful' 2 (+3)+ [2,5,8,11,14,17,20,23,26,29,32,35,38,41,44,47]+@++Our first definition will be a signal representing a simple named+timer:++@+ countdown :: String -\> Int -\> SignalGen (Signal (String,Maybe Int))+ countdown name t = do+ let tick prev = do { t \<- prev ; 'guard' (t \> 0) ; 'return' (t-1) }+ timer \<- 'stateful' (Just t) tick+ 'return' ((,) name '<$>' timer)+@++Let's see if it works:++@+ \> sigtest $ countdown \"foo\" 4+ [(\"foo\",Just 4),(\"foo\",Just 3),(\"foo\",Just 2),(\"foo\",Just 1),(\"foo\",Just 0),+ (\"foo\",Nothing),(\"foo\",Nothing),(\"foo\",Nothing),...]+@++Next, we will define a timer source that takes a list of timer names,+starting values and start times and creates a signal that delivers the+list of new timers at every point:++@+ timerSource :: [(String, Int, Int)] -\> SignalGen (Signal [Signal (String, Maybe Int)])+ timerSource ts = do+ let gen t = 'mapM' ('uncurry' countdown) newTimers+ where newTimers = [(n,v) | (n,v,st) \<- ts, st == t]+ cnt \<- 'stateful' 0 (+1)+ 'generator' (gen '<$>' cnt)+@++Now we need to encapsulate the timer source signal in another signal+expression that takes care of maintaining the list of live timers.+Since working with dynamic collections is a recurring task, let's+define a generic combinator that maintains a dynamic list of signals+given a source and a test that tells from the output of each signal+whether it should be kept. We can use @mdo@ expressions (a variant of+@do@ expressions allowing forward references) as syntactic sugar for+'mfix' to make life easier:++@+ collection :: Signal [Signal a] -\> (a -\> Bool) -\> SignalGen (Signal [a])+ collection source isAlive = mdo+ sig \<- 'delay' [] ('map' 'snd' '<$>' collWithVals')+ coll \<- 'memo' ('liftA2' (++) source sig)+ let collWithVals = 'zip' '<$>' ('sequence' '=<<' coll) '<*>' coll+ collWithVals' \<- 'memo' ('filter' (isAlive . 'fst') '<$>' collWithVals)+ 'return' $ 'map' 'fst' '<$>' collWithVals'+@++We need recursion to define the @coll@ signal as a delayed version of+its continuation, which does not contain signals that need to be+removed in the current sample. At every point of time the running+collection is concatenated with the source. We define @collWithVals@,+which simply pairs up every signal with its current output. The+output is obtained by extracting the current value of the signal+container and sampling each element with 'sequence'. We can then+derive @collWithVals'@, which contains only the signals that must be+kept for the next round along with their output. Both @coll@ and+@collWithVals'@ have to be memoised, because they are used more than+once (the program would work without that, but it would recalculate+both signals each time they are used). By throwing out the respective+parts, we can get both the final output and the collection for the+next step (@coll'@).++Now we can easily finish the original task:++@+ timers :: [(String, Int, Int)] -\> SignalGen (Signal [(String, Int)])+ timers timerData = do+ src \<- timerSource timerData+ getOutput '<$>' collection src ('isJust' . 'snd')+ where getOutput = 'fmap' ('map' (\\(name,Just val) -> (name,val)))+@++As a test, we can start four timers: /a/ at t=0 with value 3, /b/ and+/c/ at t=1 with values 5 and 3, and /d/ at t=3 with value 4:++@+ \> sigtest $ timers [(\"a\",3,0),(\"b\",5,1),(\"c\",3,1),(\"d\",4,3)]+ [[(\"a\",3)],[(\"b\",5),(\"c\",3),(\"a\",2)],[(\"b\",4),(\"c\",2),(\"a\",1)],+ [(\"d\",4),(\"b\",3),(\"c\",1),(\"a\",0)],[(\"d\",3),(\"b\",2),(\"c\",0)],+ [(\"d\",2),(\"b\",1)],[(\"d\",1),(\"b\",0)],[(\"d\",0)],[],[],[],[],[],[],[]]+@++If the noise of the applicative lifting operators feels annoying, she+(<http://personal.cis.strath.ac.uk/~conor/pub/she/>) comes to the+save. Among other features it provides idiom brackets, which can+substitute the explicit lifting. For instance, it allows us to define+@collection@ this way:++@+ collection :: Stream [Stream a] -> (a -> Bool) -> StreamGen (Stream [a])+ collection source isAlive = mdo+ sig \<- 'delay' [] (|'map' ~'snd' collWithVals'|)+ coll \<- 'memo' (|source ++ sig|)+ collWithVals' \<- 'memo' (|'filter' ~(isAlive . 'fst') (|'zip' ('sequence' '=<<' coll) coll|)|)+ 'return' (|'map' ~'fst' collWithVals'|)+@++-}++module FRP.Elerea.Simple+ ( Signal+ , SignalGen+ , start+ , external+ , externalMulti+ , delay+ , generator+ , memo+ , stateful+ , transfer+ , noise+ , getRandom+ ) where++import Control.Applicative+import Control.Concurrent.MVar+import Control.Monad+import Control.Monad.Fix+import Data.IORef+import Data.Maybe+import System.Mem.Weak+import System.Random.Mersenne++-- | A signal can be thought of as a function of type @Nat -> a@,+-- where the argument is the sampling time, and the 'Monad' instance+-- agrees with the intuition (bind corresponds to extracting the+-- current sample).+newtype Signal a = S (IO a) deriving (Functor, Applicative, Monad)++-- | A dynamic set of actions to update a network without breaking+-- consistency.+type UpdatePool = [Weak (IO (),IO ())]++-- | A signal generator is the only source of stateful signals. It+-- can be thought of as a function of type @Nat -> a@, where the+-- result is an arbitrary data structure that can potentially contain+-- new signals, and the argument is the creation time of these new+-- signals. It exposes the 'MonadFix' interface, which makes it+-- possible to define signals in terms of each other.+newtype SignalGen a = SG { unSG :: 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++instance Applicative SignalGen where+ pure = return+ (<*>) = ap++instance Monad SignalGen where+ return = SG . const . return+ SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p++instance MonadFix SignalGen where+ mfix f = SG $ \p -> mfix (($p).unSG.f)++-- | 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+-- result. This is the only way to extract a signal generator outside+-- the network, and it is equivalent to passing zero to the function+-- representing the generator.+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+ 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++-- | Auxiliary function used by all the primitives that create a+-- mutable variable.+addSignal :: (a -> IO a) -- ^ sampling function+ -> (a -> IO ()) -- ^ aging function+ -> IORef (Phase a) -- ^ the mutable variable behind the signal+ -> 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 ()++ 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++ updateActions <- mkWeak sig (upd,fin) Nothing+ modifyIORef pool (updateActions:)+ return sig++-- | The 'delay' transfer function emits the value of a signal from+-- the previous superstep, starting with the filler value given in the+-- first argument. It can be thought of as the following function+-- (which should also make it clear why the return value is+-- 'SignalGen'):+--+-- @+-- delay x0 s t_start t_sample+-- | t_start == t_sample = x0+-- | t_start < t_sample = s (t_sample-1)+-- | otherwise = error \"Premature sample!\"+-- @+--+-- The way signal generators are extracted ensures that the error can+-- never happen.+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 -> do+ ref <- newIORef (Ready x0)++ let update x = s >>= \x' -> x' `seq` writeIORef ref (Updated x' x)++ 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+-- the time of generation. It is possible to create new signals in+-- the monad. It can be thought of as the following function:+--+-- @+-- generator g t_start t_sample = g t_sample 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+-- for efficiency reasons. However, this state is not carried between+-- samples, therefore starting time doesn't matter and can be ignored.+generator :: Signal (SignalGen a) -- ^ the signal of generators to run+ -> SignalGen (Signal a) -- ^ the signal of generated structures+generator (S s) = SG $ \pool -> do+ ref <- newIORef (Ready undefined)++ let sample = do SG g <- s+ x <- g pool+ writeIORef ref (Updated undefined x)+ return x++ addSignal (const sample) (const (sample >> return ())) ref pool++-- | 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.+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)++ let sample = s >>= \x -> writeIORef ref (Updated undefined x) >> return x++ addSignal (const sample) (const (sample >> return ())) ref pool++-- | A signal that can be directly fed through the sink function+-- returned. This can be used to attach the network to the outer+-- world.+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)++-- | 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.+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)+ )++-- | A pure stateful signal. The initial state is the first output,+-- and every subsequent state is derived from the preceding one by+-- applying a pure transformation. It is equivalent to the following+-- expression:+--+-- @+-- stateful x0 f = 'mfix' $ \sig -> 'delay' x0 (f '<$>' sig)+-- @+stateful :: a -- ^ initial state+ -> (a -> a) -- ^ state transformation+ -> 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 equivalent to the following+-- expression:+--+-- @+-- transfer x0 f s = 'mfix' $ \sig -> 'liftA2' f s '<$>' 'delay' x0 sig+-- @+transfer :: a -- ^ initial internal state+ -> (t -> a -> a) -- ^ state updater function+ -> Signal t -- ^ input signal+ -> SignalGen (Signal a)+transfer x0 f s = mfix $ \sig -> liftA2 f s <$> delay x0 sig++-- | A random signal.+noise :: MTRandom a => SignalGen (Signal a)+noise = memo (S randomIO)++-- | A random source within the 'SignalGen' monad.+getRandom :: MTRandom a => SignalGen a+getRandom = SG (const randomIO)++-- The Show instance is only defined for the sake of Num...+instance Show (Signal a) where+ showsPrec _ _ s = "<SIGNAL>" ++ s++-- Equality test is impossible.+instance Eq (Signal a) where+ _ == _ = 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++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"++instance Bounded t => Bounded (Signal t) where+ 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++instance Real t => Real (Signal t) where+ 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"++instance Fractional t => Fractional (Signal t) where+ (/) = 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
elerea.cabal view
@@ -1,5 +1,5 @@ Name: elerea-Version: 1.2.3+Version: 2.0.0 Cabal-Version: >= 1.2 Synopsis: A minimalistic FRP library Category: reactivity, FRP@@ -37,13 +37,13 @@ Library Exposed-Modules:- FRP.Elerea- FRP.Elerea.Internal- FRP.Elerea.Graph- FRP.Elerea.Experimental- FRP.Elerea.Experimental.Simple- FRP.Elerea.Experimental.Param- FRP.Elerea.Experimental.Delayed+ FRP.Elerea.Legacy+ FRP.Elerea.Legacy.Graph+ FRP.Elerea.Legacy.Internal+ FRP.Elerea.Simple+ FRP.Elerea.Param+ FRP.Elerea.Clocked+ FRP.Elerea.Delayed Build-Depends: base >= 3 && < 5, containers, mersenne-random ghc-options: -Wall -O2