We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Immutability is for UI, You, and I

00:00

Formal Metadata

Title
Immutability is for UI, You, and I
Title of Series
Number of Parts
37
Author
License
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
Immutability. It may sound like an ominous something from the far-off galaxy of math, but in practice, it's one of the most pragmatic tools for thinking about UI. In this talk we'll explore the problems that an immutable style solves, and how you can use it as a thought-tool to both design and implement more powerful and composable components. Throughout we'll see just how deeply Ember supports this mode of thought at every step of the way.
13
Thumbnail
07:51
CodeVideoconferencingMultiplication signComputer animationLecture/Conference
Data modelCategory of beingEndliche ModelltheorieState observerGateway (telecommunications)Physical systemRouter (computing)Common Language InfrastructureComputing platformTime evolutionForm (programming)Library (computing)Complete metric spaceSource codeBuffer solutionObject (grammar)Rule of inferencePasswordProcess (computing)Computer networkOrder (biology)Content (media)State of matterValidity (statistics)Process (computing)Endliche ModelltheorieGroup actionGoodness of fitCASE <Informatik>MathematicsSoftware developerInformation securityMultiplication signEntire functionPhysical systemState observerMassNetwork topologyComplex (psychology)Category of beingServer (computing)Source codeDisk read-and-write headCartesian coordinate systemElectronic mailing listForm (programming)CodeExterior algebraTwitterData miningUniqueness quantificationoutputBitMobile appObject (grammar)Virtual machineComplete metric spaceFrictionHybrid computerPower (physics)NeuroinformatikPhase transitionLibrary (computing)Social classMultiplicationPoint (geometry)Client (computing)Logical constantMechanism designArtistic renderingComputing platformWater vaporKeyboard shortcutBuffer solutionResultantOperating systemPhysical lawShared memoryChainSemiconductor memorySoftware frameworkOnline helpSingle-precision floating-point format
Data modelVideo game consoleRule of inferenceData typeError messageSineExecution unitSet (mathematics)View (database)MathematicsState of matterStreaming mediaIndependence (probability theory)Frame problemContent (media)Pattern languagePresentation of a groupPasswordForm (programming)outputGroup actionRight angleCodecSoftware frameworkEndliche ModelltheorieArtistic renderingElectronic program guideView (database)Multiplication signScripting languageObject (grammar)Data managementArithmetic meanUniqueness quantificationProcess (computing)Content (media)Category of beingData structureState of matterForm (programming)PasswordStreaming mediaWordCodeEvent horizonFiber bundleConnectivity (graph theory)InformationGame controllerReal-time operating systemGroup actionMathematicsTemplate (C++)Source codeFrame problemFluid staticsSingle-precision floating-point formatControl flowDefault (computer science)Computer scienceOrder (biology)Cartesian coordinate systemAreaLibrary (computing)Web browserCore dumpComputer architectureLevel (video gaming)Shared memoryPosition operatorCASE <Informatik>Directed graphPoint (geometry)Bit ratePhysical systemPerfect groupAlgebraic closureBlock (periodic table)Key (cryptography)Line (geometry)HierarchyAcoustic shadowFilm editingChemical equationVolumenvisualisierungFigurate numberNetwork topologyRight angleTheoryMappingEntire functionField (computer science)Discrete groupSequenceSynchronizationDifferent (Kate Ryan album)NeuroinformatikGraph (mathematics)Functional (mathematics)DeterminantComplex (psychology)DeterminismKeyboard shortcutException handlingUniform resource locatorPattern languageSet (mathematics)Video game consoleLatent heatBound stateGoodness of fitComputer animation
PasswordForm (programming)MathematicsObject (grammar)Computing platformCausalityEvent horizonInformationState of matterData modelContent (media)Sample (statistics)State of matterSocial classMereologyComputer programmingNumberCASE <Informatik>Web browserPhysical systemFluid staticsMultiplication signCartesian coordinate systemContent (media)Computing platformObject (grammar)NeuroinformatikData structureNetwork topologyTape driveRight angleOperator (mathematics)Event horizonLine (geometry)Category of beingWordSequenceDefault (computer science)CodeLibrary (computing)Process (computing)Decision theoryFrame problemGroup actionObservational studyBoundary value problemSampling (statistics)Run time (program lifecycle phase)Streaming mediaError messageSingle-precision floating-point formatConnectivity (graph theory)Entire functionShift operatorSoftware bugEndliche ModelltheorieTerm (mathematics)Water vaporMathematicsWeightReplication (computing)InformationTemplate (C++)Forcing (mathematics)Interpreter (computing)Commitment schemeSoftware frameworkFormal languageComputer architectureSpeech synthesisChainLevel (video gaming)State observerVideo game consoleSoftware developerFilm editingBitComputer animation
VideoconferencingService (economics)Event horizonComputer animation
Transcript: English(auto-generated)
It was a land, it was a world, without selfie sticks.
It was 2012. I lived it, I was there. I was actually busy writing applications
using a technology called Backbone. And for those of you who are there at the time, this is going to be a little bit of a trip down memory lane. If you weren't, it might be a little bit involved, a little bit painful. But I really, really, really enjoyed working with Backbone, because it allowed me to build these model-centric applications. And because the models were rich,
the rendering could be this rote mechanical process whose outcome was just predetermined by the content of those models. And the way that Backbone did this was with property observation. You could add an observer to a model and be notified whenever a property changed on that model, and you could do any updates to anything that depended on that property.
And this allowed entire trees of objects to be reactive. If a single property changed anywhere in the system, that change could ripple throughout the tree, and the DOM could be updated via that rote mechanical process that I was describing. And I was really quite happy with this approach, and I felt generally good about the apps that I could deliver with this style.
That is, until I didn't. As my applications became more complex, I started running into more and more friction that I just couldn't seem to eliminate. Backbone lets you observe a single property on a single object,
but what if you needed to layer multiple dependencies? As the applications grew, this distressing amount of code just started to grow that was dedicated to nothing but setting up and tearing down observers. And most of those observers were doing nothing but just copying one value from one place to another.
And things got worse when you were trying to react to collections of objects, and Backbone never really did get a satisfying answer to this, and it was a constant source of pain for me. So now I know what you're thinking at this point. This is EmberConf. Why are we spending time on questions that were settled for us years ago?
And the reason that I think that it's important to include it in this story is that because the pain I was feeling with my Backbone setup was acute enough that while I wasn't fully conscious of it, I was seriously open to a complete change in tools. So I went out, and I was looking for alternative approaches.
And this is, of course, where Ember enters the story. Specifically, EmberObject. This single class up there had everything that I was missing in Backbone, and then some. It had nested key-value observations, so I didn't have to manually construct these huge chains of observers. It had array observations, so I finally got those reactive lists that I was after.
It had these things called computed properties that blew my mind with their power. And it won me over almost immediately, and we started using it. And so we had this strange hybrid phase where we ended up using EmberObject pretty much as a drop-in replacement for Backbone model. And with the turbo-charged observers and computed properties,
it turns out that Ember was actually better at doing Backbone than Backbone was. Not only did EmberObject have everything that I needed, it also came bundled with a whole lot more than I really had bargained for in the first place. It had this incredible router,
it had this nice system for decorating model objects, and while it was still nascent at the time, it had this unbelievably warm and helpful community. And Backbone didn't really offer me anything that Ember didn't, and so we never really looked back. We threw in our lot with Ember, and honestly, it's been great, and we never thought of looking back. And because of that, I got to inherit nearly for free
all of the wonderful things that came later, like Glimmer, Ember CLI, just to bring up a few. So that brings us up to 2015. It was around EmberConf one year ago, and exciting stuff was happening. Glimmer had just been announced, dated down, actions up, was being codified as kind of the community best practice,
and the hype machine was geared up, and it was in full swing. And overall, I was really quite happy with Ember, and I felt generally good about the apps that I could deliver with it. It wasn't until I didn't. As the problems that I was trying to solve became more complex, I started running into more and more friction
that I just could not seem to eliminate. And ironically, it was related to EmberObject, which was the thing that had kind of stolen my heart to begin with. And the pain that I was feeling with my Ember setup was acute enough that while I wasn't fully conscious of it, I was actually open to a complete change in tools.
So again, I went looking for alternate approaches, and I spent most of the time with two frameworks, one called Om and one called Omniscient. And these were React-based frameworks, but the fact that they used React was literally the least interesting thing to me. What really set them apart and what intrigued me most was their unique take on application state.
They were able to solve many of the problems that I was having very, very neatly, and the things that were gumming up my Ember code just seemed to fall away as if they didn't exist. So this left me with a question. Do I change my toolset completely? Unlike when I made the transition from Backbone to Ember,
there was a lot that the alternatives didn't offer. I still had a world-class router, but since I adopted it in the first place, there was even more great stuff than it had originally, not least of which was the promise that the platform could upgrade and sometimes change radically in the course of a very short time,
and that there would always be a path forward and that the community would support you. So what do I do? Well, I am standing here, so if that's an answer to the question. I mean, I don't want to give up on all that great stuff. It's mine. It makes me productive. I want to keep it. It makes me happy. So I set about to see if you could have it both ways.
If I could bring back this thought tool that I discovered when working with Om and Omniscient and use it with Ember, and it turns out that you can have your cake and eat it too. More than that, it turns out that it works really, really, really well with Ember right now, today. And this thought tool is what I want to share with you.
It's called immutability, and unlike what you might have heard in the past, it's very approachable. It sounds way more intimidating than it really is. My name is Charles Lowell. I'm CowboyD on Twitter and GitHub, and I submitted this talk in the hope that you can walk away today with this same tool in your tool belt
that has so dramatically improved the way that we write UI at the front side. And to be clear, I'm not talking about a piece of code or a library that you import and use in your Ember application. I'm talking about an organizing principle for thinking about your UI, a principle that made so many of these problems
that I was facing just melt away. So anyway, it was early 2015, Glimmer had been announced, the add-on ecosystem was really starting to flourish, and great big things were afoot. And at the time, I was heads down and I was working on a general purpose form library.
This wasn't any old form library. There's a lot of them out there. I tried all of them. And there's just a whole gaggle of problems that you always run into with user input, and I wanted to solve them in one place once and for all. And so the scope of this was pretty big. I've written a lot of forms before, and there's just a long list of things
that go into a truly sweet form experience. And as a result, it can be a lot of work to do every time. And instead of feeling like tired and exhausted, I wanted to feel excited every time that we were asked to build a complex form experience. And I knew that we would be excited if I could reduce the cost
of making all these things happen, get as close to free as possible. And that's where the problem started. In order to juggle all of those things at once, the model had to be pretty gnarly. You have this massive tree of values, this massive tree of input buffers, massive tree of validations,
all of which have these intermingled dependencies and they have to be kept in sync and react to each other. And you know what? Even as a pretty experienced Ember developer and a pretty seasoned UI developer at the time, I just could not make it happen.
I could get close. I could get really close. And I even got a couple times where I achieved this false sense of security. But then always some little edge case or some little something would happen that would just kind of shake the wheels right off the whole endeavor. And I never got there. And after a while, I spent a good bit of time asking myself, why?
Why was I so bedeviled by this particular problem in particular? Why was it difficult to come up with a general solution for something even as simple as this? Now this is a really, really simple example, but it does have validation states and ready states that depend both on the client and the server.
And this is where things got rough because it was highly asynchronous. It needed to handle updates from the keyboard, but there were server validations, the form submission itself, and they could fire in almost any order and they could return in almost any order and keeping the entire form state intact was a real headache. And I was using one-way bindings
and I was using computed properties, but the values that were backing them could change mid-computation, which introduced this non-determinism into the system. And the problem is determining exactly when that bad value is introduced. Because as your system continues to change, the bad state begets more bad state.
And when this happened, tracking it down was really, really difficult. The problem that I was having were with timing. And so to determine the sequence, I wanted to, for example, log things to the console, which is crucial. But have you ever tried actually logging a really complex data structure
that's happening in real time to the console? If it's an Ember object, you're going to see a lot of this and it's pretty rough. And in fact, when your graph is constructed with computed properties, it's pretty well useless because you can't read those computed properties from the console. But there was a deeper issue
with the computed properties. And that's for the first time that I could recall. Keeping the dependent keys in sync as I changed and kind of evolved the library became really onerous. In the form data structures, I wasn't just deriving a few extra fields to present to a template. I was actually computing, mapping entire trees to entire trees
and then computing roll-ups of those trees. And figuring out the right set of dependent keys was always possible, but it was just this endless source of paper cuts. And especially as I refactored and changed the data structures, it was like there was this shadow data structure that was chasing me of the hierarchy
of the dependent keys. And if they didn't match the actual data structure precisely, it might appear to work, but then it would fail in really subtle ways. And when I did get a stacker trace related to bad dependent keys, a lot of times it would just be completely and totally buried inside the vendor JS.
There was no reference, not a single line referencing my application code. And so none of these problems were really new to me. After all, I'd been doing nothing but Ember for three years before this, and I'd encountered each one of these in isolation. But what was new was the way they came together in this kind of perfect storm that just seemed to block me at every turn.
And so I was in the same place that I was at Backbone, and that's when I stood up from my desk and decided to start looking around. I heard good things about Ohm. It's a framework that uses ClosureScript, and then there's a pure JavaScript that's cousin called Omniscient, and so I spent some time with them. And what I found was that they had a very simple,
very unique take on state management. And when I tried applying the same techniques that they used back in Ember, well, it pretty neatly sidestepped all of the problems that I've just been describing to you. And so we'll get to that. But first let's do a quick review of the way that I handled state in Ember back then.
Ember was, and it remains today, an MVC framework. And without getting too bogged down in specifics and definitions, this is the essence of MVC to me. You have a model, and the content of that model guides the rendering process repeatedly.
It's deterministic. If the model contains the same properties, then it's gonna yield the same DOM every time. In other words, the view is a pure function of the model. This is true in Ember, it's true in Angular, it's true in React, it's even true in Backbone.
That is just MVC done right. The difference is how we go about changing that value of M up there. What we're used to doing is updating the model. We change some portion of the model by erasing one of its properties, and then replacing them with new values that we want.
The view is observing this, and it reacts accordingly. And for a lot of use cases, this is a very workable approach. But that's not how these frameworks did it. When it came to updating the model, rather than editing the properties in place, they always generated a completely brand new,
wholly formed state to replace the old one. No exceptions. And by replacing that model each time, it ceases to become a single object, the model, so much as a stream of discrete states that when rendered in succession
yield this seamless experience. And as it turns out, there's a very precise word for this sequence of discrete states in computer science and category theory. It's called a movie. We observe the movie as a single experience,
but we know in fact that the content of the movie is subdivided into discrete static frames. Each frame has clearly defined bounds. It has a start, it has a finish, and each frame of content exists completely
and totally independent of all the others. It's only when we take the static content and render it in succession through a player that the human experience emerges. Now a key point that I want to stress here is that even in the case where two of these frames
contain the same information, that information is faithfully replicated in both. And what I mean by that is if you look at these two frames, the only things that are different are the positions of Charlie Chaplin and that weird, scary masseuse. The walls, the towel racks, the sink, the faucets,
they're all in exactly the same place and yet each is individually represented in both. And because of this, any frame that you care to take contains all of the information that's needed to render. And I can print it out, I can hang it on a wall,
or I can take a piece of state that was generated almost 100 years ago and I can render it for you now. And so at some level, a movie and an immutable JavaScript framework share the same architecture. They both work by separating this content from the player.
The content or the stream of content is static. Each frame is unchanging, it's immutable. While the player is the thing that changes and rearranges itself in order to render that content. And when we play the content, that's when we see the experience.
So how do we go about harnessing this pattern in our Ember applications? That's actually a trick question because the answer is you already do. Every single Ember application is at its core a player of URLs. Those URLs, that's the stream of immutable content. And the application itself is the player.
It's the thing that rearranges itself in order to render the content. And like a real movie, you can use the browser back button and forward button to rewind and fast forward and it works well. It works flawlessly in most cases because of the way it's modeled. And so we use it here. But now let's look how we might apply this concept to an area where it's not something that we do by default.
How do we take that password form that we were looking at from earlier and how do we refactor that to be a movie? Well, it's pretty straightforward as it turns out. We want to break our component apart into two pieces, the content and the player. And we can do it in two simple steps.
The first step is to disentangle the properties that we use in the template and bundle them into a single object. And that single object is going to represent one of those static frames of content. And so here in this template,
you can see here before, these are intrinsic component properties. Is it long enough? And what's the rating? Is it a weak or moderate password? And what we're going to do is we're going to hide those inside this object called the model. And we're going to bundle them up in a single object.
And that's step one. It's really easy. And then step two is replace the whole model with every change. Whenever an event happens, like an action that might change or update the state, we always want to generate this brand new state,
this fully formed state that represents the next frame of content in your movie. So how will we do that? Well, here we've got this hypothetical action inside of our controller where we're going to set the password text. And here's how we might have done it before. We're going to overwrite that password property on the model. And instead, what we're going to do
is we're going to replace, we're going to set the whole model with this new object that we're creating, this new password form object, which is going to encapsulate the entire state. Now, this may strike you as a little odd at first, to use the new keyword
to create plain old JavaScript objects in the middle of our component. And it's entirely possible that some of you may have never actually even seen this in an Ember application before. It is a small bit of heresy, but one that I think pays off big, as we'll see pretty shortly. Because now, that's it.
Our component is now a movie. Following those two steps, that's really all there is to it. But sometimes, the tiniest shifts in thinking can open the door to entire new avenues of possibility. And one of these that presented itself immediately was a path towards a much, much, much simpler runtime.
So, to review, we've extracted our model into this stream of states, comprised of static frames of content, and like a single frame of content in a movie, this content object is effectively a still life. It will never change, because change is represented by generating
another content object, not changing the old one. And so, because of that, these individual states, these contents objects, do not have to be observable themselves. They can be just plain old JavaScript objects. So, for example, logging just works. I had a lot of problems with the JavaScript console.
It wasn't a terrible thing, but it was constantly a paper cut. You remember this monstrosity right here? What if that were to look something like this? That'd be pretty nice. And that's what you get from using a plain old JavaScript object. The same thing applies to tree structures. Those tree that was vexing me so completely
and that was just incomprehensible inside the console, well, now it's no problem. What about those other things that were kind of giving me those troubles, those computed properties? Well, it turns out you don't need those either. And you might be thinking, really? Because I know that's what I was thinking. I was thinking, really? Computed properties?
Those are like my favorite things. Well, yes and no. I'm only telling half the story, because it turns out that JavaScript already has the concept of computed properties. They're called getters. They've been a part of the spec for a really long time and most of the browsers support them. And because these static content objects
are never gonna change, you can pretty much just drop these in anywhere that you would have used a computed property before. And as you can see, they're really, really easy to look at. And they are completely and totally immune to all of the problems that I was just describing. There are no dependent keys, because the dependencies are just implicit to the computation.
And when something does go wrong, well, you get this, instead of this error being buried in three levels of chained observation, you just get this simple, synchronous stack that ends in your application code and not inside the framework. And so you get the stack, you get the logging, you get the simple properties, all because your browser deeply understands POJOs
in a way that it will never understand Ember object. Which means that you're speaking to it in its native language and it can help you out directly because it doesn't have to have talked to you through an interpreter like the Ember inspector. It's this enormous weight off. I wasn't even expecting this part to feel like your platform is really, really,
you know, pushing you forward. But whether you're using POJOs or not, because this part of it is optional, the real ace that you get when you split your system into player and content, the real ace is knowing with certainty what happened when and why.
Because you meant a fresh state every single time there's an event that causes a change. There's absolutely no doubt about what event gave rise to what state. They are effectively one to one. And what that means is that when you're trying to track down
this tricky sequencing problem, you know, what was formerly murky waters become crystal clear. In concrete terms, before, we had four events acting upon one object. But now that we're one to one, we have four events that are paired with four objects and it's very clear what happened and why.
We can just put our finger right on it and say, that's it. The second state up there, that is the first bad value. And what's more, it was the second event that caused that second state. And as a matter of fact, if we record each state as it happens, we can actually go back and examine it and see that first corrupt state
and look at it under a microscope and find out exactly what went wrong with it. It's just like having a bug suspended in amber. When we reconceive of that model as a stream of unchanging states and not just a chalkboard of properties, what we're really doing is preserving information.
We're preserving information about both sides of a state transition. The state before and the state after. It really makes it easier to make decisions. And not only that, but we're preserving information about the precise boundaries of an event and the implications that that event has
on the application state and the state that that event produces. In other words, the consequences of an event will never leak outside of that single, well-defined state. And it turns out in our line of work, information is really, really useful stuff. We shouldn't be intentionally destroying it
because the more of it we have, the more powerful we become by default. For example, undo-redo, that impossible dream, it actually becomes nearly trivial with an immutable system. And it's easy to see why. When you sequence your model like a movie,
then an undo operation is just a simple exercise in storing and rewinding the tape. And a redo is nothing but a fast-forward. That's it. And again, you're already doing this style of undo-redo in your Ember applications today
every time you use the browser's back and forward button with your Ember application. We're just taking the same concept and applying it to a state that's buried deeper within your application. And so undo-redo wasn't really part of my initial requirements, and because I wasn't even attempting it, I wasn't experiencing any pain for it associated with it.
But hey, you know, there's like a gold ingot in the condiment bar next to the pickles. I'm picking it up. Which brings us up to now. I feel really good about the way Ember is headed. And I feel confident that I'm going to be able to solve the class of problems
that I want to solve with it. And I really couldn't be happier as an Ember developer today. And that's it. That's the end of that narrative. But the story of Ember and immutability is really just beginning. There are so many more techniques and so many more tools
and so many more wonderful consequences to programming in this style that I didn't even get to talk about, because 30 minutes is such a short time. But exploring those things is something that I would love to do together with all of you in the time ahead, starting right after this talk. And all that will come eventually.
But my goal for today is that you can walk out of this room with two things. One is the philosophical underpinnings of an immutable UI architecture, aka the movie. And two, some concrete steps that you can use to get started with it
in your Ember applications today, because Ember is really ready for it right now. It works so well with Glimmer. To review your application, and your components are players of static frames of content, and to realize this, you first extract the model
into its own object, and then make a commitment to replacing that object with every change that you make. If you can do that, if you can do those two things, then you will set your fate on the path, the same path that I've been walking for a little while. And you will be ready to both understand and benefit
from all of the tools and techniques related to this that are coming in the future. Of course, it always helps to have some samples of this technique in action. We use it in our pagination library, as well as our library for doing reactive XML HTTP requests right in your handlebars templates. And finally,
I would like to thank my company, the front side, for supporting me in these explorations, especially my team, who had to suffer through my prattling about immutability for almost a year now, and who had to suffer through the trenches of grinding out and figuring out what the best way to get these things to work in your Ember applications was.
You guys are amazing. And finally, I would like to thank you, EmberConf and the Ember community at large. When I look at other communities out there, it really is hard for me to imagine myself being happier anywhere else. Thank you very much.