Netwire
Netwire is a library for functional reactive programming, which uses the concept of arrows for modelling an embedded domain-specific language. This language lets you express reactive systems, which means systems that change over time. It shares the basic concept with Yampa and its fork Animas, but it is itself not a fork.
Features
Here is a list of some of the features of netwire:
- arrowized interface,
- applicative interface,
- signal inhibition (
ArrowZero
/Alternative
), - choice and combination (
ArrowPlus
/Alternative
), - self-adjusting wires (
ArrowChoice
), - rich set of event wires,
- signal analysis wires (average, peak, etc.),
- impure wires.
Quickstart
This is a quickstart introduction to Netwire for Haskell programmers familiar with arrowized functional reactive programming (AFRP), for example Yampa or Animas. It should quickly give you an idea of how the library works and how it differs from the two mentioned.
The wire
Netwire calls its signal transformation functions wires. You can think of a wire as a device with an input line and an output line. The difference between a function and a wire is that a wire can change itself throughout its lifetime. This is the basic idea of arrowized FRP. It gives you time-dependent values.
A wire is parameterized over its input and output types:
data Wire a b
Differences from Yampa
If you are not familiar with Yampa or Animas, you can safely skip this section.
The main difference between Yampa and Netwire is that the underlying arrow is impure. While you can choose not to use the impure wires inside of FRP.NetWire.IO
, it is a design choice for this library to explicitly allow impure computations. One theoretical implication is that you need to differentiate between pure stateless, pure stateful and impure signal transformations.
A concept not found in Yampa is signal inhibition. A wire can choose not to return anything. This way you can temporarily block entire subnetworks. This is most useful with the combination operator <+>
. Example:
w = w1 <+> w2
The w
wire runs its signal through the wire w1
, and if it inhibits, it passes the signal to w2
.
Another concept not found in Yampa is choice. Through the ArrowChoice
instance wires allow you to choose one of a set of subwires for its signal without needing a switch. Essentially you can write if
and case
constructs inside of arrow notation.
Because of their impurity wires do not have an ArrowLoop
instance. It is possible to write one, but it will diverge most of the time, rendering it useless.
Using a wire
To run a wire you will need to use the withWire
and stepWire
functions. The withWire
initializes a wire and gives you a Session
value. As metioned earlier in general a wire is a function, which can mutate itself over time. The session value captures the current state of the wire.
initWire :: Wire a b -> (Session a b -> IO c) -> IO c
stepWire :: a -> Session a b -> IO (Maybe b)
The stepWire
function passes the given input value through the wire. If you use stepWire
, then the wire will mutate in real time. If you need a different rate of time, you can use stepWireDelta
or stepWireTime
instead.
The stepping functions return a Maybe b
. If the wire inhibits, then the result is Nothing
, otherwise it will be Just
the output.
Writing a wire
Pure stateless wires are easy to explain, so let's start with them. A pure stateless wire is essentially just a function of input and time. The simplest wire is the identity wire. It disregards time and just returns its input verbatim:
identity :: Wire a a