From 51d6500c6f2a52baa145aa7e0ee56eb4db057b86 Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Sat, 9 Nov 2019 15:16:36 +0100 Subject: [PATCH 1/7] Added foldDynM --- src/Specular/FRP/Base.purs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Specular/FRP/Base.purs b/src/Specular/FRP/Base.purs index 4b3111f..0f292a2 100644 --- a/src/Specular/FRP/Base.purs +++ b/src/Specular/FRP/Base.purs @@ -27,6 +27,7 @@ module Specular.FRP.Base ( , holdDyn , foldDyn + , foldDynM , foldDynMaybe , holdUniqDynBy , uniqDynBy @@ -505,6 +506,38 @@ foldDynImpl f initial (Event event) = do , change: map (\_ -> unit) (Event event) } +-- | Like `foldDyn`, but the function returns a monadic pull action that can read values from its' environment. +foldDynM :: forall m a b. MonadFRP m => (a -> b -> Pull b) -> b -> Event a -> m (Dynamic b) +foldDynM f initial (Event event) = do + -- Reference to hold the current value of the output dynamic. + ref <- liftEffect $ newRef initial + + updateOrReadValue :: Pull b <- liftEffect $ + -- Read the event once per frame, checking for presence, and perform pull action. + let + toPull :: Pull (Effect b) + toPull = do + evt <- readBehavior event.occurence + oldValue <- pullReadRef ref + case evt of + Just occurence -> do + newValue <- f occurence oldValue + pure $ do + writeRef ref newValue + pure newValue + Nothing -> + pure $ pure oldValue + in + oncePerFramePullWithIO toPull identity + + unsub <- liftEffect $ event.subscribe $ void $ framePull $ updateOrReadValue + onCleanup unsub + + pure $ Dynamic + { value: Behavior updateOrReadValue + , change: map (\_ -> unit) (Event event) + } + -- | Construct a new root Dynamic that can be changed from `Effect`-land. newDynamic :: forall m a. MonadEffect m => a -> m { dynamic :: Dynamic a, read :: Effect a, set :: a -> Effect Unit } newDynamic initial = liftEffect do From 5955a641fb91ad6b468122631be8abf4e8f85861 Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Sat, 9 Nov 2019 23:54:49 +0100 Subject: [PATCH 2/7] Trying Effect instead of Pull. --- src/Specular/FRP/Base.purs | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/Specular/FRP/Base.purs b/src/Specular/FRP/Base.purs index 0f292a2..0d22ec2 100644 --- a/src/Specular/FRP/Base.purs +++ b/src/Specular/FRP/Base.purs @@ -27,7 +27,7 @@ module Specular.FRP.Base ( , holdDyn , foldDyn - , foldDynM + , foldDynEffect , foldDynMaybe , holdUniqDynBy , uniqDynBy @@ -506,29 +506,21 @@ foldDynImpl f initial (Event event) = do , change: map (\_ -> unit) (Event event) } --- | Like `foldDyn`, but the function returns a monadic pull action that can read values from its' environment. -foldDynM :: forall m a b. MonadFRP m => (a -> b -> Pull b) -> b -> Event a -> m (Dynamic b) -foldDynM f initial (Event event) = do +-- | Like `foldDyn`, but the function returns an Effect action that can perform side-effects that will be run when events arrive +foldDynEffect :: forall m a b. MonadFRP m => (a -> b -> Effect b) -> b -> Event a -> m (Dynamic b) +foldDynEffect f initial (Event event) = do -- Reference to hold the current value of the output dynamic. ref <- liftEffect $ newRef initial - - updateOrReadValue :: Pull b <- liftEffect $ - -- Read the event once per frame, checking for presence, and perform pull action. - let - toPull :: Pull (Effect b) - toPull = do - evt <- readBehavior event.occurence - oldValue <- pullReadRef ref - case evt of - Just occurence -> do - newValue <- f occurence oldValue - pure $ do - writeRef ref newValue - pure newValue - Nothing -> - pure $ pure oldValue - in - oncePerFramePullWithIO toPull identity + updateOrReadValue <- liftEffect $ + oncePerFramePullWithIO (readBehavior event.occurence) $ \m_newValue -> do + oldValue <- readRef ref + case m_newValue of + Just occurence -> do + newValue <- f occurence oldValue + writeRef ref newValue + pure newValue + Nothing -> + pure oldValue unsub <- liftEffect $ event.subscribe $ void $ framePull $ updateOrReadValue onCleanup unsub @@ -536,7 +528,7 @@ foldDynM f initial (Event event) = do pure $ Dynamic { value: Behavior updateOrReadValue , change: map (\_ -> unit) (Event event) - } + } -- | Construct a new root Dynamic that can be changed from `Effect`-land. newDynamic :: forall m a. MonadEffect m => a -> m { dynamic :: Dynamic a, read :: Effect a, set :: a -> Effect Unit } From 685e187d59e4d81787b9aef64566334ecde2d384 Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Mon, 11 Nov 2019 14:54:33 +0100 Subject: [PATCH 3/7] Removed foldDynEffect, introduced SimpleFRP --- src/Specular/FRP/Base.purs | 84 ++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/src/Specular/FRP/Base.purs b/src/Specular/FRP/Base.purs index 0d22ec2..848613b 100644 --- a/src/Specular/FRP/Base.purs +++ b/src/Specular/FRP/Base.purs @@ -26,8 +26,9 @@ module Specular.FRP.Base ( , newDynamic , holdDyn - , foldDyn - , foldDynEffect + , MonadFold + , SimpleFold + , foldDynM , foldDynMaybe , holdUniqDynBy , uniqDynBy @@ -474,41 +475,51 @@ instance applyDynamic :: Apply Dynamic where instance applicativeDynamic :: Applicative Dynamic where pure x = Dynamic { value: pure x, change: never } --- | `foldDyn f x e` - Make a Dynamic that will have the initial value `x`, --- | and every time `e` fires, its value will update by applying `f` to the --- | event occurence value and the old value. --- | --- | On cleanup, the Dynamic will stop updating in response to the event. -foldDyn :: forall m a b. MonadFRP m => (a -> b -> b) -> b -> Event a -> m (Dynamic b) -foldDyn = foldDynImpl - -foldDynImpl - :: forall m a b. MonadCleanup m => MonadEffect m - => (a -> b -> b) -> b -> Event a -> m (Dynamic b) -foldDynImpl f initial (Event event) = do - ref <- liftEffect $ newRef initial - updateOrReadValue <- liftEffect $ - oncePerFramePullWithIO (readBehavior event.occurence) $ \m_newValue -> do - oldValue <- readRef ref - case m_newValue of - Just occurence -> do - let newValue = f occurence oldValue - writeRef ref newValue - pure newValue - Nothing -> - pure oldValue - - unsub <- liftEffect $ event.subscribe $ void $ framePull $ updateOrReadValue - onCleanup unsub - - pure $ Dynamic - { value: Behavior updateOrReadValue - , change: map (\_ -> unit) (Event event) - } +class MonadFold m where + -- | `foldDyn f x e` - Make a Dynamic that will have the initial value `x`, + -- | and every time `e` fires, its value will update by applying `f` to the + -- | event occurence value and the old value. + -- | + -- | On cleanup, the Dynamic will stop updating in response to the event. + foldDyn :: forall m a b. MonadFRP m => (a -> b -> b) -> b -> Event a -> m (Dynamic b) + +instance monadFoldEffectCleanup :: (MonadCleanup m, MonadEffect m) => MonadFold m where + foldDyn f initial (Event event) = do + ref <- liftEffect $ newRef initial + updateOrReadValue <- liftEffect $ + oncePerFramePullWithIO (readBehavior event.occurence) $ \m_newValue -> do + oldValue <- readRef ref + case m_newValue of + Just occurence -> do + let newValue = f occurence oldValue + writeRef ref newValue + pure newValue + Nothing -> + pure oldValue + + unsub <- liftEffect $ event.subscribe $ void $ framePull $ updateOrReadValue + onCleanup unsub + + pure $ Dynamic + { value: Behavior updateOrReadValue + , change: map (\_ -> unit) (Event event) + } + +data SimpleFold a = MkSimpleFold (CleanupT Effect a) + +derive newtype instance functorSimpleFold :: Functor SimpleFold +derive newtype instance applySimpleFold :: Apply SimpleFold +derive newtype instance applicativeCleanupT :: Applicative SimpleFold +derive newtype instance bindCleanupT :: Bind SimpleFold +derive newtype instance monadCleanupT :: Monad SimpleFold +derive newtype instance monadEffectCleanupT :: MonadEffect SimpleFold + +instance simpleFoldMonadFold :: MonadFold SimpleFold where + foldDyn f initial ev = MkSimpleFold $ foldDyn f initial ev -- | Like `foldDyn`, but the function returns an Effect action that can perform side-effects that will be run when events arrive -foldDynEffect :: forall m a b. MonadFRP m => (a -> b -> Effect b) -> b -> Event a -> m (Dynamic b) -foldDynEffect f initial (Event event) = do +foldDynM :: forall m a b. MonadFRP m => (a -> b -> SimpleFold b) -> b -> Event a -> m (Dynamic b) +foldDynM f initial (Event event) = do -- Reference to hold the current value of the output dynamic. ref <- liftEffect $ newRef initial updateOrReadValue <- liftEffect $ @@ -516,7 +527,8 @@ foldDynEffect f initial (Event event) = do oldValue <- readRef ref case m_newValue of Just occurence -> do - newValue <- f occurence oldValue + SimpleFold innerMonad <- f occurence oldValue + newValue <- list innerMonad writeRef ref newValue pure newValue Nothing -> From bbbfceafd13429adb982d9ee7cd3542999eb1a7a Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Mon, 11 Nov 2019 15:22:35 +0100 Subject: [PATCH 4/7] Small fixes. --- src/Specular/FRP/Base.purs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Specular/FRP/Base.purs b/src/Specular/FRP/Base.purs index 848613b..c2a8b6d 100644 --- a/src/Specular/FRP/Base.purs +++ b/src/Specular/FRP/Base.purs @@ -26,7 +26,7 @@ module Specular.FRP.Base ( , newDynamic , holdDyn - , MonadFold + , class MonadFold , SimpleFold , foldDynM , foldDynMaybe @@ -44,7 +44,7 @@ module Specular.FRP.Base ( , class MonadFRP - , foldDynImpl + -- , foldDynImpl , foldDynMaybeImpl , traceEventIO @@ -54,8 +54,8 @@ module Specular.FRP.Base ( import Prelude import Control.Apply (lift2) -import Control.Monad.Cleanup (class MonadCleanup, onCleanup) -import Control.Monad.Reader (ask) +import Control.Monad.Cleanup (class MonadCleanup, CleanupT(..), onCleanup) +import Control.Monad.Reader (ask, lift) import Control.Monad.Rec.Class (Step(..), tailRecM) import Data.Array (cons, unsnoc) import Data.Array as Array @@ -481,7 +481,7 @@ class MonadFold m where -- | event occurence value and the old value. -- | -- | On cleanup, the Dynamic will stop updating in response to the event. - foldDyn :: forall m a b. MonadFRP m => (a -> b -> b) -> b -> Event a -> m (Dynamic b) + foldDyn :: forall a b. (a -> b -> b) -> b -> Event a -> m (Dynamic b) instance monadFoldEffectCleanup :: (MonadCleanup m, MonadEffect m) => MonadFold m where foldDyn f initial (Event event) = do @@ -505,14 +505,12 @@ instance monadFoldEffectCleanup :: (MonadCleanup m, MonadEffect m) => MonadFold , change: map (\_ -> unit) (Event event) } -data SimpleFold a = MkSimpleFold (CleanupT Effect a) +newtype SimpleFold a = MkSimpleFold (CleanupT Effect a) derive newtype instance functorSimpleFold :: Functor SimpleFold derive newtype instance applySimpleFold :: Apply SimpleFold derive newtype instance applicativeCleanupT :: Applicative SimpleFold derive newtype instance bindCleanupT :: Bind SimpleFold -derive newtype instance monadCleanupT :: Monad SimpleFold -derive newtype instance monadEffectCleanupT :: MonadEffect SimpleFold instance simpleFoldMonadFold :: MonadFold SimpleFold where foldDyn f initial ev = MkSimpleFold $ foldDyn f initial ev @@ -527,8 +525,8 @@ foldDynM f initial (Event event) = do oldValue <- readRef ref case m_newValue of Just occurence -> do - SimpleFold innerMonad <- f occurence oldValue - newValue <- list innerMonad + MkSimpleFold innerMonad <- f occurence oldValue + newValue <- lift innerMonad writeRef ref newValue pure newValue Nothing -> From bc8abd9a8cd9970d299eda840ed739b25b084ba6 Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Mon, 11 Nov 2019 18:35:21 +0100 Subject: [PATCH 5/7] Got it to compile. --- src/Specular/FRP/Base.purs | 39 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Specular/FRP/Base.purs b/src/Specular/FRP/Base.purs index c2a8b6d..c5ea181 100644 --- a/src/Specular/FRP/Base.purs +++ b/src/Specular/FRP/Base.purs @@ -27,7 +27,8 @@ module Specular.FRP.Base ( , holdDyn , class MonadFold - , SimpleFold + , foldDyn + , InnerFRP , foldDynM , foldDynMaybe , holdUniqDynBy @@ -54,7 +55,7 @@ module Specular.FRP.Base ( import Prelude import Control.Apply (lift2) -import Control.Monad.Cleanup (class MonadCleanup, CleanupT(..), onCleanup) +import Control.Monad.Cleanup (class MonadCleanup, CleanupT, onCleanup, runCleanupT) import Control.Monad.Reader (ask, lift) import Control.Monad.Rec.Class (Step(..), tailRecM) import Data.Array (cons, unsnoc) @@ -63,6 +64,7 @@ import Data.Foldable (for_) import Data.HeytingAlgebra (ff, implies, tt) import Data.Maybe (Maybe(..), isJust) import Data.Traversable (sequence, traverse) +import Data.Tuple (Tuple(..)) import Effect (Effect) import Effect.Class (class MonadEffect, liftEffect) import Effect.Uncurried (EffectFn2, mkEffectFn2, runEffectFn2) @@ -475,7 +477,7 @@ instance applyDynamic :: Apply Dynamic where instance applicativeDynamic :: Applicative Dynamic where pure x = Dynamic { value: pure x, change: never } -class MonadFold m where +class Monad m <= MonadFold m where -- | `foldDyn f x e` - Make a Dynamic that will have the initial value `x`, -- | and every time `e` fires, its value will update by applying `f` to the -- | event occurence value and the old value. @@ -483,7 +485,9 @@ class MonadFold m where -- | On cleanup, the Dynamic will stop updating in response to the event. foldDyn :: forall a b. (a -> b -> b) -> b -> Event a -> m (Dynamic b) -instance monadFoldEffectCleanup :: (MonadCleanup m, MonadEffect m) => MonadFold m where +instance innerFRPMonadFold :: MonadFold InnerFRP where + foldDyn f initial ev = MkInnerFRP $ foldDyn f initial ev +else instance monadFoldEffectCleanup :: (MonadCleanup m, MonadEffect m) => MonadFold m where foldDyn f initial (Event event) = do ref <- liftEffect $ newRef initial updateOrReadValue <- liftEffect $ @@ -505,28 +509,35 @@ instance monadFoldEffectCleanup :: (MonadCleanup m, MonadEffect m) => MonadFold , change: map (\_ -> unit) (Event event) } -newtype SimpleFold a = MkSimpleFold (CleanupT Effect a) +newtype InnerFRP a = MkInnerFRP (CleanupT Effect a) -derive newtype instance functorSimpleFold :: Functor SimpleFold -derive newtype instance applySimpleFold :: Apply SimpleFold -derive newtype instance applicativeCleanupT :: Applicative SimpleFold -derive newtype instance bindCleanupT :: Bind SimpleFold +derive newtype instance functorSimpleFold :: Functor InnerFRP +derive newtype instance applySimpleFold :: Apply InnerFRP +derive newtype instance applicativeSimpleFold :: Applicative InnerFRP +derive newtype instance bindSimpleFold :: Bind InnerFRP +derive newtype instance monadSimpleFold :: Monad InnerFRP -instance simpleFoldMonadFold :: MonadFold SimpleFold where - foldDyn f initial ev = MkSimpleFold $ foldDyn f initial ev -- | Like `foldDyn`, but the function returns an Effect action that can perform side-effects that will be run when events arrive -foldDynM :: forall m a b. MonadFRP m => (a -> b -> SimpleFold b) -> b -> Event a -> m (Dynamic b) +foldDynM :: forall m a b. MonadFRP m => (a -> b -> InnerFRP b) -> b -> Event a -> m (Dynamic b) foldDynM f initial (Event event) = do -- Reference to hold the current value of the output dynamic. + ref <- liftEffect $ newRef initial + + toCleanup <- liftEffect $ newRef [] + onCleanup $ do + cl <- readRef toCleanup + for_ cl identity + updateOrReadValue <- liftEffect $ oncePerFramePullWithIO (readBehavior event.occurence) $ \m_newValue -> do oldValue <- readRef ref case m_newValue of Just occurence -> do - MkSimpleFold innerMonad <- f occurence oldValue - newValue <- lift innerMonad + let (MkInnerFRP innerMonad) = f occurence oldValue + (Tuple newValue cleanup) <- runCleanupT innerMonad + modifyRef toCleanup (Array.cons cleanup) writeRef ref newValue pure newValue Nothing -> From 52aa4411165041bdefd26b8efaf45adf58f95399 Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Tue, 12 Nov 2019 12:33:35 +0100 Subject: [PATCH 6/7] Fixed fold methods not pulling on initialization. --- src/Specular/FRP/Base.purs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Specular/FRP/Base.purs b/src/Specular/FRP/Base.purs index c5ea181..fc4d163 100644 --- a/src/Specular/FRP/Base.purs +++ b/src/Specular/FRP/Base.purs @@ -67,6 +67,7 @@ import Data.Traversable (sequence, traverse) import Data.Tuple (Tuple(..)) import Effect (Effect) import Effect.Class (class MonadEffect, liftEffect) +import Effect.Class.Console as Console import Effect.Uncurried (EffectFn2, mkEffectFn2, runEffectFn2) import Effect.Unsafe (unsafePerformEffect) import Partial.Unsafe (unsafeCrashWith) @@ -74,6 +75,7 @@ import Specular.Internal.Effect (DelayedEffects, Ref, emptyDelayed, modifyRef, n import Specular.Internal.RIO (RIO, rio, runRIO) import Specular.Internal.RIO as RIO import Specular.Internal.UniqueMap.Mutable as UMM +import Unsafe.Coerce (unsafeCoerce) ------------------------------------------------- @@ -501,6 +503,8 @@ else instance monadFoldEffectCleanup :: (MonadCleanup m, MonadEffect m) => Monad Nothing -> pure oldValue + _ <- pull updateOrReadValue + unsub <- liftEffect $ event.subscribe $ void $ framePull $ updateOrReadValue onCleanup unsub @@ -530,8 +534,9 @@ foldDynM f initial (Event event) = do cl <- readRef toCleanup for_ cl identity - updateOrReadValue <- liftEffect $ + updateOrReadValue <- liftEffect $ do oncePerFramePullWithIO (readBehavior event.occurence) $ \m_newValue -> do + Console.log $ unsafeCoerce m_newValue oldValue <- readRef ref case m_newValue of Just occurence -> do @@ -543,6 +548,9 @@ foldDynM f initial (Event event) = do Nothing -> pure oldValue + -- Make sure we pull during the current frame + _ <- pull updateOrReadValue + unsub <- liftEffect $ event.subscribe $ void $ framePull $ updateOrReadValue onCleanup unsub From e701b80bcd705b77cf7b968a81666d93fb266c0d Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Tue, 12 Nov 2019 12:50:20 +0100 Subject: [PATCH 7/7] Added comment. --- src/Specular/FRP/Base.purs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Specular/FRP/Base.purs b/src/Specular/FRP/Base.purs index fc4d163..2f1fb4e 100644 --- a/src/Specular/FRP/Base.purs +++ b/src/Specular/FRP/Base.purs @@ -503,6 +503,8 @@ else instance monadFoldEffectCleanup :: (MonadCleanup m, MonadEffect m) => Monad Nothing -> pure oldValue + -- Since foldDyn can be called during a frame, + -- make sure to pull at least once to make sure we get the first event, if any. _ <- pull updateOrReadValue unsub <- liftEffect $ event.subscribe $ void $ framePull $ updateOrReadValue