Elm architecture: Hiding the R in FRP

Petr Šíma
8 min readJul 19, 2017

--

Functional reactive programming has been all the rage in mobile application development. We were early adopters of ReactiveCocoa/ReactiveSwift at my former job at Ackee. Other teams may have been put off by the learning curve at first, but by now most companies have seen reactive programming in some form and many are using Rx frameworks on different platforms, including iOS and Android.

MVVM — the last breath of OOP

With FRP, connecting all your different data sources to your almost declarative view layer is as easy as storing state in an observable form. You just need a place to put those observable properties and a bit of business logic that fills them with values — you need a ViewModel.

But MVVM has it’s problems. You can draw all the arrows one way (i.e. View -> ViewModel -> Model), but there’s still communication in the other direction as well. You declare your reactive bindings and forget about them, you act like the arrow isn’t there. And it just works … except when it deadlocks. What’s worse is that finding the source of such deadlock (be it a recursive event or multiple signals sending events at the wrong time) isn’t the hardest problem. Fixing it is. By using a reactive framework, you’ve already declared that your code can handle whatever input we throw at it, whenever we choose to throw it. Except now it can’t. How are you supposed to fix this? A simple skipRepeats() helps some of the time, but sometimes you end up writing code like .skip(first: 2) or .delay(0.0, on: QueueScheduler.main) and pray that the project ends before it becomes unmanagable.

Furthermore, ViewModels are objects. You’ve replaced OOP with FRP on the inside, but your app still consists of these stateful actors that need to coordinate. When two ViewModels need to share some state, you flip a coin — “Heads says we put it in a ‘Service’, tails it’s a ‘Manager’”. Either way, it’s another stateful object. And what about communication between these? Have you ever been tempted to write a ManagerManager?

As I was writing my piece on iOS app architectures, I already knew it was my goodbye to MVVM. I went really soft on it, appreciating all the good it has done for me over the past few years. But it’s time to move on. It’s time to get rid of all these objects that act, and react, and communicate, and synchronize, and cause inconsistencies and deadlocks. It’s time to stop acting like there’s no concurrency, and instead be truly ready for any event to come in at any time.

MVU — “They just changed one word again.”

The Model and the View are always there, right? Whether you use MVP, MVVM, the more complicated but still object oriented VIPER (although Model didn’t make it to the name), or even when you take your kids to the museum to see the ancient MVC. It’s always Model, View, and the interesting stuff that glues them together, and where sometimes terrible things happen.

In Model-View-Update, the entire Update layer is a single pure function. It takes in the current state and a description of a change to be made, and returns a new state.

func update(from model: Model, with msg: Msg) -> Model

model is the whole application state, msg is an action to be performed. That’s just an automaton. I’ve seen this a few places in computer science, and I don’t just mean Redux and Flux. I would love to be able to describe my whole app’s behaviour in one place like that. Can I?

Stateless core, stateful everything else

We can’t replace all objects (We can’t run our apps without our UIDevices ), but we can fight objects. We can push them back into college classrooms and we can also push them out of our app logic onto the outskirts where they interact with the outside world. And by outside world I mean the physical world (playing sounds) as well as legacy code (old object oriented APIs like UIKit). There will always be a stateful shell around our application’s functional stateless core. We just won’t have objects running around, causing havok in our if-else statements.

Your app will most likely need a view. With MVU, you can choose not to have a view at all, to run a headless Program. But if you want a view, you need a way to update the view hierarchy after each state change. On the web this is easy. Just generate new HTML and let React do the rest. So, do we ditch Swift and go for React Native? Nah. React Native is cool but it’s not for everyone, far from it. If you didn’t have a reason to use React Native before, you might not have one now. React Native does VDOM + it’s for crazy web people. We only need the VDOM part.

There are Swift React clones that already do this. You supply a view(for model: Model) -> ViewDescription function and the Program will call it on every update and render the new view (i.e. run lots of diffs and update the UIKit objects for you).

