This is the core module of Elerea, which contains the signal
implementation and the atomic constructors.

The basic idea is to create a dataflow network whose structure closely
resembles the user's definitions by turning each combinator into a
mutable variable (an IORef). In other words, each signal is
represented by a variable. Such a variable contains information about
the operation to perform and (depending on the operation) references
to other signals. For instance, a pointwise function application
created by the <*> operator contains an SNA node, which holds two
references: one to the function signal and another to the argument
signal.

In order to have a pure(-looking) applicative interface for the most
part, the library relies on unsafePerformIO to create the references
of stateless signals, while stateful signals have to be obtained from
a special SignalMonad, which is just a wrapping of IO that doesn't
allow any other action to be performed.

The execution of the network is explicitly marked as an IO operation.
The core library exposes a single function to animate the network
called superstep, which takes a signal and a time interval, and
mutates all the variables the signal depends on. It is supposed to be
called repeatedly in a loop that also takes care of user input.

To ensure consistency, a superstep has three phases: sampling, aging
and finalisation. Each signal reachable from the top-level signal
passed to superstep is sampled at the current point of time
(sample), and the sample is stored along with the old signal in its
reference. If the value of a signal is requested multiple times, the
sample is simply reused. After successfully sampling the top-level
signal, the network is traversed again to advance by the desired time
(advance), and when that's completed, the finalisation process
throws away the intermediate samples and marks the aged signals as the
current ones, ready to be sampled again. If there is a dependency
loop, the system tries to use the sampleDelayed function instead of
sample to get a useful value at the problematic spot instead of
entering an infinite loop. Evaluation is initiated by the
signalValue function (which is used in both the sampling and the
aging phase to calculate samples and retrieve the cached values if
they are requested again), aging is performed by age, while
finalisation is done by commit. Since these functions are invoked
recursively on a data structure with existential types, their types
also need to be explicity quantified.

As a bonus, applicative nodes are automatically collapsed into lifted
functions of up to five arguments. This optimisation significantly
reduces the number of nodes in the network.

The Applicative instance with run-time optimisation. The <*>
operator tries to move all the pure parts to its left side in order to
flatten the structure, hence cutting down on book-keeping costs. Since
applicatives are used with pure functions and lifted values most of
the time, one can gain a lot by merging these nodes.

Sampling the signal and all of its dependencies, at the same time.
We don't need the aged signal in the current superstep, only the
current value, so we sample before propagating the changes, which
might require the fresh sample because of recursive definitions.

Sampling the signal at the current moment. This is where static
nodes propagate changes to those they depend on. Transfer functions
(SNT) work without delay, i.e. the effects of their input signals
can be observed in the same superstep.

Sampling the signal with some kind of delay in order to resolve
dependency loops. Transfer functions simply return their previous
output (delays can be considered a special case, because they always
do that, so sampleDelayed is never called with them), while other
types of signals are always handled by the sample function, so it is
not possible to create a working stateful loop composed of solely
stateless combinators.

A stateful transfer function. The current input affects the
current output, i.e. the initial state given in the first argument is
considered to appear before the first output, and can only be directly
observed by the sampleDelayed function.

A reactive signal that takes the value to output from a monad
carried by its input when a boolean control signal is true, otherwise
it outputs Nothing. It is possible to create new signals in the
monad and also to print debug messages.

The delay transfer function emits the value of a signal from the
previous superstep, starting with the filler value given in the first
argument. It has to be a primitive, otherwise it could not be used to
prevent automatic delays.

Dependency injection to allow aging signals whose output is not
necessarily needed to produce the current sample of the first
argument. It's equivalent to (flip . liftA2 . flip) const, as it
evaluates its second argument first.