{-# LANGUAGE MultiParamTypeClasses #-}1{-# LANGUAGE RankNTypes #-}23{-|45This program gives an example of how to construct a simple framework that6allows programmers to write FRP applications that process keystrokes (of7type Char) to produce a view (of type String). This model, where programmers8process Event(s) to produce Behavior(s) is akin to the OpenGL/GLUT model, where9input is pushed into the application via callbacks and and output is pulled10via periodic sampling in a render function. Other application models work11differently; for example, DOM applications built with reflex-dom ultimately12produce Events rather than Behaviors as their output, since changes need to be13pushed (rather than pulled) into the web browser's DOM hierarchy. The precise14mix of input and output structures required will depend on the needs of the15external systems with which the FRP-enabled program is interacting.1617-}1819module Main where2021import Reflex22import Reflex.Host.Class (newEventWithTriggerRef, runHostFrame, fireEvents)23import Control.Concurrent (forkIO)24import Control.Monad (forever)25import Control.Monad.Fix (MonadFix)26import Control.Monad.Identity (Identity(..))27import Control.Monad.IO.Class (liftIO)28import Data.IORef (readIORef)29import Data.Dependent.Sum (DSum ((:=>)))30import System.IO (hSetEcho, hSetBuffering, stdin, BufferMode (NoBuffering))3132-- | Define the type for apps using our host framework. Programmers33-- will write programs of type @TypingApp t m@ and use our34-- framework to run them.35--36-- In this framework, the user will write programs that take an input37-- event representing keystrokes and produce an output behavior representing38-- the current view to be shown. This is similar to how polling-driven39-- output frameworks such as OpenGL will work.40type TypingApp t m = (Reflex t, MonadHold t m, MonadFix m)41=> Event t Char42-> m (Behavior t String)4344-- | Run a program written in the framework. This will do all the necessary45-- work to integrate the Reflex-based guest program with the outside world46-- via IO.47host :: (forall t m. TypingApp t m)48-- ^ By keeping t and m abstract, we ensure that the user (the49-- programmer using our framework) can't make any assumptions50-- about which Reflex implementation is being used51-> IO ()52host myGuest =5354-- Use the Spider implementation of Reflex.55runSpiderHost $ do5657-- Create an event to be used as input.58-- It will fire wehenver we use eTriggerRef.59(e, eTriggerRef) <- newEventWithTriggerRef6061-- Evaluate our user's program to set up the data flow graph.62-- This usually only needs to be done once; the user can change the data63-- flow graph arbitrarily in response to events.64--65-- runHostFrame is an efficient way of running a computation that66-- can build arbitrary data flow graphs using 'hold' and 'sample'.67--68-- (The pure combinators in the Reflex class can be used in any context,69-- so they don't need any special treatment - but inside runHostFrame is70-- as good a place as any to run them.)71b <- runHostFrame $ myGuest e7273-- Begin our event processing loop.74forever $ do7576-- Get an input event and display it.77input <- liftIO getChar78liftIO $ putStrLn $ "Input Event: " ++ show input7980-- Retrieve the current event trigger.81mETrigger <- liftIO $ readIORef eTriggerRef8283-- Use the trigger to deliver the event.84case mETrigger of85Nothing ->86-- This means that nobody is subscribed to the input event.87--88-- Since this is the only input event in this system, that would89-- mean the guest program must be really boring! However, in larger90-- programs, there are often many input events, and most programs91-- will not care about every single one of them.92--93-- Note: The missing trigger does NOT mean we should buffer the94-- input and deliver it later - it means that nobody is interested95-- in this occurrence, so we should discard it.96return ()97Just eTrigger ->98-- We have a trigger, so someone is interested in this input event99-- occurrence.100--101-- fireEvents will process an event frame to deliver the event to102-- anyone in the data flow graph who is interested in it. It can103-- also deliver multiple simultaneous events if necessary. However,104-- the same event cannot be firing multiple times simultaneously;105-- system behavior is undefined if the same trigger is provided more106-- than once.107fireEvents [eTrigger :=> Identity input]108109-- Retrieve the current output of the user's program and display it.110output <- runHostFrame $ sample b111liftIO $ putStrLn $ "Output Behavior: " ++ show output112113-- | This is a simple guest program written with our framework. It just114-- accumulates all the characters that the user has typed.115-- Backspace functionality is left as an exercise for the reader.116guest :: TypingApp t m117guest e = do118119-- Accumulate the input events in a list.120-- Each one represents a keypress from the end user.121d <- foldDyn (:) [] e122123-- Since we're using cons to accumulate keystrokes, they will end up in124-- reverse order. Use `reverse` to fix that.125return $ fmap reverse $ current d126127-- | Main is just doing some setup so that the program's output will look nice,128-- and then invoking `host`.129main :: IO ()130main = do131putStrLn "Welcome to the example Reflex host app; press Ctrl+C to exit"132putStrLn "Press any key to process it with the Reflex FRP engine"133134-- Prevent the user's input from showing up until we want it to.135hSetEcho stdin False136137-- Ensure that we process each character right away, instead of waiting138-- until the user presses enter.139hSetBuffering stdin NoBuffering140141-- Run the guest program using our host framework.142host guest143144145