Testing in reactive-banana
frp, haskell, reactive-banana, reactive-programming
Solution
When you say "unit test," I'm imagining something like `QuickCheck`, where you inject a number of inputs into the network and examine the outputs. To do such a thing, we'd need a function along the lines of:
evalNetwork :: Network a b -> [a] -> IO [b]
Near the end of this answer I demonstrate a variant on one of the `interpret*` functions that has a similar type, for a specific type of "network."
Mumbo-jumbo about `reactive-banana` network types
Such a function is incompatible with the actual type of "entire networks" used in `reactive-banana`. Contrast the type of the actual function involving networks:
compile :: (forall t. Frameworks t => Moment t ()) -> IO EventNetwork
So the type of any network is `forall t. Frameworks t => Moment t ()`. There are no type variables; no inputs or outputs. Similarly, the `EventNetwork` type has no parameters. That tells us that all of the input and output is handled via side-effects in `IO`. It also means there can't really be a function like
interpret? :: EventNetwork -> [a] -> IO [b]
because what would `a` and `b` be?
This is an important aspect of the design of `reactive-banana`. It makes writing bindings to an imperative GUI framework easy, for instance. The magic of `reactive-banana` is to shuffle around all the side-effects into, as the docs call it, "a single, huge callback function."
Furthermore, it's typical for the event network to be intimately associated with the GUI itself. Consider the `Arithmetic` example, where `bInput1` and `bInput2` are both built using the actual input widgets, and the output is bound to `output`, another widget.
It would be possible to build a testing harness using "mocking" techniques as in other languages. You could substitute out the actual GUI bindings with bindings to something like `pipes-concurrency`. I haven't heard of anybody doing so.
How to unit test: Abstract out the logic
Better, you can and should write as much of your program logic as possible in separate functions. If you have two inputs, of types `inA` and `inB`, and one output of type `out`, perhaps you can write a function like
logic :: Event t inA -> Event t inB -> Behavior t out
This is almost the right type to use with `interpretFrameworks`:
interpretFrameworks :: (forall t. Event t a -> Event t b) ->
[a] -> IO [[b]]
You can combine the two input `Event`s using `split` (or rather, split the input into the two `Event`s required by `logic`). Now you'd have `logic' :: Event t (Either inA inB) -> Behavior t out`.
You're kind of stymied converting the output `Behavior` to an `Event`. In version 0.7, the `changes` function in `Reactive.Banana.Frameworks` had type `Frameworks t => Behavior t a -> Moment t (Event t a)`, which you could have used to unwrap the `Behavior`, although you'd have to do it in the `Moment` monad. In version 0.8, however, the `a` is wrapped up as a `Future a`, where `Future` is an unexported type. (There's an issue on Github re exporting `Future`.)
The easiest way to unwrap the `Behavior` is probably just to reimplement `interpretFrameworks` with the appropriate type. (Note that it returns a tuple containing the initial value and the list of subsequent values.) Even though `Future` is not exported, you can use its `Functor` instance:
interpretFrameworks' :: (forall t. Event t a -> Behavior t b)
-> [a] -> IO (b, [[b]])
interpretFrameworks' f xs = do
output <- newIORef []
init <- newIORef undefined
(addHandler, runHandlers) <- newAddHandler
network <- compile $ do
e <- fromAddHandler addHandler
o <- changes $ f e
i <- initial $ f e
liftIO $ writeIORef init i
reactimate' $ (fmap . fmap) (\b -> modifyIORef output (++[b])) o
actuate network
bs <- forM xs $ \x -> do
runHandlers x
bs <- readIORef output
writeIORef output []
return bs
i <- readIORef init
return (i, bs)
This should do the trick.
Comparison with other FRP frameworks
Contrast this with other frameworks like Gabriel Gonzalez's `mvc` or Ertugrul Söylemez's `netwire`. `mvc` requires you to write your program logic as a stateful but otherwise-pure `Pipe a b (State s) ()`, and `netwire` networks have type `Wire s e m a b`; in both cases the types `a` and `b` expose the input to and output from your network. This gets you easy testing, but precludes the "inline" GUI bindings available with `reactive-banana`. It's a tradeoff.
Problem
Is there a way to unit test networks created in reactive banana? Say I've built up some network with some input events - is it possible to verify that events have produced some output stream/behaviours have some value after some number of input events. Does it even make sense to do this? I noticed there are various `interpret*` functions but couldn't seem to work out how to use them. There's also the `Model` module which looks ideal for testing but has completely different types to the real implementation.