website | twitter

Thursday, December 15, 2011

Various examples in Haskell's FRP.Reactive

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
view raw hello.hs hosted with ❤ by GitHub

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..]
view raw helloMany.hs hosted with ❤ by GitHub

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..])
view raw fibs.hs hosted with ❤ by GitHub

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
view raw type.hs hosted with ❤ by GitHub

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
view raw typeClock.hs hosted with ❤ by GitHub

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
view raw mealy.hs hosted with ❤ by GitHub

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.

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete

 
Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 Unported License.