Difference between revisions of "User:Echo Nolan/Reactive Banana: Straight to the Point"

From HaskellWiki
Jump to navigation Jump to search
Line 43: Line 43:
 
data Note =
 
data Note =
 
C | Csharp | D | Dsharp | E | F | Fsharp | G | Gsharp | A | Asharp | B
 
C | Csharp | D | Dsharp | E | F | Fsharp | G | Gsharp | A | Asharp | B
| Cagain
 
 
deriving (Show, Enum)
 
deriving (Show, Enum)
 
</pre-haskell>
 
</pre-haskell>
Line 72: Line 71:
 
let networkDescription :: forall t. Frameworks t => Moment t ()
 
let networkDescription :: forall t. Frameworks t => Moment t ()
 
networkDescription = do
 
networkDescription = do
buttonEvent <- fromAddHandler addH
+
noteEvent <- fromAddHandler addH
reactimate $ fmap (uncurry playNote) buttonEvent
+
reactimate $ fmap (uncurry playNote) noteEvent
 
network <- compile networkDescription
 
network <- compile networkDescription
 
actuate network
 
actuate network
 
return (sendNoteEvent, network)
 
return (sendNoteEvent, network)
  +
 
</pre-haskell>
 
</pre-haskell>
   
From it's type we can see that this is an IO action that returns a tuple of what is, yes, just fancy <code-haskell>uncurry playNote</code-haskell> and something called a <code-haskell>EventNetwork</code-haskell>. The <code-haskell>EventNetwork</code-haskell> is the new, interesting bit. The two new important abstractions that reactive-banana introduces are <code-haskell>Event</code-haskell>s and <code-haskell>Behavior</code-haskell>s. <code-haskell>Behavior</code-haskell>s, we'll get to a bit later. <code-haskell>Event</code-haskell>s are values that occur at discrete points in time. You can think of an <code-haskell>Event t a</code-haskell>(ignore the t for now) as a <code-haskell>[Time, a]</code-haskell> with the times monotonically increasing as you walk down the list.
+
From it's type we can see that this is an IO action that returns a tuple of what is, yes, just fancy <code-haskell>uncurry playNote</code-haskell> and something called a <code-haskell>EventNetwork</code-haskell>. The <code-haskell>EventNetwork</code-haskell> is the new, interesting bit. The two new important abstractions that reactive-banana introduces are <code-haskell>Event</code-haskell>s and <code-haskell>Behavior</code-haskell>s. <code-haskell>Behavior</code-haskell>s, we'll get to a bit later. <code-haskell>Event</code-haskell>s are values that occur at discrete points in time. You can think of an <code-haskell>Event t a</code-haskell>(ignore the t for now) as a <code-haskell>[(Time, a)]</code-haskell> with the times monotonically increasing as you walk down the list.
  +
  +
<code-haskell>go1</code-haskell> has two <code-haskell>Event</code-haskell>s in it. The first is <code-haskell>noteEvent :: Event t (Int, Note)</code-haskell> the one you send at the ghci prompt. The second is anonymous, but it's type is <code-haskell>Event t (IO ())</code-haskell>. We build that one using <code-haskell>fmap</code-haskell> and <code-haskell>uncurry playNote</code-haskell>.

Revision as of 19:13, 4 October 2012

Introduction

So I'm writing this tutorial as a means of teaching myself FRP and reactive-banana. It'll probably be full of errors and bad advice, use it at your own risk.

All the tutorials on FRP I've read start with a long boring theory section. This is an instant gratification article. For starters, imagine a man attempting to sharpen a banana into a deadly weapon. See? You're gratified already! Now for a boring bit:

Go install sox: <code-bash>apt-get install sox # Or equivalent for your OS/Distro</code-bash>

Get the git repository associated with this tutorial: <code-bash>git clone https://github.com/enolan/rbsttp.git </code-bash>

Install reactive-banana <code-bash>cabal install reactive-banana</code-bash>

Musical interlude

Cd into the git repo and open rbsttp.hs in GHCi:

<pre-bash> cd rbsttp ghci rbsttp.hs </pre-bash>

Now, we can make some beepy noises. Try these:

<pre-haskell> playNote (negate 5) C playNote (negate 5) Fsharp sequence_ . intersperse (threadDelay 1000000) $ map (playNote (negate 5)) [C ..] </pre-haskell>

Play with the value passed to threadDelay a bit for some more interesting noises. It's the time to wait between <code-haskell>Note</code-haskell>s, expresssed in microseconds.

<pre-haskell> sequence_ . intersperse (threadDelay 500000) $ map (playNote (negate 5)) [C ..] sequence_ . intersperse (threadDelay 250000) $ map (playNote (negate 5)) [C ..] sequence_ . intersperse (threadDelay 125000) $ map (playNote (negate 5)) [C ..] sequence_ . intersperse (threadDelay 62500) $ map (playNote (negate 5)) [C ..] </pre-haskell>

You've probably figured out by now that C and Fsharp are data constructors. Here's the definition for my Note type.

<pre-haskell> -- 12 note chromatic scale starting at middle C. data Note =

   C | Csharp | D | Dsharp | E | F | Fsharp | G | Gsharp | A | Asharp | B
   deriving (Show, Enum)

</pre-haskell>

<code-haskell>playNote</code-haskell> is a very hacky synthesizer. It's also asynchronous, which is why <code-haskell>mapM_ playNote (negate 5) [C ..]</code-haskell> doesn't sound too interesting. Here's <code-haskell>playNote</code-haskell>'s type.

<pre-haskell> -- Play a note with a given gain relative to max volume (this should be -- negative), asynchronously. playNote :: Int -> Note -> IO () </pre-haskell>

Ground yourself, then insert the electrodes into the banana

Everything we've done so far is plain old regular Haskell in the IO monad. Try this now:

<pre-haskell> (sendNote, network) <- go1 sendNote ((negate 10), C) sendNote ((negate 10), Fsharp) </pre-haskell>

Congratulations! You just compiled your first <code-haskell>EventNetwork</code-haskell> and sent your first <code-haskell>Event</code-haskell>s. I know this looks like I just made a excessively complicated version of <code-haskell>uncurry playNote</code-haskell>, but bear with me for a moment. Let's look at the code for <code-haskell>go1</code-haskell>:

<pre-haskell> go1 :: IO ((Int, Note) -> IO (), EventNetwork) go1 = do

   (addH, sendNoteEvent) <- newAddHandler
   let networkDescription :: forall t. Frameworks t => Moment t ()
       networkDescription = do
           noteEvent <- fromAddHandler addH
           reactimate $ fmap (uncurry playNote) noteEvent
   network <- compile networkDescription
   actuate network
   return (sendNoteEvent, network)

</pre-haskell>

From it's type we can see that this is an IO action that returns a tuple of what is, yes, just fancy <code-haskell>uncurry playNote</code-haskell> and something called a <code-haskell>EventNetwork</code-haskell>. The <code-haskell>EventNetwork</code-haskell> is the new, interesting bit. The two new important abstractions that reactive-banana introduces are <code-haskell>Event</code-haskell>s and <code-haskell>Behavior</code-haskell>s. <code-haskell>Behavior</code-haskell>s, we'll get to a bit later. <code-haskell>Event</code-haskell>s are values that occur at discrete points in time. You can think of an <code-haskell>Event t a</code-haskell>(ignore the t for now) as a <code-haskell>[(Time, a)]</code-haskell> with the times monotonically increasing as you walk down the list.

<code-haskell>go1</code-haskell> has two <code-haskell>Event</code-haskell>s in it. The first is <code-haskell>noteEvent :: Event t (Int, Note)</code-haskell> the one you send at the ghci prompt. The second is anonymous, but it's type is <code-haskell>Event t (IO ())</code-haskell>. We build that one using <code-haskell>fmap</code-haskell> and <code-haskell>uncurry playNote</code-haskell>.