12 comments

[ 3.1 ms ] story [ 44.6 ms ] thread
Well explained, thanks!
Really nice explanation and code samples. I'm glad to see more accessible materials emerge for explaining what signals are. The Elm website already does a good job, but I think the extra detail in this article will help more people grok it.

I have done my own exploration in this area. I hack on a game engine written in Scheme that includes a signal implementation, a declarative scene graph, and a live coding environment via a REPL server. It's not as glamorous as Elm, but I'm quite proud of it and I plan to make the first release soon.

http://dthompson.us/pages/software/sly.html

An older blog post I did with code samples and a quick screencast when the project had a different name:

http://dthompson.us/functional-reactive-programming-in-schem...

Thank you! "Accessible" is precisely what I wanted the post feel like.

Sly seems very cool too, I hope it will help popularizing these ideas!

I think this is the first time the concept of currying has solidly clicked for me; most explanations focus on the "your function should take one argument" aspect more than the "your function should return a new function" aspect, which meant that, until now, currying looked more like an obstacle than a tool.

Thanks!

That's great to hear! I also had some trouble finding the gist of currying. As you said, the _why_ is often missing in the explanations. Glad I could help you in that regard.
So when I update ship, that means a new ship was created right? What happens to the old ship? and how is recreating the universe for every iteration efficient? Is there something tricky going on in the background?
Consider that in many cases, you don't need to recreate the whole universe--lots of stuff may stay the same.

Also, to get to the old ship, you just rewind the events, because the state can be fully reconstructed (no weird side-effects).

A new ship is created and the old ship is sailing towards garbage-collection unless other objects are still holding references to that value.

This is going to add more pressure on the garbage-collector for sure.

You claim that this is reactive rather than imperative, but instead, it feels imperative instead of object oriented.

You still have your "setup method":

    Signal.map show (Signal.foldp update initShip inputSignal)
and your "handle updates method":

    updateVelocity newVel (updateShooting isShooting (applyPhysics dt ship))
And the distinction between "set the ship's position to ship.position + ship.velocity * dt" and "create a new ship similar to the old ship but the velocity is ship.position + ship.velocity * dt" seems like splitting hairs.

It's not OO, definitely—but it doesn't feel like you're doing anything differently than you would if this was a purely structural program, even if some of the details are different. If this was straight C, "update" would be a method that gets called 30 times per second, and "main" would get called when the game starts, and everything else would map pretty much line-for-line.

Similarly, the "functional-reactive" nature of it feel like an implementation detail, rather than a different way of thinking about the code—in your update method you still walk through the steps "was <- or -> pressed? was ^ pressed? Move the ship. Fire your gun. Change your velocity." Maybe some of them don't need to be recalculated? Okay, but as the programmer you still need to describe the same steps, even if some of them get optimized away.

And honestly, even going through the same steps, it makes it harder to understand. Take your update function:

    updateVelocity newVel (updateShooting isShooting (applyPhysics dt ship))
Okay, so you have an applyPhysics method that takes a ship, and a dt. That's pretty clear. And it returns... something, and that something gets passed into updateShooting, and then what updateShooting returns gets passed into updateVelocity. You have to go elsewhere to read that, okay, applyPhysics and updateShooting both return ships. The same steps, written in a more imperative syntax:

    applyPhysics(ship, dt);
    updateShooting(ship, isShooting);
    updateVelocity(ship, newVel);
Which (a) makes it more clear that a ship gets passed into each method, but (b) also lets you pass in the more important parameter first, which aids readability, and (c) lets you list the methods in the order that they occur, rather than writing them in the reverse of the order they occur. To at least get the better argument order with Elm you'd have to write it:

    updateVelocity (updateShooting (applyPhysics ship dt) isShooting) newVel
Which is completely unreadable—you're reduced to counting parentheses to see which method "isShooting" gets passed into.
Good feedback, thank you!

I will rethink some of the phrasing, especially on the reactive/imperative references. To be honest, the reactive part in the example is rather slim (arguably only the `Signal.foldp` is "reactive"), and it is so by design. I wanted to describe the appeal of writing pure functions first off, and then subtly bind the existing code to the signals at play. Obviously this is an opinionated decision.

Regarding the update function, I refrained from using the infix operator just to make the article a bit more approachable. Was I to write the code just for myself, it would have been:

    ship
      |> applyPhysics dt
      |> updateShooting isShooting
      |> updateVelocity newVel

This is also the reasoning for the argument order.
The biggest difference in thinking comes from the concept of signals. Initially when you hear that a signal is a value that changes over time it doesn't seem all that different from setting the state on an object. But actually a signal is more like an array in that it's also a collection of values except it's a collection of values over time. And crucially, like an array, a signal can be mapped over. If you look at the last line you see that 'main' is a signal of Elements, which we can assume is some type that elm knows how to display in the browser.

So a single instance of Element can be thought of as the browser's displayed state at a single point in time, in this case a single frame of the ship's movement animation. Each subsequent Element in the signal can be thought of as a transition from one browser state to the next, or from one animation frame to the next.

Now the key is that each Element in the Element signal is being mapped 1 to 1 with a Ship in the Ship signal. (The Ship signal is the return value of 'Signal.foldp update initShip inputSignal') In fact the whole application can be thought of as a transformation on an initial Ship and a signal of keyboard inputs, to producing a signal of Elements.

So in building this app the thought process might be that I first create an initial Ship and a signal of keyboard inputs to start. I want my app to update at 30fps, so I create a signal that samples the keyboard inputs signal at this interval. Now from this input signal I want to produce a signal of updated Ships. From the signal of Ships I want to produce a signal of Elements.

Finally you can see everything come together at the end, and it is quite descriptive. You want a signal of Elements. What is a signal of Elements? It is a mapping of a signal of Ships over the show function. What is a signal of Ships? It is a signal of inputs foldp'd over an update function (which gets passed the input and the most recent Ship). What is an input signal? It is a sampling of the user's keyboard inputs at a 30fps interval. Etc.

So we end up with descriptive functional code. There are no statements here, no loops, and no mutation, which means that we don't need to coordinate the order of execution with changes in the state of objects, which is a huge step forward in reducing the complexity of a program.