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! Here, I'll write some code for playing musical notes on your computer, attach that to reactive-banana and build increasingly complicated and amusing "sequencers" using it. Now for a boring bit:

Ground yourself, then insert the electrodes into the banana

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>:

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.

In general, to get <code-haskell>Event</code-haskell>s from IO we'll need to use <code-haskell>fromAddHandler</code-haskell>. Unsurprisingly, it wants an <code-haskell>addHandler</code-haskell> as its argument. Let's take a look at those types:

Reactive-banana makes a pretty strong assumption that you're hooking it up to some callback-based, "event driven programming" library. An <code-haskell>AddHandler a</code> takes a function that takes an <code-haskell>a</code-haskell> and does some IO and "registers the callback" and returns a "cleanup" action. Reactive-banana will hook that callback into FRP, and call the cleanup action whenever we <code-haskell>pause</code-haskell> our <code-haskell>EventNetwork</code-haskell>. (You can <code-haskell>pause</code-haskell> and <code-haskell>actuate</code-haskell> an <code-haskell>EventNetwork</code-haskell> as many times as you like.)

Since we don't have anything that looks like an <code-haskell>AddHandler</code-haskell>, we need a convenience function to make one for us. Ta-da:

That gives us an <code-haskell>AddHandler</code-haskell> and the function that triggers the <code-haskell>Event</code-haskell>, which we bound to the name <code-haskell>sendNote</code-haskell> way back when we ran go1.

<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>. In general, we'll be manipulating <code-haskell>Event</code-haskell>s and <code-haskell>Behavior</code-haskell>s using <code-haskell>fmap</code-haskell>, <code-haskell>Applicative</code-haskell> and some reactive-banana specific combinators.

Put the weird type constraint on <code-haskell>networkDescription</code-haskell> out of your mind for now. The <code-haskell>Moment</code-haskell> monad is what we use to build network descriptions. I don't understand exactly what's going on with <code-haskell> forall Frameworks t. => Moment t ()</code-haskell>, but it makes GHC happy and probably stops me from writing incorrect code somehow.

<code-haskell>compile</code-haskell> turns a network description into an <code-haskell>EventNetwork</code-haskell>, and <code-haskell>actuate</code-haskell> is fancy-FRP-talk for "turn on".

Plug a metronome into the banana

Since GHC has such great concurrency support, and we were already using <code-haskell>threadDelay</code-haskell> back in section 2, we're going to use a couple of threads and a <code-haskell>Chan ()</code-haskell> to build and attach our metronome. Here's a function that lets us build <code-haskell>AddHandler a</code-haskell>s out of IO functions that take <code-haskell>Chan a</code-haskell> as an argument.

So, basically, we make a new <code-haskell>Chan</code-haskell>, <code-haskell>forkIO</code-haskell> the given function, passing the new <code-haskell>Chan</code-haskell> to it as an argument, create a second thread that triggers the callback handler whenever a new item appears on the <code-haskell>Chan</code-haskell> and returns a cleanup action that kills both threads. Some version of <code-haskell>addHandlerFromThread</code-haskell> may or may not become part of reactive-banana in the future, filing a ticket is on my to-do list.

Try it out:
<pre-haskell>
goBpm 240
-- Wait until you get tired of that noise
pause it
</pre-haskell>

If you've gotten confused here, <code-haskell>it</code-haskell> is a special variable only available in GHCi, holding the return value of the last expression, and <code-haskell>pause</code-haskell> stops the operation of an <code-haskell>EventNetwork</code-haskell>.

Warming things up: Banana, meet Microwave

Let's play some chords instead of just single notes. First, the easy part:

Now how do we hook that up to FRP? We already know fmap, so we can get something of type <code-haskell>Event t Note -> Event t [Note]</code-haskell>, but how do we get a list of <code-haskell>Note</code-haskell>s to play at the same time? Meet a new combinator:

This banana is getting crowded! Plugging in a clock

Let's take our metronome and turn it into a beat counting metronome. Then we can play some scales and other patterns - like when we played around with <code-haskell>threadDelay</code-haskell>, <code-haskell>intersperse</code-haskell> and <code-haskell>sequence_</code-haskell> back in section 2. Meet <code-haskell>accumE</code-haskell>: