packages feed

reflex 0.7.0.0 → 0.7.1.0

raw patch · 5 files changed

+157/−3 lines, 5 filesdep ~hlintPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: hlint

API changes (from Hackage documentation)

+ Reflex.Class: instance forall k (t :: k). Reflex.Class.Reflex t => Data.Semialign.Internal.Unzip (Reflex.Class.Event t)
+ Reflex.Dynamic: distributeIntMapOverDynPure :: Reflex t => IntMap (Dynamic t v) -> Dynamic t (IntMap v)
+ Reflex.Dynamic: joinDynThroughIntMap :: forall t a. Reflex t => Dynamic t (IntMap (Dynamic t a)) -> Dynamic t (IntMap a)
+ Reflex.Host.Headless: runHeadlessApp :: (forall t m. MonadHeadlessApp t m => m (Event t ())) -> IO ()
+ Reflex.Host.Headless: type MonadHeadlessApp t m = (Adjustable t m, MonadFix m, MonadHold t m, MonadIO (HostFrame t), MonadIO (Performable m), MonadIO m, MonadRef (HostFrame t), NotReady t m, PerformEvent t m, PostBuild t m, PrimMonad (HostFrame t), Ref (HostFrame t) ~ IORef, Ref m ~ IORef, Reflex t, ReflexHost t, TriggerEvent t m)

Files

