After playing with Flapjax library in Javascript, I moved to Reactive to learn more about FRP. Because research on Functional Reactive Programming is most active in Haskell, I thought it would be better to do that. Reactive seems to be a nice library, but unfortunately I couldn't find many working code examples. So I show some of them as my exercise. To write this, I owe a maoe's great article in Japanese.
(This page has been translated into Spanish language by Maria Ramos from Webhostinghub.com.)As I didn't have much time, I couldn't write a good explanation now. But still I hope it helps some people who learn Reactive like me. I used Haskell Platform 2010 (slightly old) and did cabal install reactive --enable-documentation to install Reactive.
-- Print Hello World after 3 seconds. | |
import FRP.Reactive ( atTime ) | |
import FRP.Reactive.LegacyAdapters ( adaptE ) | |
import Control.Applicative ( (<$>) ) | |
main :: IO () | |
main = adaptE $ (\_ -> putStrLn "Hello, World!") <$> atTime 3 |
The first example shows "Hello, World!" after three seconds. atTime generates a timer event, and <$> convert this event to IO action (\_ -> putStrLn "Hello, World!") which writes a string.
-- Print Hello World each second. | |
import FRP.Reactive ( atTimes ) | |
import FRP.Reactive.LegacyAdapters ( adaptE ) | |
import Control.Applicative ( (<$>) ) | |
main :: IO () | |
main = adaptE $ (\_ -> putStrLn "Hello, World!") <$> atTimes [1..] |
This is as same as above, but it makes events each second.
-- Running Fibonacci numbers | |
import FRP.Reactive ( Event, atTimes, scanlE ) | |
import FRP.Reactive.LegacyAdapters ( adaptE ) | |
import Control.Applicative ( (<$>) ) | |
fibs :: Event a -> Event (Integer, Integer) | |
fibs e = scanlE (\(n0, n1) _ -> (n1, n0 + n1)) (0, 1) e | |
main :: IO () | |
main = adaptE $ print <$> (fibs $ atTimes [1..]) |
This makes running Fibonnaci numbers. You can use scanlE to process previous value and current value of the event in a function. In this case, (0, 1) is the initial value, and when an event occurs, the function \(n0, n1) _ -> (n1, n0 + n1) calculates next value, and the result (the first case is (1, 1)) is used as a next argument when a new event occurs.
-- Show as you type | |
import FRP.Reactive ( Event ) | |
import FRP.Reactive.LegacyAdapters ( makeClock, makeEvent, adaptE ) | |
import Control.Applicative ( (<$>) ) | |
import Control.Concurrent ( forkIO ) | |
import Control.Monad ( forever ) | |
import System.IO ( stdout, stdin, hSetBuffering , BufferMode(NoBuffering) ) | |
main :: IO () | |
main = run machine | |
-- Accept a character event and print it. | |
machine :: Event Char -> Event (IO ()) | |
machine event = showChar <$> event | |
where showChar c = putStrLn (" You type " ++ show c) | |
-- Event driver to generate Char event, and run IO event. | |
run :: (Event Char -> Event (IO ())) -> IO () | |
run machine = do | |
hSetBuffering stdin NoBuffering | |
hSetBuffering stdout NoBuffering | |
(sink, event) <- makeEvent =<< makeClock | |
forkIO $ forever $ getChar >>= sink | |
adaptE $ machine event |
It shows characters as you type. It looks difficult but you don't have to worry about run function. The important part is machine :: Event Char -> Event (IO ()) that convert a character input event to an IO action.
-- Show as you type, or bang each two seconds. | |
import FRP.Reactive ( Event, atTimes ) | |
import FRP.Reactive.LegacyAdapters ( makeClock, makeEvent, adaptE ) | |
import Control.Applicative ( (<$>) ) | |
import Control.Concurrent ( forkIO ) | |
import Control.Monad ( forever ) | |
import Data.Monoid ( mappend ) | |
import System.IO ( stdout, stdin, hSetBuffering, hSetEcho , BufferMode(NoBuffering) ) | |
main :: IO () | |
main = run machine | |
-- merge two event handlers. | |
machine :: Event Char -> Event (IO ()) | |
machine event = onType event `mappend` onClock | |
-- Show as you type. | |
onType :: Event Char -> Event (IO ()) | |
onType event = (\c -> putStrLn (" You type " ++ show c)) <$> event | |
-- Show "bang" each two seconds. | |
onClock :: Event (IO ()) | |
onClock = (\_ -> putStrLn "bang") <$> atTimes [0, 2..] | |
-- Event driver to generate Char event, and run IO event. | |
run :: (Event Char -> Event (IO ())) -> IO () | |
run machine = do | |
hSetBuffering stdin NoBuffering | |
hSetBuffering stdout NoBuffering | |
(sink, event) <- makeEvent =<< makeClock | |
forkIO $ forever $ getChar >>= sink | |
adaptE $ machine event |
This example shows how to merge two events. onType is same as machine in the previous example, and onClock is same as helloMany.hs example. I used `mappend` to merge the two events
-- Toggle the state when you type something | |
import FRP.Reactive ( Event, mealy_, zipE, atTimes ) | |
import FRP.Reactive.LegacyAdapters ( makeClock, makeEvent, adaptE ) | |
import Control.Applicative ( (<$>) ) | |
import System.IO ( stdout, stdin, hSetBuffering, hSetEcho, | |
BufferMode(NoBuffering) ) | |
import Control.Concurrent ( forkIO ) | |
import Control.Monad ( forever ) | |
-- Transition definition | |
next :: String -> String | |
next "HOP" = "STEP" | |
next "STEP" = "JUMP" | |
next "JUMP" = "HOP" | |
-- An event makes state update | |
state :: Event a -> Event String | |
state event = mealy_ "HOP" next event | |
-- Zip state event and clock event, and show current state. | |
-- Because events are zipped, the state is shown when you type. | |
machine :: Event Char -> Event (IO ()) | |
machine event = action <$> zipE ("HOP", ()) (state event, atTimes [0..]) | |
where action (s, _) = print s | |
-- Event driver to generate Char event, and run IO event. | |
run :: (Event Char -> Event (IO ())) -> IO () | |
run machine = do | |
hSetBuffering stdin NoBuffering | |
hSetBuffering stdout NoBuffering | |
hSetEcho stdin False | |
(sink, event) <- makeEvent =<< makeClock | |
forkIO $ forever $ getChar >>= sink | |
adaptE $ machine event | |
main :: IO () | |
main = run machine |
This shows a simple state machine. The function next defines the state machine, and mealy_ convert the definition to an event. zipE is another way to merge two events. Unlike mappend, you can see two values in the two events in a same time.