5 Secrets of Reactive Programming
Greg Heo's talk on 5 concepts in RxSwift and Reactive programming
All right. Hey everybody and welcome to Five Unbelievable Secrets Of Reactive Programming The Experts Don't Want You To Know. Fireworks. Thank you. The most horrible and the most fun slide that I've ever made. It's back.
I've been learning a little bit of reactive programming and looking into RxSwift and as I've been doing these things I've been finding that there are things about thinking in a reactive way that were very familiar. Ideas and concepts that came up that I already knew about, but maybe were more important in reactive programming. That's what I want to talk about today. I'm going to talk about reactive programming using RxSwift in the ReactiveX.io style. How many people are already using ReactiveCocoa or RxSwift or something like that in their day-to-day coding lives? Quite a few. If you look around and see who has their hands up, inevitably someone will ask me, "So who's using this in real life? Where's this used in production?”, You should ask those people who put their hands up
While I probably can't teach you RxSwift in 23 minutes, what I want to do instead is talk about some of the big picture concepts, thinking, design ideas behind it. I think that there are some design patterns behind reactive programming that can be useful to you even if you never touch RxSwift or reactive programming, and you go with OO or functional, or something like that. I think there are some really good common ideas, universal ideas that we can take from reactive programming and apply them elsewhere.
Let's get right to the first secret of reactive programming, which is that you already know it. I think reactive programming is very much a reconfiguring of ideas that we already know, rather than a complete clean sheet design of how to design our programs. The things that reactive programming is made up of, like data types, and sequences, and closures, and things like that, that's stuff that we know already and it's just a different way of using them and applying them when we're designing our programs.
Let's start and take a step back and say what is reactive programming? We have to go to the source of all truth, which is Wikipedia, and it tells us that reactive programming is all about data flows and propagation of change. What do we mean by that? If you think of your application as this box in the middle, and then you have stuff going in and then something happens within the application as it runs, and then stuff goes out. That's what I think of as data flow and the propagation of change happening in the middle.
What's a more concrete example? So we have our application, what are the inputs is the way I like to think about it. We have data coming in over the network maybe, we have user input, so people typing on the keyboard, taps, gestures, swipes, mouse clicks, all that stuff is the input stuff coming in through application. Then we think about what are the outputs? Then similarly we can have network requests, maybe a post request going out, showing an image on the screen, updating a label, things like that. Those are going to be the outputs. Thinking about it in that idea, how is the data flowing through my application? What is it doing along the way? Then what's going to come out the other end is the way I like to think about it of designing in the reactive style.
Now we're up to our second secret of reactive programming, which is everything is a sequence. We heard a lot about sequences already which was great. Let's take a quick refresher and take a step back and say what even is a sequence? If we look at the Swift standard library source code and we look at sequence it'll say a sequence is a type that provides sequential, iterated access to its elements. Which is a little bit of a cheat, because you're not supposed to use the word in its own definition. When we say sequence, what do we mean? We mean that it has a logical order. There is a first item, and a second item, and a third item, and subsequent items moving on. What do we mean when we say that it's iterated? Iteration refers to some kind of a repeated step. In this case it's, if you'll remember, the next method. The iterator protocol says you have to have a next method that returns the next element, and so we'll say, "Next, next, next, next" and so on, and we'll keep getting these elements. That's what a sequence is.
Then maybe it's also useful to talk about what a sequence is not. Sequence isn't indexable. Indexable? That's a funny word to say out loud. It cannot be accessed by index, whereas you have to use iteration to say, "Next, next, next", you can't just say, "Give me the 10th element" for example. You have to say next 10 times and work your way up to it. A sequence also only has a single direction. So you'd say, "Next, next", but there's no such thing as previous in there.
A sequence is also optionally unbounded. Get it, it's optional, so I put a question-mark on there. What do we mean by that? If you think about an array, maybe that's the most familiar kind of sequence, where an array has a start, it has an end, and it has a length. A sequence isn't necessarily like that, you can have an infinite sequence. Think a good example is usually a random number generator. You have a random number generator sequence, you say next, it gives you a random number, you say next, next, next, and you just keep using it until the end of time. Sequence can be bounded but it can also be unbounded.
Think a little bit closer about reactive design, let's look at our motivating example. It's a MacOS game that I wrote, it's a noughts and crosses game. Those of us from other parts of the world would call it tic-tac-toe. This one is a two player game, so there's no AI or anything like that. You take turns, you click, and then you say I'm going to play over here, I'm going to play over here, you keep going, and then the game is over when you either, all the squares are filled and it's a draw, or somebody gets the three in the row and they get this lovely win message. This is the application that we have, and I'm thinking about what are the inputs and what are the outputs? I think all right, the input is, because it's a MacOS game, clicks. People are going to click on the screen and that's going to be how they make their moves, play their turns. If this were iOS it would be taps or something like that, very similar though.
Then we think what are the outputs? I've got a couple of things in mind. One is to say what is the state of the game? So you click, you play a turn, and then there's going to be some result. Either the game is over, somebody won, maybe the game is over, it's a draw. Maybe that was an invalid move or that was a valid move. Every turn that somebody takes is going to result in a result. That's one kind of output. Then the second output I would say is the view itself, because when you click on a square it has to draw the X or draw the O. So the view itself is a kind of an output. We have to tell the view, "Well square number three, draw an O. Square number seven, draw an X." That's another kind of output that we're going to have.
We have all of these parts of the application, so we have our view controller for example, and it's going to get an X, Y coordinate, a point whenever somebody clicks. Remember, they're not just going to click once, but there's going to be a sequence, there's going to be several of these, an ongoing sequence of clicks and we're going to get an ongoing sequence of these points. Then I have the game controller, and the game controller has all of the game logic in there, it knows how to play the game and the rules. It says, "I have no idea what a mouse is. I don't know what a click is. I don't know what any of this stuff is. All I want you to tell me is where did people play their turn? They played square three, they played square seven, they played square zero. That's all I know. I just want a sequence, an incoming sequence of square indices."
Then we have our view, and the view says, "I don't know what this game is, I don't know what the rules are, I don't even know what the mouse click is either. All I want you to tell me is what moves you want me to draw. Just tell me square seven, draw an X, square zero, draw an O, and that's it, and I will handle it. I'm modeling that as something called a move." The view just says, "Just give me a sequence of moves and I will be able to draw things for you." We have sequences everywhere. They are floating out there in the ether, we have these boxes with arrows and they're all out there, and we need to pull them all together somehow to have a working, coherent application. How do we do that?
That will lead us to our third secret of reactive programming which is using small pieces of logic. What do I mean by that? The nice thing about modeling things as sequences is you have this natural sort of separation and you can say, "This sequence is over here, this one is over here, this one is over here." It helps you break up the application a little bit. Let's look at an example.
We'll look at this in here, the view controller gets points, X, Y coordinates coming in, but again only the view controller cares about that. Other parts of the application are interested in other things. How are we going to handle this? Here's my view controller, we'll look at some code, and by the magic of reactive extensions I have this property called rx_mouseClicks. If you're interested in how this actually works, the code for this game is available on GitHub, there'll be a link to it at the end and you can check it out. This will say, "Whenever there is a mouse click I will let you know what the coordinates are. I'll give you the point."
Again, it's not a single mouse click, but this is what they call an observable in RxSwift, RxCocoa. An observable is a sequence, so this will give me a sequence of mouse clicks. Of course it's a little bit different than something like an array, because it's asynchronist. An array is like, "Here are the values, let's go." Whereas this is like, "Well when there is a mouse click at some undetermined point in the future, I will provide you with the value." In your head you can think of it as a sequence, but the iteration is a little bit different because it's asynchronous. But regardless of that, it's called an observable, it's a kind of a sequence. What can we do with a sequence of stuff that we want to change to a sequence of other stuff? We can map over it.
The nice thing about the RxSwift is that if you map over an observable, you get another observable. What's going on here? If we look at the closure that we're using, it's going to take a point of type NSPoint, maybe you know it as CGPoint, same thing, and it's going to return an integer. The integer's going to be the square index. I have all of my squares like this, the three by three grid, they're labeled like this, it's going to take a point, and it's going to return an index from zero to eight. The logic in-between isn't so important. I have an array of each of these squares as a view and I'm basically hit testing the X, Y coordinate to see where they clicked. I'm transforming my sequence of mouse clicks, X, Y coordinates into a sequence of integers representing a square index.
So that's what we have. We have logic in the view controller, it produces a square index, and then you say, "Wow, what a coincidence, that's exactly what the game controller wants as input." With the magic of Keynote animation I can just smoosh them together and say let's connect them together. Of course, when you say, "How do we do that in code?" That takes a little different kind of magic. We saw a little bit of magic already with these asynchronist sequences called observables and then we need another piece of magic to actually again bring it all together into a working application with some logic to it.
How do we do that? That will lead us to our fourth secret of reactive programming which is doing things with a declarative style. What do I mean by that? The example I usually give is in terms of layout. If you do layout like a real programmer you'll say, "I'm going to override layout subviews." Right? Then you say, "I'm going to calculate all of the sizes and the positioning, and I'm just going to look at the screen size, and I'm going to set all these things, and that's how I'm going to do my layout." That, I would say, it maybe more of an imperative style. You're doing the calculation and you're saying, "Here is what all of the frames are going to be throughout my whole view hierarchy, which is fine."
Then in contrast you might have a declarative style, so I usually use a stack view as an example. With a stack view you say, "All right, I'd like a stack view, it's going to be over here. I have these four views that are over here." Then you tell the stack view, "I would like these four views to be stacked horizontally with this kind of padding and this kind of spacing, and go." You kind of describe what kind of layout you want, and then the stack view handles the little details for you.
With reactive programming I think of it very similarly, where we have all these sequences and these transforms and they're each performing a small piece, and we're declaring, whenever you have a point I would like to turn that into a integer in this way. We've declared how we want these things to go, and then if have enough of those and they glue enough of those lines and boxes together, then we're getting a working application is the theory.
Let's look at a little bit more at the app structure here. Okay, so we have X, Y coordinates coming into the view controller, that's going to turn into square indices which the game controller wants. Then my game controller will handle all of the game rules and it itself has two output observables. What are those? The first one is a result which we talked about. Every time you play a turn you're going to get a result, so it's going to say, "The game is over, somebody won. The game is over, it's a draw." It also has, "That was a successful move, or that was an invalid move." Then the view controller will be interested in that because it'll say, "Oh, I need to show a message. I need to change an icon." Or something like that.
Then the second observable I have is that sequence of moves. It's going to say, "All right, if there was a valid move, and I need to update the view, then I'm going to create a move." It's a strut and it just has the square index and which player. I can somehow push that into the view and then the view will be interested in that and it'll say, "Great, I know how to update myself and draw those Xs and Os on the playing field.
Let's look at a little bit more code. We have our game controller, and I have this result observable property on it. Again, this is an observable, it's a sequence, and it's going to produce some kind of a value. Before you saw how we could map over it, that was a transform operation to turn a sequence of X into a sequence of Y. In this case I'm going to have a subscription. This is like the iteration now. I'm going to say, "Whenever there's a new value available, because it's asynchronous, here is a closure that you can call when there's a value available."
You can see I'm passing a closure into subscribe and it's going to be passing as the first argument, the result itself. So it's going to pass in the value to me. I'm thinking, "What do I want to do?" The results in this case I've just modeled as an enumeration, so I can just switch over it and say, "If there's a winner, that's great. Show a nice win dialogue. If the game is over because a draw then that's okay, show another dialogue." Then success and invalid in my current implementation don't do anything, but you can imagine if you had a little status bar to say who's turn it is, or if it's an invalid move you can show a mean message saying, "That square's already taken." Or something like that. Or, "Don't click out of bounds." So you can have different cases for that.
We've encapsulated the logic for what happens after somebody takes a turn into this little piece of code. It's, again, not quite a map transform, but it's similar. We're saying, "Whenever someone has taken a turn, what do we want the result of that to be? Then that's right here in the logic.
Their second observable, that's the move observable. It's going to update the view, we have something similar. We have the game controller, we have this move observable property on it, we're going to subscribe to it and say, "Wherever there's been a valid move I'd like to know." What are we going to do? It's going to pass in the move to us. Then all we need to do is update the view. I have an array of these square views, zero, one, two, three, four, five, six, seven, eight. I'm going to access it by index and say, "Which square was just played?" I'm going to set its state to whoever the player is. That'll be nought or cross and it'll draw the appropriate thing inside the square, and that's it.
Again, we've encapsulated the logic of saying a successful move was made, we have this two way split. In this case we're saying there was a successful move, here's what it is, and what do we want to do with it? We want to update the view. We've handled the logic to move from clicking to turning into an index, to saying whether it was valid or not, and if it was this will now update the view. We've sort of declared how we want things to move through the application, and with small pieces of logic like this. Then we're all set, we hope. We have our working application going again from clicks, to indices, and then we have this split that we also have. Once the game controller's finished with its logic we can have those two results out there.
We have that all set up and we think it works, but we're not sure, and that'll lead us to the fifth and final secret of reactive programming, which is the importance of testable code. A lot of times of course in the true spirit of software engineering we have done the test after we finish the application. When you're writing test you're thinking about the complexity of UI test. "How am I going to test this? Do I have to write UI test? Do I have to find X, Y coordinates to simulate clicks? It's so tightly coupled, I should have used dependency injection, how am I going to mock these things?" You have all these things running through your head.
How are we going to solve this problem? We can have a look. Maybe the changing X, Y coordinates into squares, maybe we don't need to test that, I'm not so interested in that. I am interested in testing the game logic, because I want to make sure I've covered all the conditions of if I get three in a row does that indeed mean that somebody won? Does it detect the draws properly? Does it detect invalid moves properly? And so on. This is the part I want to test. I want to say, "All right, I've got my game controller under tests." It expects this sequence, this observable of integers which are square indices. It's going to produce a sequence, an observable of these result struts or enumerations that I've set up.
You're thinking, "Okay, well if only I could push in a sequence of integers and then it would produce a sequence of results and I could compare it or something like that." You're thinking to yourself if only there was some way to mock a sequential, iterable, sequence of integers and then maybe someone has a light bulb going on over their head and saying, "That's an array, of course I know how to do it." So you say, "Great. I'm going to pretend to play the game. I'm going to make a simple array of integers and I'll say I'm going to play square one, I'm going to play square three, I'm going to play square 52," which should be invalid and we hope that won't actually register, "And then I'm going to play square four."
Since arrays are sequences I can just sort of push this through into the game controller and simulate a game that's playing and say, "Here are the moves that I'm pretending to make. I'm going to push it through, no UI code required, and it should give me another output array of these results." You can see we have success, so the player cross goes first, so it's a success. Next player is player nought. The next move, it's a success, the next player is cross. Third move is invalid because remember 52 is not a valid square, so the next player is still cross, and then the final move is success and so we're all done.
Then you can imagine since I have invented this array of the input moves, I should also know what the output is which means it should be very easy for me in a test to just say XCTAssertEqual, what I got with what I expected and that should be it, I should have a test. So you can see if you have things broken up into these small pieces of logic, and that they expect something like a sequence, which we already know how to mock as arrays, then it becomes very easy to test each individual piece if we're interested in testing them.
We can take a step back, reorient ourselves, and think about where we started from and where we are now. What did we talk about? We talked about the big ideas of designing your apps in a sort of reactive programming style, which is thinking in terms of data flows, inputs, and outputs, and what happens in the middle. Propagation of change. What am I going to do along the way to sort of push the data through? Thinking of that as a design principle saying sort of like the internet, a series of tubes and saying, "I have an input, I'm going to connect it to this joint, to this other thing, to this other thing, and then I have it all hooked up and then I just pour water through the tube and I make sure it goes all the way through." Thinking about data flows, propagation of change is I think is a really interesting way to think of your applications, especially UI based ones where we don't normally think of events like clicks, and taps, and gestures in this way, but thinking of them as sequences I think is pretty cool.
Then we also talked about the five secrets. Again, you already know it. I don't think there's anything too revolutionary here. Maybe there's some fancy words like subscription and observable, and granted there are a lot more if you go into RxSwift a little bit more. In general the ideas behind it of modeling things as sequences and having maps and subscriptions and closures, they're all things that are very familiar which I think makes it very easy to understand, at least at first. Then getting used to thinking in that style can be a bit of a challenge. A little bit of a learning curve. I think it's not too bad. The pieces it's made up of are very familiar.
Modeling things as sequences I think is pretty cool, again. Inputs I think traditionally not modeled as sequences. Things like network requests are often thought of in that way as asynchronist's requests that come in once ina while, but I like the idea of setting up even user input and things like that, and even outputs as sequences. We looked at the idea of having small pieces of logic. Again, I don't think this is just unique to reactive programming, but having these sequences and then having things that handle the sequences like maps to do the transform work, it helps you think about your app in smaller pieces and helps the logic be encapsulated in small pieces like that.
Along the way the declarative style. Again, having these subscriptions handle. One kind of input to have one kind of thing on the output side, and then setting up all of these subscriptions and all of these maps, and buying things, and all of that, and then you can just say, "Okay, run the application." And go. Again, seeing the data flow through the series of tubes. Finally we get nice, testable code, because each individual piece has its input a sequence, and output a sequence, and then it's like we already know how to handle that. We don't have to worry too much about UI tests unless you want to, and becomes very testable, even after the fact, as we saw.
I think programming is like a big landscape of stuff out there, and there are many different techniques and ways to design things. Then thinking about object oriented or functional, or logic based programming. It's like looking at the landscape of programming through a different frame or a different lens. I think reactive programming is a really useful thing to do as well. I do think it's all just one sort of big landscape of programming. In our day-to-day lives we mix and match. We're not like 100% functional, or 100% object oriented, but we need to know different ways of looking at it which is why I like learning about reactive programming and RxSwift because I think it is a new window on stuff that I already knew, as well as some new vocabulary and some new fancy words.
Maybe you're interested in this, there's some universal ideas here. Oops, that's the Australian version of the slide. Maybe you're interested in this and you're thinking, "Yeah, I like these concepts, these are kind of cool. I like tests, I like this declarative approach, I like sequences, who's doesn't like iteration and fake asynchronist iteration?" I think again a lot of these things are universal ideas that we can apply even if from this day forward you never touch RxSwift although I encourage you to, I hope that these ideas are still universal and things that you can take with you on your object oriented journeys.
Blog post and links:
So you're interested, you want to learn more, you can check out this fine website, this fine publication. Just before I started speaking I pushed the button to publish a blog post which is a summary of what I just talked about. At the bottom of the post there's some links to the GitHub project if you want to play noughts and crosses with yourself. Also, some links to some other tutorials and things like that about RxSwift if you're interested. That is also the place to find contact information if you have any feedback for me. At the place where I work they like to say, "Feedback is a gift." Which I really like. I really do take that seriously, so if you have any suggestions or things that you liked or didn't like, then you can always find a way to get in touch with me.
That's all I got, thank you very much.
If you enjoyed this talk, you can find more info: