Keep Calm And Curry On

Andreas Møller speaking at London Node User Group in November, 2016
28Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

In this talk we will look at the fundamental principles that underpin the functional programming paradigm. We introduce some basic techniques that make functional programming so powerful. These are techniques you can easily apply to your codebase to increase readability and reusability.


Transcript


We, myself, Andreas, we've got Stefano, sitting here as well, and James, our final speaker. We are very passionate JavaScript developers and very passionate functional programmers, and we wanted to do a talk about functional programming, but one of the issues that we often find is that it's really hard to cover an entire programming paradigm in twenty minutes. And it's hard to, sort of, give people an idea of why you would want to adopt this. So what we did instead was we strong-armed Omar to let us book the whole night up, and do three successive talks about functional programming where we sort of layer a little bit on top every time. So I'll start by talking a little bit about the fundamentals, what is it, some of the basic tools we use to, sort of, program in this paradigm. Then Stefano will talk about how we can use some of the functional programming patterns, and some of the tricks from functional programming, to solve some of the more common problems we have in, especially, JavaScript programming. And finally, James will show us how we put all these things together and build a micro service in a completely functional style that will power our, as we call it, TalkFinder 2000, which is a search service for all of our talks. As I said, we're all JavaScript developers and none of us actually come from a functional programming language. We've all tried them out, but we're original JavaScript developers. So for us it was more of a, "How do we make JavaScript work in this way, rather than trying to adapt it to a language we already knew?" And what I want to ask you to do is slightly suspend your disbelief if you're not familiar with functional programming. It can initially sound really weird and seem very counter intuitive, but as we build things on top of it, there's some things that will start to unlock and we'll start to see how actually quite a lot of benefits can come out of coding this way. So I want to start with a quote I really like. It's from John Carmack, who is the head developer on ID software. And if you don't know who he is you have not played <i>Commander Keen</i>, which is by far the best game. He also made <i>Doom</i>, and <i>Quake</i>, and these things, but he made <i>Commander Keen</i>, so here's a man who knows what he's talking about. So in the first talk we've got a three step plan for learning what functional programming is. First of all, we're going to figure out, basically, what are we talking about? Then we're going to spend some time building up a shiny toolbox of functions and tools we can use to build applications with. And finally, we're going to try and apply that to some more real world scenarios. So we start off very basic. Just before we go on, we're using ES6 syntax for our code. We kind of expect you to know most of that. If there's anything specific where you think that looks completely weird, just let us know and we'll explain it right away. So right here we see a function, and as functional programming states, it's really about programming functions. But more precisely it's about putting functions front and center, and making functions be the very core of how you build your applications. So we want to take functions and we want to combine them, we want to extend them, we want to build them in a way so we keep expanding our application. One of the things that's distinct from many other ways of programming is that there's a strict separation between what's a function and what is data. And in object-oriented programming data and functions live together in objects and functional data is kept apart. So in this case, when we're calling add, we say we apply the function add to the arguments one and two, that's just terminology. Which is quite distinct, different, from calling a method an object, we're passing in the data, we're getting out a result. The second thing we've done here is we've added what's called a type annotation. And this one is stolen from Haskell, so we just added it as a comment. And one of the top searches that the function add takes a number, and another number, and then returns a number. So, in this case, the thin air is just type annotation for fat air. And of course, three here is just the type number. And the reason why we want to talk about this, the reason why we want to bring in types is because they add a lot of information about the data we're working with. And we have a tendency in the JavaScript world to ignore the fact that there are types, because it's very loose, the type. We don't really have to talk about it, we don't really like talking about it. Because when we say type, we think, "Oh, Tiberius, or Java compiler or something like that." And the reality is that types are present everywhere. They are a part of programming whether we want to or not. A loosely-typed language doesn't mean you don't have to deal with types, it just means that you have to deal with them rather than a compiler. It means that eventually you will have to resolve it, even if right now you don't have to think of it, eventually you will have to think about it. We start out with a very useful tool called Compose. Now what Compose does, is it takes a bunch of functions, and then returns a function that'll recall them one at a time from right to the left. And it will pass the data into the first one, then the result of that will go into the next one, the result goes into the next, and so on and so forth. We've got an example here of a get URL query, which takes the first function that splits the string by a question mark, and then takes that result, which is an array of two strings, and turns it into the function last, and last just returns the last element of the array. So in this case we created a little, simple function in one line that just gets the query part of a URL string. We're can add a few more functions here. Merge the first one is just a shorthand for an object assigned that doesn't change any of the objects. So it does the same as normal object assign, but we're just not actually changing any of the originals. We're keeping them intact. From pairs is a very useful way of taking a list of key value pairs in an array, and then returning an object based on this with the keys and values. And finally in this case, split pairs just take a list of strings and splits each of them by equal signs. Now, once we have all these, we can then compose all these into our pass query function by calling them one at a time. We start by splitting into an array of key value pairs as strings. We split each of those strings into the key value pairs and finally pass that into our from pairs function that joins it all into objects. We start out with a string, we end up with an object and we can do it all in a nice little line here. And one of the things that are interesting to see here is that a lot of these are not really domain-specific functions. Merge, for example, doesn't know anything about its URL. It doesn't even know, it has no idea what it's being used for other than joining objects. And the same is true for from pairs. That means we are extremely reusable in our code, because it doesn't have to be used for your all pass, it can be used anywhere. The next function we want to add is called curry. It's a little bit complicated, we don't have to actually read it. I just put it up there for good measure. What it does is it takes a function and returns a new version of that that allows you to apply the arguments one at a time. So as an example, we can take split, which is basically just our string split operation, but if we call it with a separate on the string, so a two argument function, and then call that we can then partially apply this function, in this case. And what we do is we get back a function that already has the first argument applied. So this one is just waiting for us to give it a string so it can call the original function. And in this case, we get our little me and you variable that's just a list of the strings me and you. So this let's us use functions to create other functions by giving them some of the arguments they expect. And this is very useful, because one of the problems with our compose is that well, functions can only return one argument, so if we're going to compose them they can also only accept one. And we can use curry to get around that. So, once we start currying our argument functions we can start, sort of, creating new functions very simply, based on the original ones. These should all be fairly familiar. Map and reduce are quite common functions, split is what we just defined. And because we curried our reduced function, and because we ordered the arguments the way we do, we can actually partially apply that to, in this case, an essentially reversed merge. Just merge the previous accumulate as it calls on a reduce function with a key value pair and an empty initial object, and we can actually build our from pairs function just by passing two of the arguments to reduce. So the function from pairs just got a lot simpler, because it's just partially applying it to reduce. And of course, our pass query function we can rewrite again, much, much simpler now, because we don't have to extract and create all these specific functions, we can just partially apply existing functions. So we partially apply split to create the first function that splits by the ampersand. We then partially apply the map function, so we get a function that expects a list, which is what we get from split. And of course in this case, the function we map over is splitting by the equal signs. Finally, we pass that into our old friend from pairs, and we build up an object based on this. So, in the very same line we now get to pass a URL query, and I don't know if any of you have written those, or, even better, come across them in old code bases. They never look like this. They're never one line and they are never particularly simple to keep track of. And another very interesting thing from those years is the only function that has anything to do with queries or passing queries is the last one. All of the other ones are generic functions, and they are all in functional programming libraries. So I just defined them for you for ease, but we don't ever write these. We just get them from a library or definers. So extremely reusable code, because as you said, "Well, the only bit we needed to do was this combination of functions." Now, when we look at all this it kind of changes the way we see functions. Usually we're used to seeing it as a collection of statements to be executed. Which is true in most of the JavaScript we write every day, but in functional programming it actually looks slightly different. We start to see functions as a relation between an input value and an output value. We put something in, we get something out. And more specific, one input value and one output value. Because once we curry a function, what we've actually done is we've taken a function that took multiple arguments and created one that takes just one. Now the return value, for example if it takes two arguments originally, the return value is another function. But that's a perfectly valid thing to return from a function in JavaScript, so we create functions that only take one argument, and the reason why we pass them into curry is because sometimes you want to apply them to two, so it gets it a little bit easier rather than having to call them one at a time, we can just just give them multiple. So it's shorter, it becomes the normal way of calling functions, kind of becomes syntactic sugar, in this way of thinking. The other thing that we want to make sure is that our functions are stateless. What that means is we want to make sure that every time we call a function with a given argument, it has to return the same thing every time. Because once we start composing and building things on top of each other, and building things more complex, we have to be able to reason about the lower levels of our functions. The lower, the smallest bits of our components, and consistency is the most important part of this. So we want to make them stateless. And the final thing we need to consider is that we can't have them go and do all sorts of other things. They can return a value, and that's it. Because if we start having them call APIs and various things then what used to just be a little utility, reusable utility, now can have effects you can't quite consider or reason about in this context. So we want to keep this very minimal in what functions do. We keep them very simple, because that allows us to reuse them in ways we otherwise really couldn't. And of course the final thing that's real important here, is that functions are very composable. And that means we create more complicated functions, and finally applications, by composing them of smaller bits. Every step is a combination of previous steps. And therefore, we can drill down and find exactly what we're looking for. It's easy to find because we can walk down our little function tree to find exactly what we're looking for. So now that we've built a little bit of a library, now that we've got an idea of what it means to code in a functional way, let's take some examples of how we can use this. So I'm going to introduce two more generic functions. The first one is called prop. Initially it seems very, very useless, because all it does is access a property and an object. And that you can actually write in less code by just accessing the property of the object. But because we can curry it, it gives us some fantastic power. The second thing we do is we take sum, and again we can use our reduce function, partially apply it. So in this case, we use add, which just adds the two numbers, and a zero, and then reduce becomes a sum function. Because if you give it a list of numbers, it will accumulate over the total by adding the values together in each step and return the sum of the list of values. And even though normally a reduce function doesn't normally take up much space, it's very rare, you see, that they can be written this short and this concise. And the final thing is we want to build in this case, a hypothetical function called total price. And what it does is it takes a list of items from an imaginary shopping bag, and returns the total price of those. And the way we reason about composition in functional programming is we think about the types of data we have. So in this case, we know we need to get a total, we know we want sum in there somewhere. And we know in order to use sum we need to get a list of numbers. So our challenge is not to find the total, it's actually now, "How do I find a list of numbers?" And we know where we get the number from, that's the price property in our item. And we know how to get the price property, we partially apply property, and then we use map to basically translate that function from one that takes it from a single object into one that works on lists. So when we partially apply map, what we're actually doing is we're translating a single function on a single element into a list function that operates on lists. We can do this even further. We can say, "Let's introduce filter." I'm sure most of you have seen before as well, and we can compose that on the back of it, to say, "Well, what if we start by filtering only the things that are in stock?" And then we get the total of items in stock. And every single step we get to reuse what we've done, before because we keep it very clean. What we want this function to do, it only does that, there's no side effects, that means we can constantly reuse them in new ways. A final example I want to show is a little bit more complicated, just a little. And we are going to use it later, so it's a bit to help James out in his talk. We start out with the first index by, and what that does is, basically, take a list of objects and you give it a function that creates an index. So you see, our function if we look in the type signature, the first argument for the function is something that goes from A, could be anything, to a string. We then get a list of A's. Again, right now we don't know what they are, because this function doesn't really care what they are. And then finally, it would return an object with string parameters and A. So in this case, we create a function that defines how to build an index, we give it a list of something, and then essentially we get an index map out. So rather than going through the list to do a lookup, we can now do an index lookup in hatch map. Our inner join function does exactly what inner joins do in mySQL, or any SQL really, you give it a key for the first collection, a key for the second collection, and then returns the first collection where all the elements from the second one has been added in place of the key property of the first one. And we do that by creating first a lookup table of the rightmost, the Y's in this case we call them, a lookup table of the first one, and then we map over and add that in to the elements in the left one. And what this let's us do, is if we now go and fetch the list of talks and the list of speakers as we do later, we can simply create a function that combines them just by partially applying this inner join function with the keys we want them to look up. So this has sort of been an introduction to the simple things we do in functional programming. It's not as bad as it sounds. And as I said in the start, we want to slightly suspend disbelief. Some of this can seem a little bit strange, but as we start building things up it becomes familiar and then it becomes very simple. Guess you're going to have to trust us a little while for that one. Yes.