module FRP.Grapefruit.Signal.Incremental ( -- * Incremental signal type ISignal, Incremental (type Diff, patch, type ValidationState, validationInit, validationStep), Diff (Replacement), -- * Monolithic values Monolithic (Monolithic), -- * Construction construct, -- * Queries withInit, updates, -- * Conversion toSSignal, monolithicFromSSignal, monolithicToSSignal, -- * Composition const, map, combine, -- * Connectors consumer ) where -- Prelude import Prelude hiding (const, map) -- Data import Data.Semigroup as Semigroup -- Internal import Internal.Signal.Segmented as SSignal (SSignal (SSignal), scan) -- FRP.Grapefruit import FRP.Grapefruit.Setup as Setup import FRP.Grapefruit.Circuit as Circuit import FRP.Grapefruit.Signal as Signal import FRP.Grapefruit.Signal.Discrete as DSignal hiding (map, consumer) import qualified FRP.Grapefruit.Signal.Discrete as DSignal {- Since an ISignal may be derived from an SSignal, it can indirectly depend on a CSignal. So access to the initial value must be forbidden for the same reason it is forbidden for SSignals. -} -- #> should also work for ISignals. -- * Incremental signal type data ISignal era val = ISignal val (DSignal era (Diff val)) class (Semigroup (Diff val)) => Incremental val where data Diff val :: * patch :: val -> Diff val -> val type ValidationState val :: * validationInit :: val -> ValidationState val validationStep :: Diff val -> ValidationState val -> Maybe (ValidationState val) -- * Monolithic values newtype Monolithic val = Monolithic val instance Semigroup (Diff (Monolithic val)) where _ <> monolithic2 = monolithic2 instance Incremental (Monolithic val) where data Diff (Monolithic val) = Replacement val patch _ (Replacement val) = Monolithic val type ValidationState (Monolithic val) = () validationInit _ = () validationStep _ _ = Just () -- * Construction construct :: (Incremental val) => val -> DSignal era (Diff val) -> ISignal era val construct init diffs = ISignal init (DSignal.stateful (validationInit init) (fmap diffToTrans diffs)) where diffToTrans diff state = case validationStep diff state of Nothing -> error $ "grapefruit-frp: " ++ "incremental signal validation failure" Just state' -> (diff,state') -- * Queries withInit :: (Signal signal) => ISignal era val -> (val -> signal era val') -> signal era val' withInit (ISignal init _) cont = cont init updates :: ISignal era val -> DSignal era (Diff val) updates (ISignal _ upd) = upd -- * Conversion toSSignal :: (Incremental val) => ISignal era val -> SSignal era val toSSignal (ISignal init upd) = SSignal.scan init patch upd monolithicFromSSignal :: SSignal era val -> ISignal era (Monolithic val) monolithicFromSSignal (SSignal init upd) = ISignal (Monolithic init) (fmap Replacement upd) monolithicToSSignal :: ISignal era (Monolithic val) -> SSignal era val monolithicToSSignal = fmap (\(Monolithic val) -> val) . toSSignal -- * Composition -- analogous to pure const :: (Incremental val) => val -> ISignal era val const val = ISignal val DSignal.empty -- analogous to fmap map :: (Incremental val, Incremental val') => (val -> (val',state)) -> (Diff val -> state -> (Diff val',state)) -> (ISignal era val -> ISignal era val') map start step (ISignal init upd) = ISignal init' upd' where (init',initState) = start init upd' = DSignal.stateful initState (fmap step upd) -- analogous to liftA2 combine :: (Incremental val1, Incremental val2, Incremental val') => (val1 -> val2 -> (val',state)) -> (Diff val1 -> state -> (Diff val',state)) -> (Diff val2 -> state -> (Diff val',state)) -> (ISignal era val1 -> ISignal era val2 -> ISignal era val') combine start step1 step2 (ISignal init1 upd1) (ISignal init2 upd2) = ISignal init' upd' where (init',initState) = start init1 init2 upd' = DSignal.stateful initState (unionWith transCombine (fmap step1 upd1) (fmap step2 upd2)) transCombine trans1 trans2 state = let (diff1',state') = trans1 state (diff2',state'') = trans2 state' in (diff1' <> diff2',state'') -- * Connectors consumer :: (val -> IO ()) -> (Diff val -> IO ()) -> Consumer ISignal val consumer initHdlr updHdlr = Consumer $ proc (ISignal init upd) -> do putSetup -< Setup.fromIO $ initHdlr init consume (DSignal.consumer updHdlr) -< upd