Reflex FRP
repository·develop·Indexed 22 days ago
https://github.com/reflex-frp/reflexA high-performance, deterministic Functional Reactive Programming (FRP) engine and interface for Haskell. It allows developers to build complex interactive systems using pure functions, composable events, and time-varying values (Behaviors and Dynamics) instead of callbacks. The library provides primitives for transforming and combining events, managing dynamic collections, performing side-effects via PerformEvent, and handling dynamic sub-networks.
What's inside reflex
- Reflex is a fully-deterministic, higher-order Functional Reactive Programming (FRP) interface and engine. It allows you to build interactive programs without using callbacks or side-effects by using composable events and time-varying values to describe interactive systems as pure functions.
Flatten Nested FRP Types
developFlattening functions remove outer wrappers like
Event (Event a)orDynamic (Dynamic a)to create a single, simpler stream.Event Flattening
switch: FlattensBehavior (Event a)toEvent a. Uses the old event during switchover.switchDyn: FlattensDynamic (Event a)toEvent a. Uses the new event immediately.coincidence: FlattensEvent (Event a)to an event that fires only when both the outer and inner events fire.
Dynamic Flattening
join: FlattensDynamic (Dynamic a)toDynamic a. The output updates whenever the inner OR outer dynamic updates.joinDynThroughMap: FlattensDynamic (Map k (Dynamic a))toDynamic (Map k a).
Behavior Flattening
join: FlattensBehavior (Behavior a)toBehavior a(standard monadic join).
Understand Reflex Monadic Contexts and Annotations
developReflex functions often operate in a monadic context
m a. The specific monadmis determined by your FRP host (e.g.,reflex-dom). To help developers understand what capabilities a function requires, the documentation uses specific annotations:[ ](Pure): Operates on Events, Behaviors, or Dynamics without regard to "current time".[S](MonadSample): Requires a monad supportingMonadSample. Used for functions that produce a result "as of now".[H](MonadHold): Requires a monad supportingMonadHold. SinceMonadHolddepends onMonadSample, any[S]function also runs in an[H]context.[B](PostBuild): RequiresPostBuildto fire an event when the network is set up.[A](Adjustable): RequiresAdjustableto add or remove pieces of the network via Events.[T](TriggerEvent): RequiresTriggerEventto create new externally-fired Events.[P](PerformEvent): RequiresPerformEventto trigger IO actions via Events.
How to hack on the Reflex source code
developIf you are working within a Reflex Platform checkout and want to modify the Reflex source code, follow these steps:
- Checkout the reflex source code into your local overlay directory:
./scripts/hack-on haskell-overlays/reflex-packages/dep/reflex - Point that checkout at your fork to make changes.
- Use the
./try-reflexor./scripts/work-onscripts to start a shell environment where you can test your changes.
./scripts/hack-on haskell-overlays/reflex-packages/dep/reflex- Checkout the reflex source code into your local overlay directory:
Explore Reflex ecosystems and resources
developReflex is part of a larger ecosystem for building interactive applications:
- Reflex-DOM: A framework built on Reflex for developing web pages and highly-interactive single-page apps.
- Obelisk: A 'batteries included' framework built on Reflex and Reflex-DOM for functional reactive web and mobile applications.
- Reflex Platform: The recommended way to get started with Reflex development.
For tutorials, documentation, and examples, visit the official website.
Initialize the FRP Network with PostBuild
developTo perform actions immediately after the FRP network has been successfully constructed and started, use the
getPostBuildfunction. This returns a one-shotEventthat fires once.Requires the
PostBuildtypeclass[B].-- [B] getPostBuild :: m (Event ()) -- Usage pattern: -- e <- getPostBuild -- performEvent_ (print "Network is up!" <$ e)Manage dynamic collections with list functions
developReflex provides several functions to transform a
Dynamiccollection (like aMapor aList) into a collection of dynamically-changing widgets or values. These functions handle the lifecycle of the elements as the underlying collection changes.listWithKey: Turns aDynamic (Map k v)into aDynamic (Map k a)by applying a constructor function to each key and its correspondingDynamic v.list: Similar tolistWithKey, but the constructor function does not receive the key.simpleList: A simplified version forDynamic [v], where elements are processed without keys.listViewWithKey: Specialized for widgets that return anEvent a. It returns anEvent (Map k a).selectViewListWithKey_: Creates a set of widgets where one is selected based on aDynamic k. It returns anEvent krepresenting the current selection.listWithKeyShallowDiff: Uses an initialMapand anEvent (Map k (Maybe v))to perform shallow updates rather than full re-renders.
-- Turn a Dynamic key/value map into a set of dynamically-changing widgets. listWithKey :: Ord k => Dynamic (Map k v) -> (k -> Dynamic v -> m a) -> m (Dynamic (Map k a)) -- Even simpler version where there are no keys and we just use a list. simpleList :: Dynamic [v] -> (Dynamic v -> m a) -> m (Dynamic [a])Perform side-effects (I/O) in an FRP network
developTo connect your FRP network to the real world, use the
performEventfamily of functions. These allow you to execute side-effecting actions in response to events.performEvent: Runs side-effecting actions in anEvent. The returnedEventcontains the results of those actions. Requires aPerformEvent t mconstraint.performEvent_: Runs side-effects but does not return anEventcontaining results.performEventAsync: Used for asynchronous actions. You provide a callback to the action, which the action uses to send its return value back into the network.
-- Run side-effecting actions in Event when it occurs; returned Event contains results. performEvent :: Event (Performable m a) -> m (Event a) -- Just run side-effects; no return Event performEvent_ :: Event (Performable m ()) -> m () -- Actions run asynchronously; actions are given a callback to send return values performEventAsync :: Event ((a -> IO ()) -> Performable m ()) -> m (Event a)Create and Transform Behaviors
developBehaviors represent continuous values over time. They always have a current value.
Creation
constant: A behavior that never changes.current: Extract theBehaviorpart of aDynamic.hold: Create aBehaviorthat updates its value whenever a specificEventfires. RequiresMonadHold[H].
Transformation
fmap,ffor,<*>: Standard applicative/functor transformations.sample: Retrieve the current value of aBehaviorin a monadic context. RequiresMonadSample[S].pull: Create a behavior from a monadic action. RequiresMonadSample[S].
-- [H] hold :: a -> Event a -> m (Behavior a) -- [S] sample :: Behavior a -> m aWork with time and delays
developReflex provides primitives for handling time-based events:
tickLossy: Creates anEventthat fires at a given interval based on a startingUTCTime. It is 'lossy', meaning if the system cannot keep up with the interval, some ticks may be skipped.delay: Delays the occurrences of an existingEventby a specifiedNominalDiffTime(seconds).
-- Create Event at given interval with given basis time. tickLossy :: NominalDiffTime -> UTCTime -> m (Event t TickInfo) -- Delay an Event's occurrences by a given amount in seconds. delay :: NominalDiffTime -> Event t a -> m (Event t a)Manage dynamic networks and sub-networks
developWhen building complex applications, you may need to create, destroy, or switch between entire sub-networks dynamically.
runWithReplace: Takes an initial value and anEventof replacement actions. It returns the current value and anEventof the new values.networkView: Given aDynamicof network-creating actions, it creates a network that is recreated whenever theDynamicupdates. This is useful for switching between different UI views or logic sets.networkHold: Given an initial network and anEventof new network-creating actions, it returns aDynamicrepresenting the current network. The network is recreated whenever theEventfires.untilReady: Renders a placeholder network (m a) to be shown while a primary network (m b) is being built. It returns the placeholder result and anEventindicating when the primary network is ready.
-- Given a Dynamic of network-creating actions, create a network that is recreated whenever the Dynamic updates. networkView :: Dynamic (m a) -> m (Event a) -- Given an initial network and an Event of network-creating actions, create a network that is recreated whenever the Event fires. networkHold :: m a -> Event (m a) -> m (Dynamic a) -- Render a placeholder network to be shown while another network is not yet done building untilReady :: m a -> m b -> m (a, Event b)Transform and Combine Events
developEvents represent discrete occurrences in time. You can transform them using standard functional patterns or combine them with other Events and Behaviors.
Transformation
fmap,ffor,<$: Standard mapping functions.fmapMaybe,fforMaybe: Map and filter outNothingvalues.ffilter: Filter events based on a predicate.updated: Extract anEventfrom aDynamic(fires whenever the Dynamic changes).
Sampling Behaviors and Dynamics
Use these to capture the state of a
BehaviororDynamicat the moment anEventfires:gate: Only allows anEventto pass if aBehavior BoolisTrue.tag: Attach the current value of aBehaviorto anEvent.attach: Combine anEventwith the current value of aBehaviorinto a tuple(a, b).attachPromptlyDyn: Similar toattach, but uses aDynamicand uses the most recent value immediately.
Combining Multiple Events
leftmost: Returns the first event from a list that fires.mergeWith: Combines multiple events of the same type using a combining function.align: Combines two events into aThese a bstructure (handling simultaneous firings).difference: Returns an event that fires when the first event fires, but only if the second does not.
-- Example: Tagging an event with a behavior value -- [ ] tag :: Behavior a -> Event b -> Event a -- Example: Filtering an event -- [ ] ffilter :: (a -> Bool) -> Event a -> Event a