Sessions is temporarily moving to YouTube, check out all our new videos here.

Pure and Simple

Tom Davies speaking at React Native London in July, 2017
78Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

Introducing Copal: examining the reasons behind moving from Redux to creating a new framework. We'll take Redux apart and put it back together again, bringing a better understanding of what it does in the process.


Transcript


Pure and simple: Why? I had to come up with a name and this is as good as any. All right, so I'm Tom Davies, that's my Twitter if you wanna follow me or whatever. Don't say much on it, but that's how you can sort of keep in touch if you want. So I've been at this a long time. I started doing web development years ago, and I've done loads of things since. I've worked quite a lot in sort of video on demand, on set-top boxes and things like that. And a lot of Flash, but I'm rehabilitated, so ... So ... Last time I ... So I made this. This is my sort of business, as it were. Currently freelancing again, but I've made this as my startup. It's basically an app that, built in React Native, runs across devices and allows you to find events in your area. So if you're stuck for something to do, you can have a look and it'll tell you what's on tonight. I also made this quite a while ago. It's a first-person version of Pac-Man that's quite scary. Uh, yeah. There's a VR version out there somewhere as well. Anyway, what is this talk about? So I'm gonna talk about very geeky things. So this is sort of my journey into FP, so moving into functional programming from doing a lot of OOP, and what I've learned on that journey, and where I'm at now. So ... I'm not ... There's a lot of opinion here, but it's more about asking questions, and thinking about things and sort of to get everyone thinking about how things ... Rather than just that's the way you do it, pick this up, considering things, okay? And there might be some answers there. But, well ... Warning! So, there's criticism of things in here I don't fully understand. I'm gonna ... Some of these things are gonna be wrong, you know. It's always the way. Unsubstantiated opinions. Untested ideas and frameworks, definitely loads of those. Use of cats. Yes, well we'll see those later. Right, so ... I'm gonna sort of try and summarise a lot of this stuff before we get into the more complicated stuff, so see how we go. So reducing functional programming down to its basics. I'm not a functional programmer. I'm still learning. But this is kind of how I see it. So I'm sure people will correct me on this. So this is a quote that I quite like: You're building your own maze, in a way, and you might just get lost in it. So when you're programming, you're you're kind of inventing your own prison, as it were. The more you write, the more complicated it gets, and I'm sure everyone's been lost in that maze at some point, wandering around. So OOP, this is how I see it. You have each object or whatever, each part of your system. It encapsulates the state, and that state is spread throughout your entire programme. And then all these objects talk to each other. The less they do that, the better architected your system is, but they always have to, at some way, talk to each other. Problem with this is the more you add to it, the more connections you're gonna get. So the more ... If you add another node here, it kinda gets more exponentially more complicated, because you've got more and more connections, and things have to be more and more connected. So functional programming's more like this. You have your data in the middle, which is this white thing, and you just pass it down the chain. And each function, as it were, manipulates that data and then passes it on to the next one. So what you're ending up with is something that's sort of a ... It's more linear, so rather than increasing ... The more stuff you add, it's not making it exponentially more complicated, you're kind of controlling that and making it ... It still increases in complexity, but not at the same level. So I kinda ... This is how I think about it. It makes your maze 2D, not 3D, because you're limiting yourself. You're kinda cutting out whole ways of doing things. You're simplifying your maze. You can't go up anymore, you can only go in 2D. So you could say it's squared rather than cubed. I don't know. So the core of functional programming is this concept of a pure function. So what is a pure function? I told you there'd be cats. So pure function is deterministic. What you put in is what ... determines what comes out. It's predictable, and therefore it's easy to understand. It must return something. If your functions don't return something, you know that they're doing something, otherwise there'd by no ... They're doing something bad, or there'd be no point in calling it. There'd be no point in it existing, so it must be doing something. And you see a lot of OOP is functions that don't return something. Don't mutate your inputs. Those things that're going in: don't change them. And this is where the whole concept of immutability in functional programming, I think it comes out of this concept of purity. So producing no side effects. They're not calling off to other parts of your system. They're not starting async stuff. They're not doing all those nasty things that you can do. And this makes them really easy to test. So you can literally write a test that says calls that function, what goes in goes in, and what comes out is predictable. So you don't have to have any complicated async way of testing everything, it just becomes what goes in, what comes out, so. Right, so these are the side effects. So everything else in functional programming is side effects, and then network calls, timers, user input, date/time, random number generators, anything that's non-deterministic, that interacts with the real world. And you can't write a programme without these. You can't write a programme just in this world, because it doesn't do anything. You're always gonna have something this ... your program's gonna do. The key is maximising this. Making everything pure and simple, basically, where possible, and pushing all these right to the edge of your application. So React. So React can be functional. It's very good at being functional. So if you avoid state, that's the key to it, wherever possible, and that's not always possible, as I'm sure everyone knows, if they try to use stateless components everywhere. You always run into trouble, especially if you're using Airbnb maps. Move all the state to the top. So rather than having state in your components spread through your app, just push it all up to the top. That's the simplest way to do it. If you do that, it makes it easier for different parts of your app to interact. So ... that's often unpredictable. So you might say, "I'm gonna put state in this menu," and it'll slide out and slide in. But then you find, when you click somewhere else in your app, you need to close that menu. But if you've encapsulated that state down here, you can't talk to it easily, and if you do, you're starting to wire your app up in an OOP way. So if you push that state up to the top, then it's kinda global, as it were, and everything can easily interact with anything else. And if you get it right, your view becomes just a function. So you can say, "Here's your app's state," and it will render. So we'll go to Redux, yeah? Has ev-- Who hasn't used Redux? Oh, that's not too bad. Okay. Good, 'cause this ... Redux is a whole thing in itself, but basically, what it does is it is a single way of ... It's a single state for your entire application. You're state exists in this store, and you render it within your view, okay? So this is the React part, or the React Native part. This is where all your state for the whole thing. You push the state into view and say, "Render this," basically. When someone interacts, so they click on something or something like that, what we do is we create an action creator. That can create side effects. And it creates these action objects, which are like instructions to tell Redux how to update. And they're passed into the store. And you have pure functions in there, called reducers, that pick up those actions and say, "Oh well, someone clicked this button. "I'm gonna update the state like this." So this is the functional part of of Redux. This bit can be functional, which we'll get onto. This bit isn't. It is to an extent, but generally it's not. I don't know if I need to go into any more detail on that. MVC: this is your view, this is your model, this is your controller. Sorry, I come from the I'm old school; I've seen this a million times. The only real change here is this: how you manage your model. But it's just the same thing with a different name. This is MVC. So ... These are all the things that you have in Redux: smart components, React Routers. Has anyone used React Router, 'cause maybe this ... Good; some people have, 'cause React Native users might not understand this. I've put it in there because I think it's important, but ... Also, this idea of smart components. Actions and reducers. And you have pattern matching in your reducers, which is the big switch statements you'll see. Action creators, and your middleware for dealing with side effects like thunks, and sagas, and all sorts. So ... How do I explain this right? So ... Sometimes, say you've got a website, and you got to a URL, but you are logged out, so you can't go to that URL, and it's gonna send you somewhere else to log in, okay? This is a fairly common thing. So ... In ... If ... React Router basically determines what the view looks like, the state of React from the URL. And so what happens is comes up on the screen, it says, "Right, this is what we're gonna render," and your React tree renders. These smart components then start going, "Oh okay, well I need to be ... " They check names and say, "Oh well, they need to be logged in in this state." So they come back and they adjust the router, and then re-render the tree. I really struggle how to explain this in a simple way, but basically, your state ... Your ... Your app's being controlled by the state of your view. There's a thing called Redux First Router. I don't know if anyone's come across this. If you haven't, it's worth a look at. Basically the router can ... You just route straight to actions. So the action can say, "Am I logged in or not? Yes, I am," and then it can update the store accordingly, and the store can update the view, okay? It's an important by subtle difference. It means that your your view can be simplified. So if you use React Router, your view is driving the application flow. It's telling the application what to do next, and that can be quite complicated, so things can appear in your view, and they can launch side effects, which can then change the route, which can make more things appear in your view, and it can be quite hard to work out what's going on. So the flow's non-explicit. Can't quite see. So the view has state not in the store. This is an interesting one, because your ... the router is saying how, or what page I'm gonna show. That's not in the store, so how do you tell what page you're on just from the data? You can't. So all you really need is a way for you to wire your URL up to something to change the state, and then render from the state. So the other thing that Redux ... And I don't know, does any one not use smart components with Redux? No; everyone does, okay. So I'm kinda saying, "Don't." So smart components, all, what they do is they stop you having to pass your state down, every step down the React tree and dispatch down that ... down the tree. And they do that by using what's called context in React, which is like kinda magical global state for React. But what they do is they wire your view to the store in Redux. And what I mean by that is imagine the next big thing comes along, and you've got your React out there, but you don't wanna use Redux, you wanna use something else. You can't, because Redux is bound into a view, so if you can make your view a single function that takes state and has callbacks, that view can then be ported to any state management system. It's not ... It's not wired into Redux. So ... Another downside is they introduced stateful components where there could be none, and that's because of the way they connect. They changed the props in that component, and if you don't use them, if you just pass everything in from the top, all the way down the tree, you don't need this. You could do away with having a stateful component there. And so lastly, and probably most importantly, is they break the purity. Rather than having a view that accepts state and renders, you don't have that anymore, because it's all wired out all through it. You don't have a single function you can call and say ... Say if you were debugging, you could say, "Right, I've got a really weird bug, "where it only happens in this state," and then I can take that state, and just put it in my view, and see what happens. If you go down this route, it becomes ... you can still do that, but it's not as simple. Right, so yeah, we've got this guy again. So what is Redux? So ... These things are commonly used with Redux, and I'm saying, "Well, perhaps we shouldn't be using them." All right. Actions and reducers, I explain this because not everyone's used Redux, but others know. You create an action, and an action is an object. It just has a type, and maybe some data, that's all. You say to your store, your Redux store, "Dispatch this," and that tells it to update. Here's our reducer, and it takes the current state of the app, and the action, which is this, and it just has a big switch statement in it, and it says ... Or if this is an ADD_TODO, then I'm gonna update the state like this, and you can see it's adding. This is taken straight from their docs. It's adding a new todo, adding the text from the action, saying it's completed false, and then returning that state. So this is nice. This is all pure, but it's a bit cumbersome. So ... there's a lot of these. So what this is ... This is the best one I've seen. It's something called Redux-Act, and what it allows you to do is make the ... You've got ... It gives you certain tools and abilities. So, going to sleep. So we've got this thing called createAction, and we can create these actions and give them names. And then we could say createReducer, and here we're not pattern-matching, we just have each reducer function is just given increment/decrement add, which are these. And then, when we dispatch, we just call increment/decrement add. Has anyone seen this, or used this? Okay. 'Cause I hadn't, until recently, been doing my research. So basically, it allows you to simplify things. You don't have a big case statement. You can just create one of these, make these, and off you go and you can dispatch stuff. It just makes things simpler, yeah? Thought it was quite cool. So actually, we can get away with a lot of this. We don't need pattern-matching. We don't need action creators, particularly. So this ... We've also got this async stuff, so. So async in Redux. And we always talk about it last, last time, actually. This is another of their example. So we have something that's calling reddit. Takes a reddit, and it returns a function that has dispatch. And the first thing it does, is it dispatches something to update the application, say, "We're requesting this reddit." So that then updates the state, which will update the view, and then it starts doing an async call, and when that comes back, it either finds an error, or it creates things. So ... It's this guy again, right? This ... Every time we've got this handler here, and we're dispatching, which means that it's side-effect-y. Okay, so ... is there some way we can get around this? Redux is taken from Elm, okay? Only Elm doesn't work like Redux, because it's actually pure. So in Elm, you have this thing called update, and this is your reducer in Redux. The difference is it can do something else. It doesn't just return the state, it returns these things called commands, and subs as well, which I'm not quite sure what they are, but they're basically the side effects. I came across this as well, which is Redux Loop. We did talk about it briefly last time, but this is sort of like an Elm-like approach inside Redux. You've got this thing that's called Cmd.run, and so inside your reducers, you can actually create your side effects and fire them. Now I still think that this is this guy, because this Cmd.run is still creating those side effects inside this function, so actually you've taken a nice pure function and made it like this. I might be missing something here, but I think it's a side effect. So ... Isn't this all more complicated, yeah? And couldn't we do ... Could we do this better? And there are quite a few people out there doing this quite differently. Has anyone come across this thing called HyperApp? No? Okay. I didn't think anyone would have. So this is really interesting. This is a HyperApp counter, and that's it. You say app, you give it the initial state, the view is a pure function that takes the state, well, it's not pure, but it's as good as we can get. It takes the state and the actions, and when you click, action.add is dispatched, and here are our actions that update our state. I think this is awesome, and I think they've done a very good job. So, it's super simple. It's really, compared to other things it's very simple. It's 1k, but it's not just like Redux. It has its own virtual DOM, and it's not React. This is completely its own bespoke thing. So no React Native support, which is critical for us. And the async is still sort of Redux-style. It's not quite, but it is. I mean, I'm not gonna go into how, but it's worth a look at. So the ... So ... This one's really good. So this is the Elm Architecture implemented in JavaScript. And again, it's very much the same. We've got this it's basically taken straight from Elm, so you have init, update, and view, which are your, sort of, Elm ... This is the three functions in Elm. But initialise, we don't have here. Update does this, but the key thing is, basically, these are your reducers. And then we can say we're just sort of dispatch signals taking a pure function and running it. And that's updating your model, and then that view re-renders. It's ... It's fairly simple and easy to use. Now this is the bit where it starts to get interesting, because it's doing async Elm-style. So each reducer not only takes the model and returns the new model, but it returns a list of side effects to be run. It's not actually running them, it's sleight of hand. It's saying, "Here's ... "This is gonna return a function "that will be run after the reducer." So that's the function here, so he's saying, "getRandomGifs," and we say, "Right, "here's the function we're gonna run, loadGifs," and this is the promise, yeah? So ... It's a trick, basically. We're saying, "This is gonna return a function, "but it's not actually gonna do anything." All it's gonna do is return a function that, at some point later, will be run. And that means that it's pure.Okay? It took me a while to get this, but this is the idea. There's things called tasks and promises, but this whole idea of you can push your side effects out to the sides of your application. So we can basically get ... We can make our reducers into actions, and they're still pure functions. They're updating state, but they're also saying what side effects are gonna happen. Right. I like that cat. So yeah, so they're pure functions. It makes everything easy to test, 'cause you can say, "When a user clicks here, I'm gonna fire this action." And that action is pure. You can say it's gonna take this state, and it's gonna update the state like this, and it's also gonna dispatch these side effects. And you can write a test for that, that works on that function. You don't have to do any difficult, async-style testing. So the ... This is what I used to build godo. I started down this road, and I got to this point, which was: how do we compose our actions? And this is when I started doing my own thing. And ... I this is the first time I've talked to anyone about this, so we'll see how it goes. So I started down that road, and I got so far, and I realised that, as things grew, I started running into another set of problems. And so when I started building my own thing, and I went down that road, and I have now created that as, like, its own framework that you can go and have a look at. I'm not saying it's the right way of doing things, but it is quite an interesting way of doing things. So why do we need another framework? Well, it's like a Elm-like architecture, so we're not tied to any frame, like many rendering technologies, so your React Native, React, but you could also use Preact, or anything, really, that takes a state and renders a view, and a bunch of handlers, it doesn't matter. So async is a first class problem, whereas Redux is un-opinionated about how it does that, I'm being more opinionated. And like Redux, there's not much to it. So what is actually new? Because we've looked at all these things, and they all do all those things, so ... "not much," is the honest answer. We just make actions composeable, and that means ... It gives us a sort of language so we can talk to our app at a higher level. So this is what my app would look like. So this is a counter. So ... although I'm not tying my app to anything, I have kind of like versions, and this is the Redux-- this is the React version. So we say copal, and the first it takes two arguments. One is the view, which is a function that has state and actions. We dispatch in an action like this, actions.decrement or .increment, as you do in HyperApp. And then we've got our actions here. Now they're not quite reducers, Because they're more complicated than that. They have side effects as well. But this is the bit where I've kind of gone off and done my own thing. So we have this weird setup here. So this is your action, it's an object, and when you map on it, you're saying this is your reducer function, so these are the reducers here. And we can ... Map is a way of saying update the state. Don't worry, I'm gonna explain this. So the reason we do this is so we can compose actions. So we've got a reddit app. It has a refresh button to load the reddit you're on. It has a button for selecting reddits, and also, when it initialises, it has to load the reddits that are selected. So here we've got refresh, that says it takes the action objects, and it maps, and it says, like, "Hey, we're gonna set the status to loading," which is just a reducer, yeah? And then we're gonna add an effect, which is loadReddit. But when we initialise, we're gonna say, "Okay, we'll set the initial state of the app, "but then we're gonna call refresh, "because it's the same thing," yeah? There's no point in writing this again, because we've already written it, so we can just reuse it. And when we select a reddit, we can say we'll set the selected reddit, so we'll change that, but then we'll just click refresh. So that's kind of what I'm aiming for: a higher-level conversation with your app, and the ability to just chain all these things together. So this chain is just call the function. It's just composing of functions. So everything's nice and pure. Just to prove that this is how it actually works. Does anyone know what Point Free is? No, okay, well it's kind of like ... So this this is the the dot-chained version, okay? So when you work with arrays, you dot-chain stuff, but you could also do that manually, if you will. You could do it with something like Ramda, which takes a point-free approach. I started doing this, but I found it very difficult to work with, and it just became a lot of sort of boilerplate code, but here's initialise, and it takes our state and our actions, and ... This should be effects; I've written it wrong, but never mind. So it takes a state and the list of effects to run. This is a list. And then when we refresh on it, we call refresh, so we're wrapping all of this, that comes out in refresh, which is another function. So we're composing these functions, and that's the chain. And then here we've got state, and we're saying here's the reducer, setLoadState, so we get the new state back, and here's the actions. We wanna take the actions in, and we wanna take loadReddit, so we're adding this to the action list, yeah? So the idea here is that we don't have to think about doing this, we can just say add an effect, and it will add one to the list. We don't have to say, "I want to change the state, "I want to inject the state into this thing, and this ... " and I have to do this same boilerplate code each time, I can just map. If I wanna change a state, I can just chain when I wanna add things, and I can just add effect. Right, so this is the architecture. It's almost like Redux, in a way, but what we've done is we've taken all ... parts of, which would normally ... parts of your code that would normally be here, in sort of your action creators, and we've made them pure. So we've pushed that into your store, as it were. And we've got our view and our side effects. So we're trying to squeeze them out to the sides, and the interesting thing is that when you start looking at your side effects here, they're just the same as your view. Like, they take ... This the same function. It takes state, and it takes actions, so actually this is all we've got. The view's just a different type of side effect that happens to run every time the state is updated, which is interesting. I'm not quite sure if that means anything yet, but it's just, it's a special side effect that's added every time you do anything, basically. So could you use this today? Well, sort of, yeah. It works, I mean, I'll show you in a minute. So things that I ... it doesn't do, and I haven't even touched: hot reloading, server side rendering. In theory, both of these are possible. I've got a question here about how to branch your code. So if you have something where in the store you might wanna do something in some cases, or in other cases you wanna keep that pure, and ... Like, the way you would do that in functional programming is using something like a a left and right, and a maybe, or an if/else, and these kind of things, and an either, I think they're called. And I'm not quite sure how to tackle that. I did it my own way, and I'm pretty sure that's wrong, so if anyone knows how to do those things properly ... How to deal with non-async side effects. I'm not sure this is an issue, but it might be. It would be nice to be able to read in, I was gonna say the size of the screen or something like that, but obviously that comes from the view, which is a side effect, so I'm not sure this is a problem. I haven't come across a use case where this is an issue. And how to make it typed, so. I quite like using TypeScript, 'cause I came from that sort of type background, and JavaScript just feels very loose. It would be very nice if we could have everything typed, and I managed to do that with godo, but I didn't manage to do it in a way that was generic, so I could say ... I basically had to define this is what the model ... This is the type that the model is, and then everything's typed, but if we could do that in a generic way, it would be awesome. So hopefully, someone may be saying that you're missing the point of Redux. And the big point of Redux is this time-travel debugging, 'cause that's why it was invented. You've got completely predictable state, and all the devtools. Right. There. So this is this is the todo app, running in Copal, so. Here we've got our our Copal app, and we're saying view and actions, okay? and then we just read into that. So our actions look like this, and they're very simple, 'cause it's a very simple app, and there's no need to chain reducers, or chain actions here, and there are no side effects. So basically, this is a one-to-one map to reducers, which makes things nice and easy. It doesn't really show this off very well, because because that's not where things get complicated. But there are reducers, and this is exactly the same as you would in a Redux app. What I'm doing is I'm exporting all of these functions, and then when I import them, I'm just importing them as star from actions, so that they all come in in a single object, yeah? You can use them, so just makes things nice and tidy. Okay, but we can add our todos ... One ... Oh, it's working. Can't actually see what I'm doing there. It's just like a load of rubbish 'Kay. Then we combine and complete. It's pretty boring, really. 'Kay. But ... is this gonna work? Yeah, we've got our Redux devtools. So ... So one of the big things about Redux is you've got time travel debugging, so we can just start stepping back through time and see what our app is doing. And we can see the the diffs, the state, and we can see the actions. So there we go, we've got our ADD_TODO, and we can see the first argument is that. So what I'm trying to show is, basically this is giving us all the benefits of Redux behind the scenes, but in a much simpler, easier-to-use way. So we can go all the way back and we can play through the whole thing. So we can also skip back to any particular state. Oh, and I can jump back. I haven't quite got skip to work, but skip's a bit more complicated. But so we've basically got exactly the same as ... It's somewhere in here. I think you can even see, like, the whole ... Oh yeah, you can see the charv, the estate, and all that, so Thought it's quite neat that you could actually take something that works completely different, and just wire it up to the Redux devtools, and you've got something that's doing the same as Redux, but with much less overhead. So I'm not creating any action objects, anything like that. These are all being created automatically. Right, and then we've got ... So ... I should have shared my screen, really. Which one is it? That one ... Did I shut the other one down? Yeah, so this ... I won't show you the counterexample, 'cause it's it's just so simple, but this is the reddit example. And we've just got a nonsense thing in here to show you that it handles areas as well. So we could go and hit Refresh, and it will reload. And we can also open the devtools, and we can see a whole history, so we can see these are what I did, so I hit Refresh and receive posts, refresh, receive posts, selected with the reddit. You can see all the changes. Basically, all the functionality you get with Redux, and less code. I'll just show you something, because this is where it does actually help. So we've got our reducers, exactly like you would in Redux, only there's no pattern matching, there's no need to switch. We've got our We've got our side effect here, which is below the reddit, that's it. We could, in theory, push some of this parsing of JSON into your actions, and I'm not really sure where it should live. This seems as good a place as any. And then, these are the actions, as I was showing you earlier. They're just chaining them together, so they're all calling refresh. So when you click refresh, it just calls this action directly. And I'll just who you in the view that what what we do here, so here's the state of the app selected reddits, the status of the app, where we would load in or we create an error, what reddits, that, you know, our reddits, if we've got any loaded. And we've got these are the actions, refresh and selectReddit, 'cause that's all the user can do. And then somewhere down here, we just ... Yeah, here you go, we say on change, selectReddit, and this is the value of that reddit, so ... Yeah, it's pretty easy to use, really. And this is all nice and pure, we'll ... Well, I say pure 'cause we're calling handlers. It's as pure as can be. But we try and keep things simple. That's it, basically. Thank you very much. How much time time did it take me to write? Not an awful lot of time. I'd ... It's not actually very complicated. It's only about ... I don't know. It's not a lot. Do you wanna see? It's not a huge amount of code. So I guess it took me a lot of time thinking about it, but most of that was done when I was writing godo. This is it, so that's all it is. There's not a lot to it, because all it is is something that updates the state, and there's nothing really complicated about it. Could be simpler. And then we've got this bit here that wires it into React. And most of that's to do with wiring it to the React uh, the Redux devtools, so it didn't take me too long. It was mostly ... It did take a while to evolve it from where I started, but that was done whilst I was building godo, so it was only ...