Design your signals before resorting to convoluted logic

Using a tool that is new to us doesn’t automatically make
our brains adopt a corresponding way of thinking. Thus, opportunities for misuse
and overcomplication present themselves in many places.

In this case the new tool is Elm, and the way of thinking is Elm’s
particular interpretation of FRP. Specifically signals.
We’re going to see that having a seemingly convenient signal plugged to
the wrong part of our program can lead us to making up problems, and
having to come up with convoluted ways of solving them.

Input signals and the “Update” layer

While built-in goodies such as Window.dimensions and Mouse.isDown are pretty much
the cornerstone of our Elm GUIs, we can (and sometimes should, as we’ll see) control,
transform, and interpret these primitive signals, to create more semantic ones.

This is not just a matter of aliasing the True and False values of Mouse.isDown to
more meaningful ones, i.e. Shooting and Idle. It is also a matter of creating the
simplest and most correct signal for the job.

What I’m going to do here is stage a common situation I’ve found myself
and others (from the Elm questions I’ve gotten from other newcomers) in.

The type – which isn’t annotated because type inference FTW – of our input value is now Signal (Float,Bool).
That is, a signal of a pair of values: the time delta, and a boolean representing whether the enter key is down.
The first argument of the update function has also been modified to accept the new shape of our input.

So far so good, but we want to toggle the state, which means we can’t just change the update code to:

Right? Because this “pause” functionality would pause the program only while the enter key is down. When we release
the button, the program will resume, and that’s not the behavior we want.

Therefore we’re going to need more than just the Keyboard.enter signal.

Adding a representation of the program’s state

Presumably, we want some representation of the running state of the program.
Once we have that as a value, the plan is then to check Keyboard.enter in
the update function, and if it’s True, toggle the value which
actually represents the state of the program.

Before proceeding: Do you see a problem with this plan?.

Let’s add an algebraic data type for representing the state,
and attach an initial value of this type to our model.

The program now toggles the state depending on what the current value of Keyboard.enter is,
and then the meat of our update logic acts according to the computed state.

However, if we tested this program, we’d see that our pause button is
broken: Sometimes it pauses the program. Sometimes it doesn’t. Sometimes it pauses
and then immediately resumes.

Our plan has a problem

The plan seemed complete: If Keyboard.enter is True, toggle the ProgramState. What else
is there to do?.

The problem is that we forgot about the fact that the update function is executed 24 times per second!.

From the moment we press down the enter key, until the moment we release it, update will have seen
Keyboard.enter as True (and therefore toggled the state) a bunch of times.

If you already know a bit about Elm, you’re probably thinking about a signal-filtering
function such as dropRepeats. But that wouldn’t solve the problem,
because even if you plugged dropRepeats in front of Keyboard.enter, the update
function would still see the current value of the signal (repeated or not) 24 times
per second. (However, you are on the right track!)

Further action is needed

This is where things will get convoluted. However, since there’s
people who browse the web for tutorials and then copy/paste
code without even reading the actual explanations for it, I will not write the
actual code for the convoluted solutions to our current problem, just in case!.

But it’s easy to imagine that the
“next step” in the evolution of our increasingly convoluted program would be
to add more state and conditions to the update logic to basically, somehow,
toggle the state if Keyboard.enter is down, unless it’s just been
pressed and it hasn’t been released. Or something like that.

Yikes.

To be clear: we could (and I have, and seen others do, too) keep adding
more flags and state and crap to our current program, and eventually get a
functioning pause button (or similar requirement), but we
should stop here, and take a couple of steps back.

Where did we go wrong?

At the beginning, actually.

Remember I mentioned the idea of a convenient signal
connected to the wrong part of our program.

If we go back to our introduction of the Keyboard.enter signal, we’ll see that the update
function changed to:

-- ...-- Updateupdate(t,enterIsDown)(x,y)=((sint)*50,y)-- ...

But I did not ask the question: is Keyboard.enter plugged to update a sensible signal
path for our ultimate goal?. And the answer to that question is negative.

I made the following
logical leap (which perhaps you caught right away): “Program P needs to react
to key K. Therefore, update logic U needs to react to key K.”

The worst thing about this leap is that, after it was made, all further problems and
“solutions” appeared to be the concern of the Update layer. So, as I saw that the program
still wouldn’t behave as expected, I kept wanting to add more logic to the update function.

Designing our signals

As I’ve hinted enough already, what we need to do is a bit of signal transformation.

The ProgramState algebraic type and its toggleState operation were actually a good idea,
so let’s keep ‘em, but use them better:

Look at the update function. It no longer reacts to the current value of
Keyboard.enter. Rather, it reacts to the current value of a Signal ProgramState.

Does it matter that the update function gets executed multiple times while our finger is
pressing enter? Nope! As can be seen in the definition of input, the logic for toggling
the ProgramState signal only runs whenever Keyboard.enter updates discretely.

Now what the update function sees 24 times per second – it will still run that many
times, since it has to react to the (fps 24) signal we’re also plugging into it – is
whatever the current value of the ProgramState signal is.

Additionally, note that we don’t even need to pollute our model m with a ProgramState value.

A last bit of refactoring

For extra points, let’s remove some repetition, by making a function that returns its first argument if a state is
Running, or the second argument otherwise. We can then use that function both for toggling the state in our
now more tailored input, and for reacting to it within the update function:

Revision Jan 10, 2014

…Or is it?. As Apanatshka has noted,
there is still one superfluous call to the ifRunning function. And from what we’ve been talking about, it’s obvious which one that is.
But I’ll leave the actual further (and final) complete refactoring step as an exercise to the reader! (Hints in Apanatshka’s comment.)

Goodness achieved

We went back to the drawing board, and:

Put the logic for computing the ProgramState in a better part of the program.

Avoided a set of problems entirely, by recognizing the usefulness of Elm’s discrete signals and signal manipulation.

Made the update function react to a more semantic signal of ProgramState. Now it knows nothing about Keyboard.enter.

Basically, we gained expressiveness and controlled complexity as a direct result of plugging the right signals into the right places.

Finally, a generalization of the lesson

(Because no programming blog post is complete without some goddamn “rule of thumb” by the author!)

Leaving signals, FRP, Elm, and actually pretty much all of the specifics of this
post aside, there seems to be an additional, and more general lesson here,
which if I had to formulate, would go something like this: The less interpretation
logic – such as conditionals and other forms of probing – we do on the
input arguments of a function, the more the function can be read as a simple equation,
which is A Good Thing.