Scale By The Bay 2019: Tikhon Jelvis, What is Functional Reactive Programming?
Recording: Scale By The Bay 2019: Tikhon Jelvis, What is Functional Reactive Programming?
[Music] I'm going to be answering exactly this question what is functional reactive programming and I've actually seen this question asked over and over I've seen on the internet on horror on Stack Overflow I've talked to people in person and it's surprisingly hard to find an introductory resource that really does a good job of answering this question and this is a real shame because functional reactive programming is an absolutely beautiful idea and it's also one of the best answers I've seen for how functional programming can kind of maintain a functional style while solving a wider range of problems so what is functional reactive programming well the first step is just saying that functional reactive programming is an abstraction and in the true style of functional programming it is very abstract and this by itself explains why it's hard to find good introductory resources and why it's hard to build up an intuition for what functional reactive programming is the problem that I've noticed with people trying to teach a lot of abstract concepts and this includes not only functional reactive programming but also monads category theory just anything that we have trouble teaching one of the difficulties is that whenever we're operating with an abstraction that is not very concrete that doesn't directly map to ideas we're already used to we really need to present answers to two different questions we need to answer clearly what the abstraction is we need to make sure we define it and kind of give enough detail so that you can understand exactly what it is but the problem is that by itself it's very hard to link the details of what an abstraction is to how you would actually use it or how you think about it right because abstractions when they're paired down are just formal rules but humans can't necessarily think about formal rules without some additional context some understanding of what the abstraction is trying to accomplish and what the meaning of the abstraction is so apart from just formally defining functional reactive programming I also want to make sure to talk about why we care about it you know why is this abstraction in the infinite space of all possible abstractions interesting for us and why is this the one that we're looking at and what I've seen with a lot of introductory resources is that they forget to answer one or the other of these questions and so you either end up with a whole bunch of details of code or math that you can't link to any greater context or you end up with ideas about what you're trying to accomplish but no ideas of how you're actually accomplishing it and I think the place to start for understanding functional reactive programming is trying to understand what the components mean so what do we mean when we say reactive and these days reactive has become a bit of a buzzword so the first place I looked was dictionary.com and it told me that something is reactive if it tends to react unfortunately this definition is just completely useless and of course we can't really look up terms like this in the dictionary because reactive is in this specific context a bit of programming jargon so what do we mean by reactive well in the specific case of functional reactive programming I'm gonna say that reactive programming means two important things it means writing programs that can change over time so we're not just writing a computation that takes some input and provide some output we actually care about the behavior of our program over time and on top of this we care about how it interacts over time this means that our program might need to respond to events happening elsewhere in our code or it might actually need to respond to things happening outside of the program maybe even in the real world and if there is one summary of what functional reactive programming is all about it's time functional reactive programming is an abstraction for thinking about and programming with time now this leads us to the next question which is what does functional mean and I mean hell if I know it's really one of these words which has no clear concise definition and kind of more pointedly in the context of functional reactive programming nothing about the ideas here is specific to let's say the lambda calculus of programming with functions instead the important idea is that functional reactive programming is a functional style abstraction it's built along the same ideas that we like in functional programming and in particular there's three points that to me define what it means for an abstraction to be functional style the first one is that it's explicit all of these salient parts of the abstraction are defined in the abstraction itself we don't implicitly use features of the language to capture the core parts of the abstraction in the case of functional reactive programming what this means is that time and how things change over time are fundamental parts of the abstraction provided by FRP where in imperative programming we often work with time implicitly by relying on the current state of mutable variables right and so one of the big differences is just like functional programming makes connections between data in our code more explicit functional reactive programming makes time and how things change over time more explicit then the next step is that functional reactive programming is designed to be very composable we want to be able to write our reactive systems out of small reusable parts and we want to be able to build up a larger system out of these small parts and then finally functional reactive programming needs to be declarative we want some way to talk about behavior over time some way to talk about reactive programming without needing to specify all of the details of how it actually gets executed of how it actually happens the goal of the abstraction is to abstract over a lot of the specific details of what happens in the actual machine how we get information from the outside world how the information over time travels back and forth between our program and whatever it's actually doing so that's all great and this gives us an understanding of our goal with functional reactive programming but how do we actually accomplish this how do we go about doing this and the answer here is that functional reactive programming gives us specific abstractions for working with time varying values so in our code we're going to have a value that doesn't just represent let's say a 1 or a 2 but it represents a number that can change over time and the way we do this in a classical FRP is with two specific kinds of abstractions we have behaviors and a behavior of an int for example represents an int that can continuously change over time so specific examples that are easily expressed as behaviors are things like the mouth position so the mouth always has a position and while we're moving the position might be changing continuously it doesn't really make sense to think about a mouth moving as a series of discrete events I mean sure in the hardware there's some kind of pulling going on but that is very much an implementation detail and so if all we had were discrete events and we try to represent the mouse moving we would end up with an abstraction that is easy to use incorrectly and I learned this firsthand back when I was doing JavaScript for the first time because JavaScript gives us a mousemove event and then I wrote some code that just naively said oh you know when the mouse moves do something but the problem is when you move the mouse across the screen how many mouse move events are you going to get and the answer is it's purely implementation defined it's up to the browser and so inevitably if you try to use this mouse move event you're exposed to the details of how the browser decides to discretize the movement and so your code becomes very jerky very inconsistent but at the same time we still have a lot of elements that we want to work with in our domain that are discrete in nature so for example semantically when somebody presses a key or when somebody clicks the mouse button there's a discrete event that we want to call a mouse click or a key press so in order to capture that effectively we want to have these two different abstractions one the behavior for continuously changing things and then one a stream of events for things that change at points in time and another way to think about it is that behaviors let us directly express the state in our program as it changes over time while events let us describe a specific events that happen at specific points and the combination of these two lets us more explicitly express exactly the kind of logic that we would write in an imperative setting using mutable state and callbacks and event handlers so another really important idea in functional reactive programming is that we want a simple model for what a behavior and an event is so wylie behavior is implemented in some complicated way we can always semantically think about it as being a function from time to a value so again this isn't a realistic way to implement it but this gives us a powerful mental model for how behaviors behave for how they can be composed for what operations over e behaviors mean and I know I listed declarative as one of the points that were aiming for in functional reactive programming but the word declarative is another one of those weaselly words that has no clear meaning so in this specific case what I mean is that the core abstractions in FRP have clearly defined semantics that you can use to think about the abstraction and that's what I mean by declarative so behaviors are functions from time to a value and then events being discrete points on time you can just think of them as a list of time value pairs and so later on we'll see a bunch of operations where if you naively imagine behaviors and events as being modeled by functions and lists respectively you'll see how the semantics of those operations have to operate so now we've talked about behaviors and events and we've set the stage for functional reactive programming as programming with these two time varying abstractions well why do we want to do this at all so to me one of the biggest answers is that this is something that without any abstraction functional programming can't do very well and I certainly heard lots of comments about how you know functional programming can't do you eyes you have to go imperative you have to go object-oriented or maybe people love talking about how monads are some hack and that you know we have monads and means the whole idea of functional programming is broken right and functional reactive programming is one of the better answers i've seen for why that isn't true at least specifically for reactive programming so in general we really want to be able to answer this question can functional programming do something yeah we always want to say yes it can and in the case of functional reactive programming if somebody asks us hey can functional programming write code that actively interacts with the world yes it can so now that we've set the stage let's dive into some more concrete examples for how this actually plays out in the way a lot of functional reactive code and the frameworks that support it is organized usually follows this relatively simple pattern we have some series of inputs these are events and behaviors that correspond to external things in our world then the library gives us a wide set of combinators that let us combine events and behaviors we use those Combinator's to build up a model of the domain that we're working on so if there are a whole bunch of semantically meaningful constructs in in the coder writing we express them in terms of the inputs by using the Combinator's provided by the library and then once we have this kind of semantically rich model of our system in our code we take that and we feed it to the real world via a set of output functions and the inputs and the outputs are gonna vary depending on the domain in which we're programming our reactive code but the events the behaviors and the Combinator's can actually be the same regardless of what we're doing so what domains am i talking about well the inputs are a great place to look if we're writing UI code and I think today this is probably the number one use for functional reactive programming a lot of our inputs are driven by the user so for example we have a an input that is the behavior of the mouse position and we also have a stream of events that tells us when the mouse was clicked and the same thing happens for widgets we display on the screen that the user can interact with that the same thing happens for the keyboard and so ultimately all of the inputs to our UI come in the form of behaviors and events however there is nothing UI specific about functional reactive programming so if we were writing some kind of robotics code the inputs could be things like sensors so we can take a camera and actually wrap the output of a camera into a behavior of an image so we can always use that behavior to look at what the camera sees in our code and then another application that is very near and dear to my heart is simulation so for context I currently work on a target on our supply chain optimization team and one of the main things I've been focused on is simulation and in particular the kind of simulation where we want to model what our supply chain does over time and as it happens functional reactive programming is actually a very expressive way to write exactly this kind of simulation code so in the case of simulation the inputs are going to be parts of the simulation we get from the outside world from the configuration things like for example the schedule at which trucks arrive at a given location then once we have these inputs we want to use a bunch of Combinator's to build up values that make sense in our code and in for me the Combinator's were the main thing that got me interested or excited about functional reactive programming because here we have these simple functions that let us express relationships that back when I was writing JavaScript with event handlers in state I found very difficult to write so the first one that really amazed me was this Combinator called when and the idea is that we have some behavior that can either be true or false and then we want to take that behavior and filter events we get to only have events when that behavior is true so for example if we wanted to know when somebody does a click while holding the shift button well we would get the two input events the stream of clicks and a behavior of whether shift is held down or not and we would just say a shift click is when the shift key is held and somebody clicks and I think intuitively this is a great example of what it means for FRP to be declarative we don't have to have a bunch of fiddly logic that says what to do to figure out if it's a shift click we just define what it means to do a shift click and the other really powerful Combinator that I really love is just the good old fashioned map so let's say we have some kind of audio level going on and we want to warn the user if the audio gets too loud so if you go above a B we want to display yellow and if you go above a hundred we want to display red luckily functional programming is great at encoding exactly this kind of logical business rule so here's the simple function that does exactly what I was talking about so how do we go from this function to having a behavior that tells us what color we should display to the user well all we do is we take the audio level and we map the warning function over it and this is a simple thing and it comes up over and over in functional programming in the form of functor specifically and in the case of FRP being able to go from the raw inputs to these more semantically meaningful values is incredibly powerful and then finally we've used a bunch of Combinator's I mean the libraries provide dozens and dozens of these we've used these combinators and now we want to do some kind of output and again depending on the framework the outputs are going to differ based on what you're doing so if you're building a UI an output might take a behavior of images and draw those images to the screen if you're writing some kind of controller code for a robot the output might be a voltage and it might pass that voltage to the motor and when we're doing simulations the output would take the state of the simulation or take some events that happen in the simulation and calculate the observations and the metrics that we need from that simulation and specifically in the case of simulations I found this pattern is incredibly powerful in letting us very cleanly separate which parts of the simulation logic are actually affect the state and represent things that actually happen and which calculations are purely observations that we want to report and this made a big difference to keeping the simulation simple and maintainable ok so let's put it together in a slide a larger example to see how this plays out in practice and this is actually an exercise I've done repeatedly in a couple of different frameworks I want to implement a simple game of life now I won't go into the details of how the game works but the idea is that we start with some series of dots enabled and then there are simple mathematical rules for how these dots change over time so what I want to do is I want to build a system that has a grid to display the state of the game a button which lets us either pause or run the life the game of life and I also want the grid to be editable by a user so you should be able to click on parts of the grid and flip whether the cell is on or off so to do this the first step is we just need an implementation of the game of life and I'll just treat these as primitives so we have a way to make a blank grid we have a way to evolve the grid step by step this is the actual rules of the game of life and then finally we have the modify function which is what lets us change the cell at a particular location so given these domain pieces how can we tie them in to this UI that I described well we'll follow the same pattern I was talking about with inputs than Combinator's than outputs so the inputs well we have some stream of events that represent when we have the next frame in my code this is just a simple timer but if you were doing this in JavaScript and Haskell does compile to JavaScript so this is a variable option you could actually have exactly the same interface but under the hood rely on JavaScript requestanimationframe and the important idea there is that this gives us a very simple way to draw a clean abstraction layer between what causes the next frame to happen and what we do with each frame that we get then the other inputs we get are again you know the the mouse position and when the user clicks the mouse and then since we've defined this pause button we also get a series of events that fires off each time somebody presses the pause button so what can we do with these events well the most powerful thing that we can do is we can fold events into behaviors and this essentially lets us take a bunch of events and turn them into state the simplest example of this in the context of this life is that we want to keep track of whether the game is paused or not and the way we do this is we say okay we'll start with the game paused it'll start at true then each time I get an event from the pause button each time the user clicks it I'm just gonna flip true to false or false to true and this by itself now gives us this purely semantic behavior called paused which tells us is the game running or not and again the point of the abstraction is that whether the game is running or not is not tied directly to where the controls for that come from this is part of the model of our code and then we do something very similar to deal with modifies but this actually lets us use a pattern which is very powerful not only in functional reactive programming but in all kinds of reactive programming and that is instead of starting with a state we start with a stream of updates so each event in this stream is a diff to the state in the case of modify we'll take every single click we get and we'll create a function that will take thee we'll take some life grid and flip just the point that was collect and so we have a stream of functions that update the grid then for each step we do exactly the same thing we take every single frame and then for that frame we return the function that advances the game by one generation and this function also has the type grid - grid and then in order to support the pause button all we need to do is add a clause that says hey only give me these events when we're not paused so if you read this left-to-right I know there's a little bit of extra plumbing but if you just read the semantic parts of the code we're literally saying the steps in our game are when we're not paused do a step on every frame so again it's very declarative and then finally in order to combine all of these together well we can just take two streams of events and union them and so now we have a single stream that represents every single change we want to do to our game and okay and then finally we just take this stream and we turn it into a behavior that represents the actual value of the grid by again folding it into a behavior now the reason this is such a powerful pattern is that it decouples the state itself from the set of update that we want to do and it makes it very easy for us to source updates from different places put them in the right order and only execute them once they're in the right order and then finally once we have this behavior we need a factored into the UI so the basic thing is we actually just draw the game of life you know we turn it into an image and we draw it on some canvas and then another thing I like is that a lot of the other UI elements can be expressed in a similarly high level way so in the pause button right when you press the pause button I want to switch the icon from play to pause and back and the way I do this is I just define a function that says hey if I'm paused show the pause pause symbol and if I'm running show the running symbol and then I just map that over exactly the same paused behavior that I earlier used to control the game itself and this by itself this relatively small amount of code actually lets us encode a pretty large number of features you know we have a game with animation with a pause button and with user control with I think it's like if you do this in a real framework it would be a little more verbose but it's on the order of like 20 lines of code to manage all of this interaction and the other really cool thing that I found doing FRP and practice is that it makes it very easy to add new features without significantly changing existing code so if I wanted to add a generation counter that counts how many times we've advanced the game all I have to do is take the steps behaviors steps stream that we defined earlier and fold it starting from zero and just adding one each time we take a step and because steps itself is already defined in terms of whether we're paused or not this generation counter will only update when the game is actually running and so when we display it it'll work correctly with our pause button and then the pattern I mentioned with these streams of changes also makes it very easy to add new controls so if you want a button that is gonna clear the grid when you press it all we do is we take the button we tell it okay each time somebody presses I want a stream of grid to grid functions that ignores the existing grid and just clears it that's clear grid and then once I have this stream I just add it to the Union of changes and now I have a new control without needing to change anything that wasn't directly related to this clear functionality and that I think to me really captures why I like FRP so much and I did to be honest I lied several awkward bits in practice but this should be enough to get you started in programming and so finally you know what is FRP looking back well functional is a bit misleading so let's call it it's functional style programming that we can do over time and if that's the only thing you remember about it I think that's enough to get started learning just by looking at a framework okay and I don't think we actually have time for questions yeah so [Applause] [Music]