ChangeLog.md view
@@ -1,5 +1,12 @@ # Revision history for reflex +## 0.7.1.0++* ([#413](https://github.com/reflex-frp/reflex/pull/413), [#417](https://github.com/reflex-frp/reflex/pull/417)) Add `Reflex.Host.Headless` module which provides `runHeadlessApp` as an easy way to run a Reflex network in a "headless" environment.+* ([#420](https://github.com/reflex-frp/reflex/pull/420)) Add a [`Data.Zip.Unzip`](https://hackage.haskell.org/package/semialign-1.1/docs/Data-Zip.html#t:Unzip) instance for `Event`.+* ([#419](https://github.com/reflex-frp/reflex/pull/419)) Add `distributeIntMapOverDynPure` and `joinDynThroughIntMap` as convenience functions for working with `Dynamic` `IntMap`s.++ ## 0.7.0.0  * Add lifting instances for most classes to `Reflex.Profiled.Profiled`. ([#398](https://github.com/reflex-frp/reflex/pull/398))
reflex.cabal view
@@ -1,5 +1,5 @@ Name: reflex-Version: 0.7.0.0+Version: 0.7.1.0 Synopsis: Higher-order Functional Reactive Programming Description: Reflex is a high-performance, deterministic, higher-order Functional Reactive Programming system License: BSD3@@ -119,6 +119,7 @@     Reflex.FastWeak,     Reflex.FunctorMaybe,     Reflex.Host.Class,+    Reflex.Host.Headless,     Reflex.Network,     Reflex.NotReady.Class,     Reflex.PerformEvent.Base,@@ -243,7 +244,7 @@                , directory                , filepath                , filemanip-               , hlint < 2.1 || >= 2.2.2+               , hlint (< 2.1 || >= 2.2.2) && < 3   if impl(ghcjs)     buildable: False 
src/Reflex/Class.hs view
@@ -180,7 +180,7 @@ import Data.These.Combinators (justThese) #endif #if MIN_VERSION_semialign(1,1,0)-import Data.Zip (Zip (..))+import Data.Zip (Zip (..), Unzip (..)) #endif #endif @@ -1091,6 +1091,12 @@   zip x y = mapMaybe justThese $ align x y #endif +#ifdef MIN_VERSION_semialign+#if MIN_VERSION_semialign(1,1,0)+instance Reflex t => Unzip (Event t) where+  unzip = splitE+#endif+#endif  -- | Create a new 'Event' that only occurs if the supplied 'Event' occurs and -- the 'Behavior' is true at the time of occurrence.
src/Reflex/Dynamic.hs view
@@ -53,10 +53,12 @@   , foldDynMaybe   , foldDynMaybeM   , joinDynThroughMap+  , joinDynThroughIntMap   , traceDyn   , traceDynWith   , splitDynPure   , distributeMapOverDynPure+  , distributeIntMapOverDynPure   , distributeDMapOverDynPure   , distributeListOverDynPure   , Demux@@ -88,6 +90,8 @@ import qualified Data.Dependent.Map as DMap import Data.Dependent.Sum (DSum (..)) import Data.GADT.Compare (GCompare (..), GEq (..), GOrdering (..))+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap import Data.Map (Map) import Data.Maybe import Data.Monoid ((<>))@@ -202,6 +206,11 @@ distributeMapOverDynPure :: (Reflex t, Ord k) => Map k (Dynamic t v) -> Dynamic t (Map k v) distributeMapOverDynPure = fmap dmapToMap . distributeDMapOverDynPure . mapWithFunctorToDMap +-- | Convert an 'IntMap' with 'Dynamic' elements into a 'Dynamic' of an 'IntMap' with+-- non-'Dynamic' elements.+distributeIntMapOverDynPure :: (Reflex t) => IntMap (Dynamic t v) -> Dynamic t (IntMap v)+distributeIntMapOverDynPure = fmap dmapToIntMap . distributeDMapOverDynPure . intMapWithFunctorToDMap+ -- | Convert a list with 'Dynamic' elements into a 'Dynamic' of a list with -- non-'Dynamic' elements, preserving the order of the elements. {-# DEPRECATED distributeListOverDynPure "Use 'distributeListOverDyn' instead" #-}@@ -213,6 +222,11 @@ -- with the current values of the 'Dynamic's in a map. joinDynThroughMap :: forall t k a. (Reflex t, Ord k) => Dynamic t (Map k (Dynamic t a)) -> Dynamic t (Map k a) joinDynThroughMap = (distributeMapOverDynPure =<<)++-- | Combine a 'Dynamic' of an 'IntMap' of 'Dynamic's into a 'Dynamic'+-- with the current values of the 'Dynamic's in a map.+joinDynThroughIntMap :: forall t a. (Reflex t) => Dynamic t (IntMap (Dynamic t a)) -> Dynamic t (IntMap a)+joinDynThroughIntMap = (distributeIntMapOverDynPure =<<)  -- | Print the value of the 'Dynamic' when it is first read and on each -- subsequent change that is observed (as 'traceEvent'), prefixed with the
+ src/Reflex/Host/Headless.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ConstraintKinds #-}++module Reflex.Host.Headless where++import Control.Concurrent.Chan (newChan, readChan)+import Control.Monad.Fix (MonadFix, fix)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Primitive (PrimMonad)+import Control.Monad.Ref (MonadRef, Ref, readRef)+import Data.Dependent.Sum (DSum (..), (==>))+import Data.Foldable (for_)+import Data.Functor.Identity (Identity(..))+import Data.IORef (IORef, readIORef)+import Data.Maybe (catMaybes)+import Data.Traversable (for)++import Reflex+import Reflex.Host.Class++type MonadHeadlessApp t m =+  ( Adjustable t m+  , MonadFix m+  , MonadHold t m+  , MonadIO (HostFrame t)+  , MonadIO (Performable m)+  , MonadIO m+  , MonadRef (HostFrame t)+  , NotReady t m+  , PerformEvent t m+  , PostBuild t m+  , PrimMonad (HostFrame t)+  , Ref (HostFrame t) ~ IORef+  , Ref m ~ IORef+  , Reflex t+  , ReflexHost t+  , TriggerEvent t m+  )++-- | Run a headless FRP network. Inside the action, you will most probably use+-- the capabilities provided by the 'TriggerEvent' and 'PerformEvent' type+-- classes to interface the FRP network with the outside world. Useful for+-- testing. Each headless network runs on its own spider timeline.+runHeadlessApp+  :: (forall t m. MonadHeadlessApp t m => m (Event t ()))+  -- ^ The action to be run in the headless FRP network. The FRP network is+  -- closed at the first occurrence of the resulting 'Event'.+  -> IO ()+runHeadlessApp guest =+  -- We are using the 'Spider' implementation of reflex. Running the host+  -- allows us to take actions on the FRP timeline.+  withSpiderTimeline $ runSpiderHostForTimeline $ do+    -- Create the "post-build" event and associated trigger. This event fires+    -- once, when the application starts.+    (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef+    -- Create a queue to which we will write 'Event's that need to be+    -- processed.+    events <- liftIO newChan+    -- Run the "guest" application, providing the appropriate context. We'll+    -- pure the result of the action, and a 'FireCommand' that will be used to+    -- trigger events.+    (result, fc@(FireCommand fire)) <- do+      hostPerformEventT $                 -- Allows the guest app to run+                                          -- 'performEvent', so that actions+                                          -- (e.g., IO actions) can be run when+                                          -- 'Event's fire.++        flip runPostBuildT postBuild $    -- Allows the guest app to access to+                                          -- a "post-build" 'Event'++          flip runTriggerEventT events $  -- Allows the guest app to create new+                                          -- events and triggers and write+                                          -- those triggers to a channel from+                                          -- which they will be read and+                                          -- processed.+            guest++    -- Read the trigger reference for the post-build event. This will be+    -- 'Nothing' if the guest application hasn't subscribed to this event.+    mPostBuildTrigger <- readRef postBuildTriggerRef++    -- When there is a subscriber to the post-build event, fire the event.+    for_ mPostBuildTrigger $ \postBuildTrigger ->+      fire [postBuildTrigger :=> Identity ()] $ pure ()++    -- Subscribe to an 'Event' of that the guest application can use to+    -- request application shutdown. We'll check whether this 'Event' is firing+    -- to determine whether to terminate.+    shutdown <- subscribeEvent result++    -- The main application loop. We wait for new events and fire those that+    -- have subscribers. If we detect a shutdown request, the application+    -- terminates.+    fix $ \loop -> do+      -- Read the next event (blocking).+      ers <- liftIO $ readChan events+      stop <- do+        -- Fire events that have subscribers.+        fireEventTriggerRefs fc ers $+          -- Check if the shutdown 'Event' is firing.+          readEvent shutdown >>= \case+            Nothing -> pure False+            Just _ -> pure True+      if or stop+        then pure ()+        else loop+  where+    -- Use the given 'FireCommand' to fire events that have subscribers+    -- and call the callback for the 'TriggerInvocation' of each.+    fireEventTriggerRefs+      :: MonadIO m+      => FireCommand t m+      -> [DSum (EventTriggerRef t) TriggerInvocation]+      -> ReadPhase m a+      -> m [a]+    fireEventTriggerRefs (FireCommand fire) ers rcb = do+      mes <- liftIO $+        for ers $ \(EventTriggerRef er :=> TriggerInvocation a _) -> do+          me <- readIORef er+          pure $ fmap (==> a) me+      a <- fire (catMaybes mes) rcb+      liftIO $ for_ ers $ \(_ :=> TriggerInvocation _ cb) -> cb+      pure a