keera-hails-reactivevalues 0.1.0 → 0.1.2.1
raw patch · 2 files changed
+157/−48 lines, 2 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ Data.ReactiveValue: (&&&) :: (Monad m, ReactiveValueWrite a b m, ReactiveValueWrite c b m) => a -> c -> ReactiveFieldWrite m b
+ Data.ReactiveValue: (<^>) :: (Monad m, ReactiveValueRead a b m) => (b -> c) -> a -> ReactiveFieldRead m c
+ Data.ReactiveValue: constW :: (Monad m, ReactiveValueWrite v a m) => a -> v -> ReactiveFieldWrite m b
+ Data.ReactiveValue: eventR :: Monad m => (m () -> m ()) -> ReactiveFieldRead m ()
+ Data.ReactiveValue: guardRO' :: (Monad m, ReactiveValueRead c a m) => c -> (a -> Bool) -> ReactiveFieldRead m a
+ Data.ReactiveValue: lMerge :: (Monad m, ReactiveValueRead a v m, ReactiveValueRead b v m) => a -> b -> ReactiveFieldRead m v
+ Data.ReactiveValue: liftR3 :: (Monad m, ReactiveValueRead a b m, ReactiveValueRead c d m, ReactiveValueRead e f m) => (b -> d -> f -> g) -> a -> c -> e -> ReactiveFieldRead m g
+ Data.ReactiveValue: rMerge :: (Monad m, ReactiveValueRead a v m, ReactiveValueRead b v m) => a -> b -> ReactiveFieldRead m v
- Data.ReactiveValue: class ReactiveValueRead a b m | a -> b
+ Data.ReactiveValue: class ReactiveValueRead a b m | a -> b, a -> m
- Data.ReactiveValue: class ReactiveValueWrite a b m
+ Data.ReactiveValue: class ReactiveValueWrite a b m | a -> b, a -> m
- Data.ReactiveValue: liftMR :: (Monad m, ReactiveValueRead a b m) => a -> (b -> m c) -> ReactiveFieldRead m c
+ Data.ReactiveValue: liftMR :: (Monad m, ReactiveValueRead a b m) => (b -> m c) -> a -> ReactiveFieldRead m c
- Data.ReactiveValue: liftMW :: (Monad m, ReactiveValueWrite a b m) => a -> (c -> m b) -> ReactiveFieldWrite m c
+ Data.ReactiveValue: liftMW :: (Monad m, ReactiveValueWrite a b m) => (c -> m b) -> a -> ReactiveFieldWrite m c
- Data.ReactiveValue: liftR :: (Monad m, ReactiveValueRead a b m) => a -> (b -> c) -> ReactiveFieldRead m c
+ Data.ReactiveValue: liftR :: (Monad m, ReactiveValueRead a b m) => (b -> c) -> a -> ReactiveFieldRead m c
- Data.ReactiveValue: liftR2 :: (Monad m, ReactiveValueRead a b m, ReactiveValueRead c d m) => a -> c -> (b -> d -> e) -> ReactiveFieldRead m e
+ Data.ReactiveValue: liftR2 :: (Monad m, ReactiveValueRead a b m, ReactiveValueRead c d m) => (b -> d -> e) -> a -> c -> ReactiveFieldRead m e
- Data.ReactiveValue: liftRW :: (Monad m, ReactiveValueReadWrite a b m) => a -> BijectiveFunc b c -> ReactiveFieldReadWrite m c
+ Data.ReactiveValue: liftRW :: (Monad m, ReactiveValueReadWrite a b m) => BijectiveFunc b c -> a -> ReactiveFieldReadWrite m c
- Data.ReactiveValue: liftRW2 :: (Monad m, ReactiveValueReadWrite a b m, ReactiveValueReadWrite c d m) => a -> c -> BijectiveFunc e (b, d) -> ReactiveFieldReadWrite m e
+ Data.ReactiveValue: liftRW2 :: (Monad m, ReactiveValueReadWrite a b m, ReactiveValueReadWrite c d m) => BijectiveFunc e (b, d) -> a -> c -> ReactiveFieldReadWrite m e
- Data.ReactiveValue: liftW :: (Monad m, ReactiveValueWrite a b m) => a -> (c -> b) -> ReactiveFieldWrite m c
+ Data.ReactiveValue: liftW :: (Monad m, ReactiveValueWrite a b m) => (c -> b) -> a -> ReactiveFieldWrite m c
- Data.ReactiveValue: liftW2 :: (Monad m, ReactiveValueWrite a b m, ReactiveValueWrite d e m) => a -> d -> (c -> (b, e)) -> ReactiveFieldWrite m c
+ Data.ReactiveValue: liftW2 :: (Monad m, ReactiveValueWrite a b m, ReactiveValueWrite d e m) => (c -> (b, e)) -> a -> d -> ReactiveFieldWrite m c
Files
- keera-hails-reactivevalues.cabal +1/−1
- src/Data/ReactiveValue.hs +156/−47
keera-hails-reactivevalues.cabal view
@@ -7,7 +7,7 @@ -- The package version. See the Haskell package versioning policy -- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for -- standards guiding when and how versions should be incremented.-Version: 0.1.0+Version: 0.1.2.1 -- A short (one-line) description of the package. Synopsis: Haskell on Rails - Reactive Values
src/Data/ReactiveValue.hs view
@@ -1,11 +1,24 @@ {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-} {-# LANGUAGE FlexibleContexts #-}--- | This is a more general, cleaner interface that allows Model to Model--- synchronization and view to view.+-- | Reactive Values are typed mutable variables with a change notification+-- mechanism. ----- It is meant to replace Hails.MVC.Controller.Reactive as soon as--- we do not need to provide an undefined value for the function--- reactiveValueOnCanRead.+-- They are defined by providing a way to read the value, a way to change it,+-- and a way to install an event listener when the value has changed.+--+-- RVs are pruposely defined in an abstract way, as a type class. GUI toolkits,+-- for instance, can use existing event-handling installing mechanisms to+-- enclose widget attributes as Reactive Values, without the need for an+-- extra layer.+--+-- RVs are complemented with Relation-building functions, which+-- enable pairing RVs during execution so that they are kept in+-- sync for the duration of the program.+--+-- This module only defines RVs and operations on them. For connections+-- to existing backends (GUIs, devices, files, network, FRP), see+-- https://github.com/keera-studios/keera-hails+-- module Data.ReactiveValue where import Control.Monad@@ -13,15 +26,15 @@ -- in the source category import Data.Functor.Contravariant --- * Reactive values: common interface+-- * Reactive values: common interface for all RVs -- | Readable reactive values-class ReactiveValueRead a b m | a -> b where+class ReactiveValueRead a b m | a -> b, a -> m where reactiveValueOnCanRead :: a -> m () -> m () reactiveValueRead :: a -> m b -- | Writable reactive values-class ReactiveValueWrite a b m where+class ReactiveValueWrite a b m | a -> b, a -> m where reactiveValueWrite :: a -> b -> m () -- | Read-write reactive values@@ -34,17 +47,20 @@ infix 9 =:> infix 9 <:= --- | Left to right+-- | Left to right RV synchronisation function. If the value on the left+-- changes, the one on the right is updated accordingly. (=:>) :: Monad m => (ReactiveValueRead a b m, ReactiveValueWrite c b m) => a -> c -> m () (=:>) v1 v2 = reactiveValueOnCanRead v1 sync1 where sync1 = reactiveValueRead v1 >>= reactiveValueWrite v2 --- | Right-to-left+-- | Right-to-left RV synchronisation function. If the value on the right+-- changes, the one on the left is updated accordingly. (<:=) :: Monad m => (ReactiveValueRead a b m, ReactiveValueWrite c b m) => c -> a -> m () (<:=) v2 v1 = reactiveValueOnCanRead v1 sync1 where sync1 = reactiveValueRead v1 >>= reactiveValueWrite v2 --- | Bidirectional+-- | Bidirectional synchronisation. When either value changes, the other+-- is updated accordingly. (=:=) :: Monad m => (ReactiveValueReadWrite a b m, ReactiveValueReadWrite c b m) => a -> c -> m () (=:=) v1 v2 = do -- This is often async, so the fact that one comes before the other does not guarantee@@ -56,7 +72,12 @@ -- where sync1 = reactiveValueRead v1 >>= reactiveValueWrite v2 -- sync2 = reactiveValueRead v2 >>= reactiveValueWrite v1 --- * Purely functional implementation+-- * Purely functional implementation of RVs.+--+-- These are used internally for combinators that need to return RV instances. They can+-- also be used to write new backends and library extensions, but they are not+-- recommended to enclose application models. For that purpose, see light models and+-- protected models instead. -- ** Setters, getters and notifiers type FieldGetter m a = m a@@ -110,59 +131,113 @@ where notifier _ = return () getter = return e +-- | TODO: Bad name. Should be eliminated or extended with a setter. initRW :: Monad m => a -> ReactiveFieldRead m a initRW e = ReactiveFieldRead getter notifier where notifier _ = return () getter = return e -- ** Lifting onto readable values-liftR :: (Monad m, ReactiveValueRead a b m) => a -> (b -> c) -> ReactiveFieldRead m c-liftR e f = ReactiveFieldRead getter notifier++-- | Lift a transformation onto a RV. Note that this creates a new+-- RV, it does not modify the existing RV.+liftR :: (Monad m, ReactiveValueRead a b m) => (b -> c) -> a -> ReactiveFieldRead m c+liftR f e = ReactiveFieldRead getter notifier where notifier = reactiveValueOnCanRead e getter = liftM f (reactiveValueRead e) +-- | Shorter name for 'liftR'+(<^>) :: (Monad m, ReactiveValueRead a b m) => (b -> c) -> a -> ReactiveFieldRead m c+(<^>) = liftR++-- | Lift a transformation onto two RVs. Note that this creates a new+-- RV, it does not modify the existing RVs. When either RV changes,+-- the new one triggers a change. liftR2 :: (Monad m, ReactiveValueRead a b m, ReactiveValueRead c d m)- => a -> c -> (b -> d -> e) -> ReactiveFieldRead m e-liftR2 e1 e2 f = ReactiveFieldRead getter notifier+ => (b -> d -> e) -> a -> c -> ReactiveFieldRead m e+liftR2 f e1 e2 = ReactiveFieldRead getter notifier where getter = do v1 <- reactiveValueRead e1 v2 <- reactiveValueRead e2 return (f v1 v2) notifier p = do reactiveValueOnCanRead e1 p reactiveValueOnCanRead e2 p +-- | Lift a transformation onto three RVs. Note that this creates a new+-- RV, it does not modify the existing RVs. When either RV changes,+-- the new one triggers a change.+liftR3 :: ( Monad m, ReactiveValueRead a b m, ReactiveValueRead c d m+ , ReactiveValueRead e f m)+ => (b -> d -> f -> g) -> a -> c -> e -> ReactiveFieldRead m g+liftR3 f e1 e2 e3 = ReactiveFieldRead getter notifier+ where getter = do v1 <- reactiveValueRead e1+ v2 <- reactiveValueRead e2+ v3 <- reactiveValueRead e3+ return (f v1 v2 v3)+ notifier p = do reactiveValueOnCanRead e1 p+ reactiveValueOnCanRead e2 p+ reactiveValueOnCanRead e3 p++-- | Lift a parameterised monadic transformation onto an RV.+-- -- Same as lifting join . f?-liftMR :: (Monad m, ReactiveValueRead a b m) => a -> (b -> m c) -> ReactiveFieldRead m c-liftMR e f = ReactiveFieldRead getter notifier+liftMR :: (Monad m, ReactiveValueRead a b m) => (b -> m c) -> a -> ReactiveFieldRead m c+liftMR f e = ReactiveFieldRead getter notifier where notifier = reactiveValueOnCanRead e getter = f =<< reactiveValueRead e -- ** Lifting onto writeable values++-- | Create a constant writable RV.+--+constW :: (Monad m, ReactiveValueWrite v a m) => a -> v -> ReactiveFieldWrite m b+constW c v = ReactiveFieldWrite $ \_ -> reactiveValueWrite v c++-- | Lift a transformation onto an RV. This creates a new RV, it does+-- not actually modify the old RV (when this one is written to, so will+-- be the old one, but both will keep existing somewhat independently). liftW :: (Monad m, ReactiveValueWrite a b m)- => a -> (c -> b) -> ReactiveFieldWrite m c-liftW e f = ReactiveFieldWrite setter+ => (c -> b) -> a -> ReactiveFieldWrite m c+liftW f e = ReactiveFieldWrite setter where setter = reactiveValueWrite e . f +-- | Lift a transformation onto two RVs. This creates a new RV, it does+-- not actually modify the old RVs (when this one is written to, so will+-- be the old ones, but both will keep existing somewhat independently). liftW2 :: (Monad m, ReactiveValueWrite a b m, ReactiveValueWrite d e m)- => a -> d -> (c -> (b,e)) -> ReactiveFieldWrite m c-liftW2 e1 e2 f = ReactiveFieldWrite setter+ => (c -> (b,e)) -> a -> d -> ReactiveFieldWrite m c+liftW2 f e1 e2 = ReactiveFieldWrite setter where setter x = do let (v1,v2) = f x reactiveValueWrite e1 v1 reactiveValueWrite e2 v2 +-- | Binary writable replicator.+--+-- r1 &&& r2 = liftW2 (\x -> (x,x)) r1 r2+--+(&&&) :: (Monad m, ReactiveValueWrite a b m, ReactiveValueWrite c b m)+ => a -> c -> ReactiveFieldWrite m b+(&&&) v1 v2 = ReactiveFieldWrite $ \x -> do+ reactiveValueWrite v1 x+ reactiveValueWrite v2 x+++-- | Lift a parameterised monadic transformation onto an RV. liftMW :: (Monad m, ReactiveValueWrite a b m)- => a -> (c -> m b) -> ReactiveFieldWrite m c-liftMW e f = ReactiveFieldWrite setter+ => (c -> m b) -> a -> ReactiveFieldWrite m c+liftMW f e = ReactiveFieldWrite setter where setter x = reactiveValueWrite e =<< f x +-- | Make a RW RV read only readOnly :: ReactiveValueRead r a m => r -> ReactiveFieldRead m a readOnly r = ReactiveFieldRead (reactiveValueRead r) (reactiveValueOnCanRead r) +-- | Make a RW RV write only writeOnly :: ReactiveValueWrite r a m => r -> ReactiveFieldWrite m a writeOnly r = ReactiveFieldWrite (reactiveValueWrite r) --- * Lift monadic operations+-- ** Lift monadic actions/sinks (setters) and sources (getters) --- ** Lifting (sink) computations into writable RVs.+-- *** Lifting (sink) computations into writable RVs. -- | Wrap a monadic computation in a writable reactive value. wrapMW :: (a -> m ()) -> ReactiveFieldWrite m a@@ -182,7 +257,7 @@ wrapDo_ :: m () -> ReactiveFieldWrite m () wrapDo_ f = wrapMW (\() -> f) --- ** Lifting (source) computations into readable RVs.+-- *** Lifting (source) computations into readable RVs. -- | Wrap an reading operation and an notification installer in -- a readable reactive value.@@ -195,6 +270,10 @@ wrapMRPassive :: Monad m => m a -> ReactiveFieldRead m a wrapMRPassive f = ReactiveFieldRead f (const (return ())) +-- | Wrap event-handler installers in RVs+eventR :: Monad m => (m () -> m ()) -> ReactiveFieldRead m ()+eventR notifInstaller = ReactiveFieldRead (return ()) notifInstaller+ -- ** Lifting onto read-write values -- *** Bijections@@ -216,22 +295,22 @@ -- *** Actual lifting liftRW :: (Monad m, ReactiveValueReadWrite a b m)- => a -> BijectiveFunc b c -> ReactiveFieldReadWrite m c-liftRW e (BijectiveFunc (f1, f2)) = ReactiveFieldReadWrite setter getter notifier- where ReactiveFieldRead getter notifier = liftR e f1- ReactiveFieldWrite setter = liftW e f2+ => BijectiveFunc b c -> a -> ReactiveFieldReadWrite m c+liftRW (BijectiveFunc (f1, f2)) e = ReactiveFieldReadWrite setter getter notifier+ where ReactiveFieldRead getter notifier = liftR f1 e+ ReactiveFieldWrite setter = liftW f2 e liftRW2 :: (Monad m, ReactiveValueReadWrite a b m, ReactiveValueReadWrite c d m)- => a -> c -> BijectiveFunc e (b,d) -> ReactiveFieldReadWrite m e-liftRW2 e1 e2 (BijectiveFunc (f1, f2)) = ReactiveFieldReadWrite setter getter notifier- where ReactiveFieldRead getter notifier = liftR2 e1 e2 (curry f2)- ReactiveFieldWrite setter = liftW2 e1 e2 f1+ => BijectiveFunc e (b,d) -> a -> c -> ReactiveFieldReadWrite m e+liftRW2 (BijectiveFunc (f1, f2)) e1 e2 = ReactiveFieldReadWrite setter getter notifier+ where ReactiveFieldRead getter notifier = liftR2 (curry f2) e1 e2+ ReactiveFieldWrite setter = liftW2 f1 e1 e2 pairRW :: (Monad m, ReactiveValueReadWrite a b m, ReactiveValueReadWrite c d m) => a -> c -> ReactiveFieldReadWrite m (b, d)-pairRW a b = liftRW2 a b (bijection (id, id))+pairRW a b = liftRW2 (bijection (id, id)) a b {-# INLINE eqCheck #-} eqCheck :: (Eq v, Monad m) => ReactiveFieldReadWrite m v -> ReactiveFieldReadWrite m v@@ -252,6 +331,18 @@ reactiveValueModify :: (Monad m, ReactiveValueReadWrite a b m) => a -> (b -> b) -> m () reactiveValueModify r f = reactiveValueWrite r . f =<< reactiveValueRead r +-- * Merging++-- | Left merge (give priority to the value on the left)+lMerge :: (Monad m, ReactiveValueRead a v m, ReactiveValueRead b v m)+ => a -> b -> ReactiveFieldRead m v+lMerge = liftR2 (\a _ -> a)++-- | Right merge (give priority to the value on the left)+rMerge :: (Monad m, ReactiveValueRead a v m, ReactiveValueRead b v m)+ => a -> b -> ReactiveFieldRead m v+rMerge = liftR2 (\_ b -> b)+ -- * Deactivating reactive values -- | Turning an active RV into a passive one (does not propagate changes)@@ -270,6 +361,17 @@ passivelyRW rv = ReactiveFieldReadWrite (reactiveValueWrite rv) (reactiveValueRead rv) (\_ -> return ()) +-- | A form of binary readable lifting that passifies the second RV but reads+-- exclusively from it.+--+-- governingR r1 r2 = rMerge r1 (passively r2)++governingR :: (ReactiveValueRead a b m, ReactiveValueRead c d m)+ => a -> c -> ReactiveFieldRead m d+governingR r c = ReactiveFieldRead getter notifier+ where getter = reactiveValueRead c+ notifier = reactiveValueOnCanRead r+ -- * Conditionals -- Check condition and notify only when holds@@ -317,24 +419,31 @@ where when' m = do x <- reactiveValueRead c when x m --- * Category theoretic definitions+-- Check condition and notify only when holds+guardRO' :: (Monad m, ReactiveValueRead c a m)+ => c+ -> (a -> Bool)+ -> ReactiveFieldRead m a+guardRO' c p = ReactiveFieldRead getter notifier+ where getter = reactiveValueRead c+ -- If either changes, the value *may* be propagated+ notifier = reactiveValueOnCanRead c . when' + -- Propagate only if the condition holds+ where when' m = do x <- reactiveValueRead c+ when (p x) m++-- Category theoretic definitions+ -- Functor definitions instance (Functor m, Monad m) => Functor (ReactiveFieldRead m) where- fmap = flip liftR+ fmap = liftR -- FIXME: I might not want to provide this: the contravariant library -- depends on transformers. -- (ReactiveFieldRead getter notifier) = ReactiveFieldRead (fmap f getter) notifier instance (Monad m) => Contravariant (ReactiveFieldWrite m) where- contramap = flip liftW+ contramap = liftW instance Monad m => GFunctor (ReactiveFieldReadWrite m) BijectiveFunc where- gmap = flip liftRW---- | Temporary: will be moved to Keera Hails' Reactive Values library.-governingR :: (ReactiveValueRead a b m, ReactiveValueRead c d m)- => a -> c -> ReactiveFieldRead m d-governingR r c = ReactiveFieldRead getter notifier- where getter = reactiveValueRead c- notifier = reactiveValueOnCanRead r+ gmap = liftRW