Transcript
So the name of this talk is just enough functional programming to be a danger to yourself and coworkers. And short story, since I'm condensing a 30 minute talk down to around 10 minutes -- but short story, don't introduce functional programming concepts the first week on a new job. You will freak out your coworkers. They'll be like... What the heck is this? No! And they will reject your PR immediately. Just saying. That's kind of how this got inspired. Who am I? I'm Kyle Shevlin, as it was mentioned before. Everyone needs to have my Twitter handle, of course. I'm pretty prolific on there, unfortunately, or fortunately, depending on your perspective. I'm a senior software engineer at Fastly, as I said, lover of JavaScript, hater of semicolons, maintainer of this glorious beard, and I host a new podcast that I'm gonna plug right here, called Second Career Devs. Some of you who know me know that I was a pastor before I became a software engineer. I'm just trying to find great stories out there and share them with the world. If you have great stories, let me know.
Today we're gonna cover some functional programming techniques. You've probably heard it's all the rage right now in JavaScript especially. And I want to give you just enough that you can actually start to delve into functional programming. And you can dry up your code bases, you can make them more easy to reason about, and hopefully be able to write code that's more easily tested, and just makes more sense. You'll start to understand as we go through this. But we need to cover four topics to be able to do the basics. And those topics are: Higher order functions, purity, currying and partial application. I promise you, currying is the most FP vernacular word in here. You will not be scared by functors and monads and applicatives and that kind of thing. But first... A function. Some of you might not know what functional programs is. I thought it would be important to define it. But functional programming is kind of a pain in the ass to define, it turns out. Because the formal definition is something like this: It's the style of building a structure and elements of computer programs that treats computation as evaluation of mathematical functions. Who's bored right now, just reading that? That's a technically correct definition, but it doesn't really tell you much, right? It's unfortunately like just very not helpful. And it's like... This is the metaphor I decided to come up with. It's not that different than telling you that the definition of painting is to apply paint to a surface. Because we can't define it based on a couple words. What we can see is that functional programming is easier to see. It's easier to read than it is to define. We're gonna define it by the characteristics that differentiate it. Pointilism is something we can define. Art comprised of points. And Impressionism. We can say it's different from Pointilism because these are broad brushstrokes. It isn't very detailed. We can tell the difference by sight. And we can do the same with functional programming and our code. It expresses things in statements, it avoids mutations, avoids side effects, and its logic is built through composing pure functions. I will teach you what a pure function is eventually. So this is some imperative code that you're seeing on the screen right now. Imperative code means that we write code such that we instruct the program or the computer what to do, line by line by line. In here, we create an empty array and run a for loop. If you learn functional programming, you will never have to write a for loop again in your life. You will never have a global i on the let line. Like, if you forget let, and now you have a global i sitting around, and you've had that bug. I hear a few laughs. Some people have had that bug before. You will never have that again in functional programming. We run a for loop and push every result into the results array. And apparently I have a typo there. So this program wouldn't work. And then we return it. That's imperative. You see on each line of the code we tell it what to do. You've probably heard this fancy word, declarative.
We declare what result we want from a program and we just let someone else deal with the implementation details. Someone out there wrote the map method on arrays and somehow it magically takes this function and gives us back a new array, right? This is declarative programming, and functional is a type of declarative programming. So you see here we have the same function. We give it an array and return the values one at a time, multiplied by 2. Functional would kind of look like this. We would have a couple functions. We would have a double function that takes a value and returns its double. We would have a map function which takes function and then returns a new function that takes an array. That's the key there. Returns a function. Returns an array and then we make our map over it. So that's the composition of passing double into map and then an array into that composed function. I know I just said a whole bunch of words. Let me teach you them. So let's start with higher order functions.
This actually makes up a lot of JavaScript, and you're probably doing this all the time without maybe necessarily thinking about the fact that you're using higher order functions. A higher order function just has to obey one of these two or both of these laws. It accepts a function as an argument or it returns a new function. So imagine having built an add function that takes an X and a Y and returns their summation. So a higher order function means that I can actually pass add functions into add functions. Into add functions into add functions. It can keep going down the stack. And JavaScript allows us to do this. This is actually pretty awesome. But in order to make the most use of this, we want to make sure that our functions are pure. This is where bugs fall in your code. All the time. Is when you write impure functions. A pure function is one that gives the same inputs and will always return the same output without any side effects, every time.
So that add function I was telling you about, right? We take an X and a Y and return an X and a Y. And you're like... Kyle, that's really boring. But how many times have you seen what seems to be a really simple function in your code and you have a mutation, like on the impure example? You're walking through a pull request and someone decides they have to mutate a state somewhere else and they're trying to do four things in the same function. Pure functions come down to something really simple and really easily tested. If you have this add function, you know immediately how to write a test for it. You would give it some data and expect the data out. It would always be the same. We need pure functions in order to do functional programming. And true functional programming languages like Haskell or some of the frontend languages coming out these days such as Elm and Reason, they will push you to do this all the time. You'll write everything in pure functions. Purity leads to easily testable functionality, as I suggested. But it also does this thing where it creates these trustworthy contacts between our functions.
And that's a fact that we can utilize throughout our application. And that leads us to what I call the meat and potatoes of functional programming and getting into it. It's currying and partial application. And I literally mean meat and potatoes to some degree. I love yellow curry. Anyone else a fan of curry here? So I'm talking about this guy. Haskell Brooks Curry. He's a super smart dude. And I'm not talking about the food, which is super delicious food. Right? Haskell Brooks Curry was this genius... His claim to fame is what's called combinatory logic and advancing mathematical logic in that way. I won't get into that. I don't have time for it. But his name is so important that it's become three different programming languages, including the most popular one you probably know -- Haskell. But this technique was named after him too. And currying is the technique of refactoring a function that normally accepts multiple arguments into one that accepts them one at a time. So the canonical curried function that everyone learns the first time is the add function. That's why I've been talking about it. So add normally takes an X and a Y, but if we make it a curried function, we're gonna use a higher order function -- I brought that word back -- and return a new function that waits for its second parameter. You get that? We get an X. Return a function that waits for the Y. When it gets the Y, it finally evaluates. It doesn't evaluate beforehand. And if you're already using ES6, you get to use arrow functions to write this, and it's really elegant. You become really good at writing argument, returns, argument, returns, argument, and writing out your curried functions. So why was that useful?
Well, it's useful because we can partially apply values to these functions and store them in closure. Closure is like that classic JavaScript interview question that all of us stumble on. But it just means it's stored in that state that we're waiting on to add our output. I have our add function from before. And I gave it its first value, that X. And now I've created a new function, an add 5 function that I can continue to reuse again and again and again throughout my application. And just keep logging out or adding new numbers to it, and I always get a summation. So currying is really powerful, but you also have to understand that the argument order in which you supply arguments to your curried functions really matters. And the general rule of thumb, as I like to describe it to people, is you want the most unstable argument to be passed last. How many are familiar with using array.map or array.filter or array.reduce? Go ahead. Okay. So most of you are pretty familiar with that. If you have... How many have used it with low dash? The regular low dash? In the regular low dash, what do you do? You supply the array first and then you supply the callback function, or if you're writing with a native method, you have an array and you dot it with the callback function. But that isn't the most useful order of functionality. Because I might reuse that function, that callback, again and again and again, but I might want to call on many different arrays. This is what functional programming and currying does. We flip the order. So rather than having an array and feeding it the callback, we have a callback and we wait to give it an array. Does that make sense?
So in this case, I've made a new filter function that's actually a curried version, and what I do is: The first argument it gets is the callback function, and then it returns a new function. And it's waiting for the array. So I make a filter, I give it that callback function. That's less than 10 -- is now a new function. And I have multiple arrays, and I can use the same filter and supply it the arrays, over and over and over again. What you can start to see is that functional programming is a really useful way to build up complex logic for manipulating data. And as you grow in your JavaScript or your frontend careers, that's most of what we do. We take data in. We just get JSON dumps all the time. We take data in and we spit data out back on the page. So I think that's really where the power of this starts to come in, is when we start to realize how we can compose and make these reusable functions throughout our applications. Which brings us to composition. Now, I promise you, this is probably the worst, the most boring part of it. It's gonna bring you back to high school, and if you're like me, you hated it so much and don't want to think about anything you learned or did then. Maybe some of you loved high school. I don't know. But I want you to think back, but not to the shitty parts. To the math parts. Which might have been shitty parts. I apologize.
But do you remember doing some math and you had an equation like f of x=y? Anyone remember that? So in math, all functions are pure functions. Right? They always give you one output for the input that you give. That's kind of nice. Unlike our programs at work where, like, I call a function and 8 things happen and I don't know why it's doing that, and now I have to spend an hour and a half banging my head against a wall trying to solve a problem, right? Math gives us pure functions. Functional programming demands that we use pure functions. As we said before, though, we can give functions a function, as an argument. And that's what composition is. So if you look at the bottom of this slide, F of g of x=y is a composition of a G function and fed into an F function. So in programming, we rarely name functions a single letter. If you do, stop. Just don't do it. I feel bad for your teammates, if you write functions that only have a single letter. But nesting functions can be really, really ugly.
And so I have this example here. I've made three curried functions at the top. They only take one argument, so I didn't really have to curry much. But they all take a string and they return something. The first one is a scream, the second is an exclaim, the third is a repeat, and I supply it a string. So what I get is I can create this result by composing these functions together, where I first scream at Bob, and then I exclaim at Bob, and then I repeat it at Bob, and if there's anyone named Bob here, I'm really sorry. But we yell at Bob twice that the world is coming to an end. But that could be really ugly, especially if you have really long, clear function names. Right? If you're following clean code principles, you might be like... If this does X, blah-blah-blah blah-blah... And you have a 40-character function name. Imagine trying to compose those. That would be a nightmare to try and read.
So... There's a great other way to do it. Instead of nesting functions, what if we use that... Excuse me. What if we use that currying and partial application that we learned to create new functions for us that would do the composition for us? So I'm gonna introduce you to what's called the compose function. And if you get into this, you're gonna be using compose all the time. It's a little hard to read sometimes the first time, so I want to walk you through it. Compose is a function that takes any number of functions as arguments. Any number. It creates an array out of them. We're using the REST parameter, if you're familiar with ES6. And it returns a new function that's waiting a value. In this case, I call it X. And what it does -- it does a reduce, but a reduce right. So we're gonna start at the end of the array and work our way to the front. The reason we do that, rather than left to right, is it's more mathematical. We want to work from the inside of our function out, rather than some weird outside-in composition. We take the last function, supply it our starting value, and that gets passed as the accumulator. And we return the value that's derived from firing that function with that value. And then it's fed into the next one and fed into the next one and fed into the next one. You following me? And eventually we get our end result.
So that's compose. If you ever come across the functions pipe or flow, that's the opposite. It's the same function, but going in the other order. Just want to make you aware, in case you do. So we can take this compose function, and I've already walked through all of this, apparently. The compose function takes a blah-blah-blah blah-blah. So let's take those functions that we made to yell at Bob before, and let's make a much easier to read function that can be reused throughout our application. In this case, we're gonna pretend we've imported all those functions, and we create a new function, warn adamantly, where we compose scream, exclaim, repeat. And yes, I've read that from bottom up. Because as I said, composition is from right to left. So that's one thing you want to get in your brain. It's from right to left. So we're first gonna scream, then we're gonna exclaim, then we're gonna repeat. And we supply it its string, and we have a much easier to read line, where this reads warn adamantly... Our string. Everyone agree that's easier to read than our nested version? So that's composition. You end up creating a bunch of functions that are really simple to reason about, and you build up bigger and greater complexity by composing and combining them and reusing them. You can see how this is really useful with array manipulation or object manipulation. And so I don't have time in a short talk to do any, like, live coding or show you how, so I made a repo that has some functional programming exercises. That you are welcome to go try. I heavily borrowed them from the Mostly Adequate Guide to Functional Programming. I made them slightly different and a little easier, but that is a great book that can introduce you to functional programming in JavaScript. And I actually have it here in my resources. So I recommend the Mostly Adequate Guide to Functional Programming, by Professor Frisbee, Dr. Boolean, AKA Brian Landsdorf. He has so many aliases on the internet. I don't know why. I recommend these two libraries, Ramda or low dash FP. They provide you with all these utility functions you're used to, but the arguments have been switched to be more functional. And it has a curried function. So you can start to autocurry all these functions and get used to using that. Also I recommend the YouTube channel Fun Fun Function. He has a number of functional programming tips. He's very hilarious. One that's probably the quintessential one to watch is one called composition versus inheritance, where you get to learn how to make a Murder Robot Dog. And that's really nice. And lastly, there was a really great video that came out about the fundamentals of Lambda calculus, which is actually the logic and mathematics that make this all possible. If you think this is all weird, there was a Twitter thread today where it was discussed that both World War II, some of its results, are dependent upon functional programming. Turning machines are equivalent to Lambda calculus, if you've seen The Imitation Game. NASA uses functional programming to send people to planets and satellites and all that stuff. So I encourage you to learn it. I encourage you to come up to me and ask me any questions, follow me on Twitter. My DMs are always open. Thank you so much.
Live captioning by Mirabai Knight