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 +7/−0
- reflex.cabal +3/−2
- src/Reflex/Class.hs +7/−1
- src/Reflex/Dynamic.hs +14/−0
- src/Reflex/Host/Headless.hs +126/−0
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