A typical app also does other things — makes HTTP requests, plays sounds, writes to DB, or otherwise affects the outside world. We say that it has side-effects. To trigger an effect (as an additional step after updating the state), all you need is access to the shell and a bit of bad programming taste, so you don’t stress over your update function no longer being pure.

Going full Elm

Elm is a purely functional programming language. This means that, much like in Haskell, you can’t just fire side-effects at will. Elm treats effects as data. The only way to affect the outside world from Elm is to construct a description of an effect, and ask the runtime to perform the necessary work. This means that the update function stays pure, it just returns a new state and a list of tasks for the runtime to carry out. In Swift, we can’t quite enforce this purity at compilation time, but having a strict no side-effect policy and a pedantic enough code reviewer comes close for all practical purposes.

Incidentally, Elm is an incredibly simple and powerful language with a great set of tutorials. Even if you have experience with Redux, or perhaps even Swift unidirectional dataflow frameworks like ReSwift, I highly recommend that you step back, read the full Elm language guide and learn to appreciate what we are trying to achieve with Model-View-Update. That way, when you are forced to make compromises, be it for performance, a lack of Swift’s features or an outdated, object oriented SDK, you won’t second guess the motivation behind or the usefulness of what this new architecture brings to the table.

Effects can also feed events back into the Program, and so can the View. Effects are split into Commands, for tasks like making HTTP requests, and Subscriptions, for listening to data changes (sockets, local and remote databases, etc.). So our final update layer looks like this:

func update(from model: Model, with msg: Msg) -> (Model, Cmd<Msg>)
func subscriptions(for model: Model) -> Sub<Msg>

and our view layer:

func view(for model: Model) -> ViewDescription<Msg>

I’ve lost track of time.

This article is getting long, but that’s not what I mean. I used to sell FRP to students as functional programmning + time. I used to be aware of how my reactive signals synchronized. I got pretty good at it. In time, your eyes begin to see potential deadlocks, much like they began to see memory leaks after some time spent in an ARC environment. But now I can just take those 3 above functions, shove them into what’s called a ReactiveAutomaton (It does what you think.) and forget about the R in FRP.

Sure, a long running Command can produce an event at an unexpected time, but now we care less than ever. If there’s logic to be added, just store the necessary info in your Model:

let ignoreTheCommandResultCauseTheUserHasLoggedOut: Bool

Btw, it’s most likely already there:

var ignoreTheCommandResultCauseTheUserHasLoggedOut: Bool { 
return !isLoggedIn
}

We must always expect the unexpected, but we are gaining so much in return. State restoration and time-travel, great composability and testability, but most importantly for me, pure functional programming. It’s really quite a feat to be able to write an interactive application using just data transformations. The notion of time is still there, we can’t get rid of it. But it lives out there, on the outskirts, in the bezel ouf our app. And we hear the future might be pretty bezeless.

Does it do animations?

Yes. Probably. To be honest, I don’t care that much. When React first came out, everyone was stressing about the lack of animation support. I’m sure many people said they won’t use it for that reason. But they still did. So who cares? The benefits are there, and there’s plenty of people who aren’t willing to ignore them. So yes, we will do animations. It might be a bit more complicated. Project managers might have to reallocate some time from fixing stupid bugs to implementing animations. But it won’t be too bad. Since iOS8, CAAnimations are additive by default, so just use those for now, it will look ok. And performance? We could talk about that for a long time. And by the time we’re done talking, it will no longer be an issue.

So this seems to be the way to go. Us mobile programmers have one big advantage — when in doubt, we can just look at what the web did 2 years ago. And I truly believe that these reactive automata, these unidirectional apps with a feedback loop are the future of mobile application development. A future that will allow even more people to take up programming, because it’s just so fucking easy. And for you, the experienced developer, it will be a breeze. Afterall, it’s just functional reactive programming minus the time. Who knows, you might even have time to write some tests.

--

--