What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Functional-Lite JavaScript
by Kyle Simpson
This course offers a practical take on functional JavaScript so you can use techniques like pure functions, map/reduce/filter, and recursion to improve your programming today.
Resume CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Recommended
Pure Functions
Course Introduction
Super huge honor to be here. For those of you that haven't heard of me before, my name is Kyle Simpson, known as getify online. And all the places that matter, you can find me at getify, so Twitter, GitHub, Gmail, everywhere that you want. So I make all that stuff available so that I can encourage feedback. I might say something today that's particularly confusing, or I might say something that you just totally disagree with, and that's entirely great if you want to provide that sort of feedback. So in addition to, if you're watching the slides, in addition to the chatroom, feel free afterwards, or if you're watching these videos later, feel free to reach out with any sort of feedback about that. Actually I think I want to self-correct myself, so if I'm doing my counting right, just so we make sure for posterity's sake here, I think this actually might be workshop #7, and then the next one we do is #8. So I think I've had Web Performance, I've had Advanced JavaScript, I've had Node, I've had JavaScript Basics, I've had Coercion, I've had Organizing JavaScript Functionality, so today makes #7, so that's pretty awesome. It's a huge honor. I mention that not just to point out what is available but also I want to always give credit where it's due. That very first workshop, Web Performance Optimization, is the very first workshop I did as a teacher. Mark reached out to me sight unseen. He'd seen a few of my conference talks, and he said, Hey, have you ever thought about doing some teaching. And he coerced me, convinced me to do some teaching. I'd thought about that before but never done this long-form software development teaching. So he gave me my shot, and I am now a full-time teacher as a result of that. So, it's good to almost---it's kind of like coming home to come back to Frontend Masters to be part of this. It's a fantastic platform, and I'm thankful for it, and you all are incredibly lucky to have access to it. So, let's jump in. I'm going to try to keep my intro about myself a bit brief because they're on all the videos, but just for those of you that haven't heard of me before or seen some of the stuff that I've done before, just real quickly, in addition to doing this teaching thing about JavaScript, traveling all over and teaching and things like that, I also do a lot of open source development. I've got several dozen, a couple dozen repos out there. But just to point out a couple ones that are representative of the things that I've done. So LABjs is probably the one I'm most well-known for. It's a dynamic script loader. It's been around now for 6-1/2 years. It's been very stable. It hasn't even changed in almost 5 years because nothing has changed in the script loading world for 5 years. But that is changing. With HTTP version 2 coming along, we're going to see I think a renaissance or a rebirth of interest in intelligent script loading because with version 2, you're going to want to server everything as small and as many files as possible. So, if you're looking for a performance optimized script loader, you might check out LABjs. Grips is a templating engine. Basically I've observed that there're a bunch of problems that happen regardless of how we present our views. I think we've been fundamentally asking the wrong questions, and even the complete rethinking that has happened recently with virtual DOMs, React, Ember, and all those, even those are starting to make some of the same mistakes that templating approaches made in terms of not asking the tough questions about where thinks really should---where the lines really should be drawn. They've sort of played a switcheroo on us and redefined some terms, like what the notion of concern actually is compared to what we used to say 15 years ago. And I was in the trenches when we originally did those things. But basically there're a lot of problems that occur, and I don't think that we've really asked the right fundamental questions. So grips is an attempt, an experiment, an ongoing research experiment into asking maybe better questions about where those lines should be drawn, what we should be able to do, and what we shouldn't be able to do with our views, what should be kept separate, and what shouldn't be kept separate. So if you've had any pain points around those, or if you feel like even with the amazingness of something like React, if you feel like you're still running into those questions about, How do I know where something should be or shouldn't be?, you might check out grips. I also have a CSS templating engine on top of it because CSS preprocessing is exactly the same thing with exactly the same problems, and all of the current tools, as great as they are, all fall short of answering the same questions in the same ways. They didn't really learn any of those lessons that we learned elsewhere. So that's an ongoing research experiment. It's not just a research experiment. I actually use it in production on all the sites that I've ever built. But it is obviously in flux as I figure out how to do things better. So that, and then asynquence, and we're actually going to not talk much about asynquence today in this workshop on functional programming. But it's a flow control library for promises and generators and even higher order asynchronous patterns like observables and things like that. It's designed specifically from the perspective of teaching, so it tries to take these higher-order concepts, which are often pretty hard conceptually. We can barely get our heads around the APIs for these things, but to understand the concepts is really difficult. So I tried to design the API to teach the concepts. That's what this library is all about. If you don't already have something that's helping you understand not just callbacks or not just promises but even higher-order patterns, like observables, for example, if you don't have one library that's helping you navigate all that and understand the conceptuals, or if you feel like, Man, I really just, I don't even understand what it's doing. It's a black box, and I just treat it like that, I encourage you to maybe check out asynquence and see if that might help. I am the head of curriculum for Maker Square as of about three months ago from today, almost four months ago. So Maker Square is a developer engineer training school. We currently have three campuses: Austin, San Francisco, and Los Angeles. Other well-known names you might have heard of like Iron Yard, General Assembly, Hack Reactor, we're actually part of the Hack Reactor network of partner schools run by Hack Reactor. So if you've heard of those sorts of schools, Flatiron's another one if you're from the New York area, you've probably heard of that. So we sort of are trying to shift towards the upper end of this spectrum of developer engineer training schools. I kind of like to say that we're trying to become sort of like the MIT of developer training engineer schools. We're not just anybody can--- a lot of people apply and can't pass the entrance test for example. So we set a very high bar, but that means that when you come of the school, you're even further along in your career. You're a full-fledged intermediate developer. So we take that very seriously. It's full stack JavaScript, and I, newly in this post, I take it very seriously to make sure that you have absolutely the best in-class possible education around JavaScript. So in addition to doing trainings like this, and we do corporate trainings, I also manage the curriculum for those in-person classes. So if you're listening on the video, or if you're in person, and you're interested in this, maybe you know somebody that would like to get in, or maybe you yourself would be interested in something, I'd love for you to reach out and ask questions. I'd love to chat with you about that. In addition, you should also know, and many of you have heard, of the book series I wrote on JavaScript called You Don't Know JS. I often times get asked, Where does that name come from? Obviously, there's a little bit of a tongue-in-cheek joke there. But where does the book come from? Where does the book title come from? Honestly, the reason for that was really more about myself than anybody else. The two years that I spent writing this six-book, 1100-page series is all available for free online so you can check it out. You can also purchase the books. But the journey that I went through caused me to realize that there is no attaining of full knowledge. It's asymptotic. You approach closer and closer to a full understanding as you learn more and more, but you never actually fully arrive, and so we have to commit ourselves not to just, Hey, I'm just going to spend this month and become a master of JavaScript. I've been at this 17 years, and I'm nowhere near calling myself a master of JavaScript. But what I've done is kept asking those questions and getting closer and closer and closer to that line of deeper and deeper understanding. And I regularly, regularly, week after week and day after day run across something that I thought I understood, and then I more fully understand it. And then I'm like, There's so much embarrassing code that I wish I could go back and fix. So really the title of the series is to sort of gently challenge all of us to adopt that continuous learning mindset. The workshops that I've done through Frontend Masters are the same perspective, and they track very closely to the kinds of things we talk about in the books.
Course Agenda
Let's talk about our topic today. What on Earth is Functional-Light Programming? Completely made-up term that I came up with to try to describe what I hope is a slightly more approachable almost remedial look at this much bigger and much more complex notion of functional programming. Some of you may be very, very familiar with functional programming already. And for those of you that are, for those of you that feel like you've cut your teeth on functional programming, you were a LISPer back in the 70s, or whatever, if that's you listening to the tape, I apologize that some of this may feel very remedial to you. But I think we have a problem because I think functional programming is one of the most powerful sets of techniques that we have in programming. But I think it has been presented in a way that is completely off-putting to most developers. And I think if you cornered somebody who has been steeped in the traditions and in the academics around functional programming, if you really ask them to admit, they would say it was a pretty high barrier to entry. It's like a really, really powerful tool. It's like walking into a workshop and seeing a chainsaw with all kinds of fancy features on it and things like that, maybe it's digital and has an LCD display, and you're like, I don't have any clue to do that. I just got a piece of wood I need to cut, and I have no idea how to use this thing. It's a high learning curve. So the workshop today, my goal is to try to get at some of those fundamental principles without wrapping any of it in any of that sort of academic notational terminology, things like that. We're not going to talk about monads. To be honest with you, I'm not even fully sure I understand what a monad is. I can kind of fumble around with some of those sorts of things because I've been to I can't even tell you how many talks that claim to be the talk that's going to teach functional programming, and I do not claim that this talk is going to teach you functional programming. This workshop is not going to teach you functional programming, but it is going to teach you the basis upon which functional programming can be built from the observational perspective rather than from the academic perspective. That's why I call it Functional-Light Programming. These are like walking into a workshop and saying, Well, I don't understand that crazy complicated chainsaw thing, but I just have a tiny little piece of wood that I need to cut. Okay, here's a little handsaw. It's much, much simpler. It's much more remedial. It doesn't take a lot of learning. You can figure out how to do this. The principles of a handsaw are the same principles upon which a chainsaw is built. But obviously the chainsaw is much more complex because it's designed to handle much more complex problems. So what we're doing here is a start. It's not all of functional programming. But it's a start towards understanding where we need to go if we do want to be better with our functional programming practices. This represents my journey to understanding functional programming if I really have to boil it down. These are the sorts of things that without ever understanding any of the terminology, I gleaned some of these concepts from programming and from looking at other people's code. And I'm just trying to simply lay some of that stuff out to help you gain a better understanding. So let's quickly look at what we're going to talk through over the next several hours. First, we're going to talk about what pure functions are, and even before that, we have to understand obviously what a not pure function is. So we're going to look at the notion of not pure versus pure functions. We're going to look at composition. Already starting to sound like a slightly fancy word, like a slightly fancy terminology, I promise I'm going to help you understand what that word is. I chose that word as opposed to a number of other words that would make no sense at all. The famous quote about monads is Oh, they're just bifunctor in the category of endofunctors, or some crazy thing. We're not going to use any of that stuff. So don't get too scared with the terminology. Immutability--the notion that we have something that doesn't change. We treat it immutable. We can even make it immutable. So we're going to look at what immutability is just from a very basic sense. Closure--closure is probably the most important programming concept ever invented. I know that's a big claim to make because there're a whole bunch of great programming concepts that have been created, and we use lots and lots of great tools in our programming. But I would say that closure is the most core, the most important, the first place that we really need to grasp. So closure, we definitely want to take a look just briefly at understand what that means and how we use that. Some of you may have already seen other courses and other books that I've written on it, and I go into that topic in much more detail. We're not going to go into a lot of detail about it but just to present to you the way that we use closure from the functional programming perspective. Recursion is another one of those topics that has usefulness outside of functional programming but is also very heavily related to functional programming. So we're going to have a basis understanding of recursion. Recursion's one of those things I know that is very easy to kind of get scared of. I've talked with students, college students recently, who have told me things like, I just can't even possibly wrap my brain around recursion. And that's a shame. I'm saddened by that. I'm saddened by this notion that we can't figure out some basic way of explaining this. So this is my attempt to help you kind of get your brain a little more wrapped in that direction. Then we're going to look at a set of things, these are going to build on higher-order, so slightly higher-order patterns on top of these basic fundamentals, but they really are kind of the main takeaway from our workshop today. So we're going to look at taking a list of values and performing a set of transformations on them. For those of you that have heard of functional programming before, transformation is done with map. So we're going to look at transformation and then look at the map. Then we're going to look at exclusion, excluding items from a list, paring down a list to a smaller set of items based upon some condition. That goes by the name filter. Then we're going to look at composing items from a list into either a smaller list or even down to a single value, composing values together. Now I don't mean composing lists together. We're not actually going to take a very close look at that, but that's pretty straightforward as well because we can concatenate two lists together. So there are fundamental list operations that really don't necessarily show up a ton of times in functional programming. But composition here, what we mean is composition of the items, putting things together. That goes by the name reduce. And, finally, list iteration, which in JavaScript we call forEach. Other frameworks will call it each or things like that. In particular that last one I'm mentioning not because that's actually---that's actually kind of frowned upon in the functional programming world. We're going to find out that list iteration is actually an impure action. So from a purely theoretical perspective, functional programming, they turn their nose up at something like list iteration. So I just want to show you the notion of list iteration so that we understand why it's the---when we watch Sesame Street as kids, and they're like, One of these things just doesn't belong, list iteration's going to be that thing that just doesn't belong, but we need to understand the other things before we understand why list iteration doesn't belong. So that's our set of topics that we're going to go through. There are several exercises that we'll spend some time on, and we will take some breaks throughout our discussion as well.
Side Effects
First of all, I said we need to talk about this notion of purity, but we have to understand the opposite first. And the clearest and easiest way to understand what an impure function is is to think about a function that produces a side effect. What is a side effect? A side effect by virtue of its name is something that occurs that's indirect as a relationship of an action that you took. So an action that you take like calling a function and asking for it to compute some value and return it back, that's the direct effect of that function. We get the value back. A side effect is basically when we change the state of the program through an indirect means. I call this function, I get a direct result back, I get some direct effect back, but I also change something else about the state of my programs. And side effects are the big evil in functional programming, like the big, big evil. That's why we start here. A functional programmer will tell you we've got to reduce and remove as many side effects as possible. If you've ever worked in a _____ language, I haven't done much, but I've read some things about Haskell, and Haskell and other functional programming languages. And they get virtually impossible to do side effects. Like the language is designed from the ground up to not even let you be able to do that, which to be honest with you sort of twists my brain. I'm not entirely sure I understand whether that's valuable or even how that works because I haven't done---personally, I haven't done a lot of Haskell programming. But it's this notion that this is so important that they baked that into the language. Now, unfortunately, there are some places where we kind of have to cheat. For example, input and output. Even something as basic as a console log statement to your console is from a theoretical perspective a side effect. You've changed the state of what is happening. So input and output, pretty basic importance. You're not going to get a ton of usefulness out of your programs if you have no notion of input and output. So even in those functional programming languages that draw very distinct line and say, You should not and cannot create side effects, they have this cheat, this side door cheat that allows them to do things like side effects on input and output. And they say, Well, that's just a necessary evil. We don't want to program that way, so we keep everything else pure, and we just leave that over to the side. And there's virtue and there's value in being very intentional and very explicit about what's pure and what's not pure, and virtually everything in Haskell programming is pure except for these few little cheats. So side effects, producing side effects can be things like the console log. It can be changing the state of some variable such that each time the function is called, you're going to get a different result. That's also a side effect. What's interesting is that there's a little bit of tension, and I'll just sort of project a little bit to a little bit later in our discussion about closure. Closure is about maintaining state inside of a function. And when you maintain state in a way where that function can be called over and over again and get a different result each time, that can start to---there's some tension there between that and this notion of side effects. So even the way that we put these Lego blocks together, we can have two very good Lego blocks that a functional programmer would say, Yup, those are both great. But there's a way to configure those that is not so great. So we have to be careful about these things. Side effects are the big evil that we want to avoid, so if we're looking for the big evil to avoid, this is just a quick illustration. You can see that I call foo. I pass in the value 5, and I have two side effects as a result of this function. I have the y and the z variables that are outside of the function. Now there's lots of fancy terminology for this, but I'm trying to keep us away from the terminology, but these are just variables in the global scope. And we're changing those variables. And by the way, they don't have to be in the global scope. There could be some wrapping function around this, and we could still be doing the exact same thing, which is changing variables outside. So the first time I call foo, I change the state of y and z forever; permanently I've changed that state. So foo is said to be an impure function. Now, one of the interesting things that I've observed, again, not coming at functional programming from being steeped in the academic tradition but trying to sort of come at it from the reverse, like I'm a programmer and I need to get my hands around some of these tools, one of the interesting things that I've observed is when I look at functional programming, even though they tell you the evil is side effects, you should not have impure functions, everything should be a pure function, and I'll show you pure in just a moment. But what's interesting is there're a whole bunch of impure functions in their programs. A functional programmer will write many times impure functions. But the magic is, this again is just my takeaway, it's kind of like an observational takeaway, the magic is they never leave something impure publicly exposed. So if you have some action that fundamentally needs to be impure, it needs to have some sort of side effect because that's all you have available to you, the way you make it functional again, the way you make it pure is to wrap another function around that to contain all of those side effects within that function, and from the outside world, that function itself, that outer function is pure. So it's kind of like turtles all the way down, you know that principle. It's not actually pure all the way down. It really only has to be pure at the highest most level, and under the covers, it can be as ugly as you need it to be. And we'll see some examples of that. So, if I had this impure function as I show this function on line 1, this impure function, what can I do to make it more pure? How could I wrap it in something that would be more pure? Well, I could---sorry, before I get to that. This is just another example of making those side effect changes. This is illustrating the fact that this change is happening over time. So when y and z have changed the first time, when I call foo on line 13, I'm making a change to the state that's not the same as the state was on line 8. So over time---and the reason why state change, by the way, the reason why state change is frowned upon, the reason why this side effect is frowned upon is that it makes code harder to reason about. It doesn't make it impossible to reason about. Most of you that are not steeped in functional programming traditions have been very successful at writing very, very stateful programs, non-immutable, non-pure programs. You can get the same job done. But, honestly, it does make the program a bit harder to reason about. So what we're trying to strive towards are ways to use some of these techniques in certain patterns that will help us make more reasonable programming, not things that we couldn't do before but things that we couldn't do as reasonably before. So here the fact that I have to know from the outside that foo changes some state, and the first time I call it, I'm going to get a different answer than the second time I call it, that's a complexity in my program that makes it harder to reason about. I have to trace the entire runtime stack to know how many times foo has been called before I can predict its output. If I could rearrange this program in such a way that every time I called the outer function, I got the same result, I don't have that state complexity to worry about. So, pure of course is no side effects. What is a pure function going to look like? Well, I'm going to wrap bar around foo. You notice foo is the same definition, but you notice what I'm doing with function bar declaration on line 1, I'm passing in the entire universe. The whole universe that is cared about, I'm passing in the x and the y and the z state. I'm passing those things in, and that means that every time that I call bar with the exact same universe, I'm going to get exactly the same answer back. And bar could be arbitrarily complex. Bar could be our entire program, and we could have the entire state of our program that we put in, and it churns a whole bunch of stuff and gives us an end result back. And every time we run the program, we're going to expect to get exactly the same result. So what I've done is taken something that's fundamentally impure, the actions that I'm doing with foo, and I've made it pure by wrapping a wrapper around it. Now the contract is not the same. You'll notice that I don't have independent free variables like y and z anymore. I get an array back with y and z in it. As I'm returning on line 3, I'm returning an array with y and z because I don't have a way with the return keyword to return multiple valuables other than to wrap it up in some sort of container. But it turns out that a functional programmer would tell you that's exactly what you want. You want some new value back that represents some new state because I can take that new state and run my program again with the new state if I care to, or I can discard that new state. I have those options. But I don't have to worry that this thing might have been called several times and might have changed state in weird ways that I can't understand. So creating a pure function, eliminating the external side effects, can be as simple as simply wrapping a function around a whole bunch of impurity. Now we could've gone back and refactored foo itself and made foo pure, but I wanted to illustrate to you, and we will see this later in some of our exercises, that the notion of wrapping one pure function around one or more impure functions is actually something you see not terribly rarely in functional programming.
Exercise 1
So we do have an exercise, exercise 1 to help to try to drill that into you a little bit if you're not terribly comfortable with that notion of wrapping something around it. You'll notice that exercise 1 is going to say exactly what I just suggested, and it's going to be very similar to that. So let's pull up the README. I'll just orient you to exercise 1 very quickly. Ex1--we're going to pull up the README, and I'll go ahead and pull up the ex1.js as well. So the README is going to say Make a pure function bar that wraps around the implied impure function foo. Let me zoom out just a little bit so we can see. There I have a foo that's doing something very similar to what I just showed you in the slides. It is an impure function. It is making changes to a y and a z variable on the outside. And each time I call foo with a different value, I'm getting an actual different end result. And if I called foo with 20 each time, I would still get different results. I wouldn't get the same result every time with foo(20). So your task, very quickly, is to define a bar, a wrapper very much like the last slide that I just showed you, define a bar, a wrapper that turns this program into a pure program, encapsulates all of that state. I'll give you 3-5 minutes. We'll take a break for just a few minutes while you work on that, and then I'll walk you through the exercise. Just as a reminder as with all my exercises, you'll notice that if you get stuck, obviously you can ask questions, but if you get stuck, the fixed version of all these files is always available to you in that same folder. So if you get stuck or you just want to cheat, you can look at the fixed version of the ex1.
Exercise 1 Solution, Part 1
We said in the slides before I entered the exercise, we said in the slides portion that one of the ways to turn a stateful program, a side effect program, into a pure program is to pass in the entire universe of state. Now there're lots of different ways of doing that. You could pass it in as an object of course. Here we're just being very simple to illustrate the concept. There're a couple of variables that represent the state. So what are those variables that represent the state? And let's look closely. Does x represent the state of our program? Not really. X is a parameter to the universe, but it's not really part of the state, so we don't necessarily need to represent x in some stateful fashion, although we're going to need x, so it's going to need to be one of our arguments. Well, what about y? Y clearly is something that changes over time. It's incremented there on line 2. It's something that changes over time. It is part of our state, so we're going to need to pass in that initialization of that state initializing it, of course, to 5 like we do on line 6. So y is part of our state. And z, is z part of our universe? Does z change over time? Well, z is an output of our program, but it's not something that is reused each time. It's reset, and we just assign it on line 3, and it doesn't matter whatever value it previously had. So z is not actually something we need to import as part of the state of our universe. It's just something that's going to represent an output. So all of that notion is just to help you with the concept that syntactic things aren't going to look very much different from what we already did. So, let's use a bar. And I use, by the way, these functions foo/bar sorts of things because if I gave you too heavy of something that was kind of of a specific problem domain, then it starts to get a little bit like your brain not only has to wrap your head around these concepts, but your brain also has to wrap your head around that particular problem domain, and if it's one you're familiar with, great! If not, well, now you have two things to learn. And I want you to have as few things to learn as possible. So that's why I use the foo and the bar thing. But we will to whatever extent possible try to make these things a little bit more concrete as we go along. So one thing I'm going to do, and this is just a complete pure side note, but I have taken recently in my programming to inverting the way---I normally used to write the functions at the top and the executable code at the bottom of any particular scope, any particular file, or any particular function, and I now do the reverse. And the reason I do that is because it's much easier, I found this maintenance-wise to be much easier to open up a file or to look at a function and at the very top be able to see everything that it's doing, and then I rely upon function declarations and the fact that function declarations are hoisted so they'll be definable. So I can call foo before it's technically been declared because JavaScript is compiled, and the scope is going to take care of that, and the shorthand way of talking about that is hoisting. I talk more about that in the books and in the advanced training video if you're more interested in that. But I'm going to move the function to the bottom just because I'm trying to practice that more as a discipline for myself. I find that to make code a little bit easier to understand. So, y isn't going to be a variable like a local variable. It's going to be something that's passed in for sure. And in fact we're also going to pass in x because we need that x value to be used. So I'm not going to declare y or x as variables, but I do need a z because I'm going to need to be able to return a z. So the foo function as we know it takes an x. Now I don't have to pass the x in. I can take the x out and just simply reference it. You'll notice there that on line 8, I have an x, and I can reference that. That's going to be called closure. But we're not getting to the closure point yet, so I'm going to formally pass in the x. And that means the x needs to be passed in here. Whatever the x was given to my universe, I'm going to pass that in to this inner impure function. Now you notice that I'm changing the y. That, of course, is changing it as a result of that lexical scope, that closure over the y and also the z. It's changing the z. So I'm going to call the function foo, and then I know that my y and my z have been set to their proper values for the end of my program. So what I need to do is just simply return them. And I'm going to encapsulate them in something. And a functional programmer usually turns to an array when they want to encapsulate some value. Lists are particularly convenient, and we'll get more to lists later. So, I'm just going to simply return my y and my z. Now you notice I'm not returning x. I could, but x isn't something that's changing as a result of any of what we're doing. It's just simply an input to my universe. It's a starting state. It's an initial state. So I don't really need to return it. So I really only need to return that which changed, that which would be something that somebody wanted to observe. Now if somebody didn't care about observing y for example, of course this program changes y so you probably do, but if you didn't care about changing y, you wouldn't even want to return that. You only want to return that which somebody would want to observe. So that's our bar function, and it's going to execute the foo function and then return the result of whatever those values are. Clearly on the inside, this function is as impure as it gets. Hopefully everybody can agree with that. But from the outside perspective now, bar takes in a universe, and it returns back a result. And nothing on the outside of bar changes the result of calling bar, which means we can call bar over and over and over again with the same input and get the same output. That's how we've made it pure. So, just to illustrate that, instead of calling these foo variables down here, I'm going to call a bar. I'm going to pass in an x, which in this case was the 20, and I'm going to pass in a y. You remember, I've taken it out now, but remember our initial value state that we cared about with y was 5. So if I pass in the value 20 and ask for the value 5 back, what am I getting? Well, let's run through it just to make sure, and then we can---you can of course execute this in the console of your browser if you care, but if I pass in 20 as x, 20's going to come in as y. If I pass in 5 as y, 5 is going to become 6, and then I'm going to say 6 * 20, which is 120, and that's going to be my z. So what am I expecting back as my return value? I'm expecting back an array with 6 and 120. And if you try it, you'll notice that you get that value back. Now if I called bar(20,5) again, I'm going to get the exact same state back because now I'm dealing with a pure function. There are no side effects to a pure function. And, of course, if I call bar with 25, and I call it with 6 now because 6 was my new state that I'm observing from y, if I take that state and I pump that back in as the starting point for my second run of this universe, now 6 is going to become 7. Seven * 25, we're going to get---Y is going to be 7, and we're going to get 125 back.
Exercise 1 Solution, Part 2
I saw a couple of hands. Wouldn't you have 175? Sorry, I typed it wrong. The question is, Wouldn't I have 175? I just completely mistyped. Yes, the end result would be 175. Was that your question also? I just wanted to make sure I got the right answer. I have not had enough coffee apparently. I can't do my math this morning. I said 175 and typed 125. There was a question, Is it possible for an app to have all of their functions pure? Well, we could quibble about that. I would say in general the takeaway would be if your program was entirely pure with no side effects, that would mean your programmer would have no input and no output. And if he had no input or output, then your program would be completely pointless. If it had no effect on anything, if there was no observation of it, if there were no state changes as a result of your program, then I know what you can do with that program, you can simply delete it because that's about how useful it is. That's the glib way of saying you're going to have to balance where you need purity and where you don't. And you notice, again, I didn't start this workshop to say functional programming, do all functional, do all functional. Functional's cool, but it's not the hammer that you use for everything. The perspective that we're taking here is there's some usefulness to it, and there are places in our program where it will make it easier to reason about. So I don't encourage you to write your entire program in a functional style. I've never once done that. But I have used some of these functional concepts in my regular programs, and the places that I've done, it's improved the understandability, the reasonability of that code. Good question online. Thank you. I see a question in the chatroom. Where would you use all these pure functions if you're using frameworks like Angular and such? My answer to this is not going to be terribly satisfying, but I'm not going to answer any questions about frameworks because that's way outside of the scope of what we're going to talk about here. I'm not an Angular expert. There are plenty of people that are very smart about Angular, and I would recommend if you're doing Angular programming, embrace the Angular way because Angular has a way of doing it. If you're doing React, embrace the React way. My goal is to really sit at a lower level than frameworks and to talk about the mechanisms of the language itself and how we can use those mechanisms. So I think that it's entirely possible that you might be able to improve some of your Angular code with some functional-light programming concepts. But the chances are you're not going to be able to do a full functional programming pass and still use Angular because that's just not the mindset that they used to design that framework. The same would be true of React and Ember and Backbone and all of that. That's one of the reasons why I'm teaching this workshop, by the way, is I'm trying to make some of this stuff more palatable even in mixed environments where you know you're not going to be able to go entirely functional programming. These are little tiny tools that we can use in very tactical specific ways. Yes? We're kind of getting a few here so --- Okay, great. --- bear with me. Is it best to return an array? What other way could we---what else could we return---what other way could we return y and z to keep the function pure? You're going to have to wrap it in some container. The two fundamental containers that we have in JavaScript are going to be the array and the object. So we could have gone to the trouble of assigning several different values into properties in an object. Usually functional programmers like arrays. They like lists because lists are very convenient to do operations on. So my instinct in a functional programming mindset or in something that's even remotely close to functional programming, my instinct is to reach for the array before I reach for the object. But any kind of container would do. Even a more sophisticated container like a map or set or something if you wanted to. You're going to have to have some container because syntactically the language only allows you to return a single value if you need to return multiple values. Another way of answering that, I guess, is to say refactor your program so you don't have to return multiple values, but that may not be something that you can use as general advice. So when you do have to have multiple side effects, you're going to need some container with the return here. Can you repeat what a pure function is? Yes, a pure function is a function that has no side effects. It operates entirely on its own variables, its own state, or any of the things that are passed into it, so the arguments that are passed in and any of its own. It operates entirely on that and does not change anything. A pure function does not mean it doesn't access outside state. It means it doesn't change the outside state. So there're no side effect. So I could write a function that accesses a variable outside of itself, and that's totally okay. And that's not going to be considered at least from our perspective, for our intents and purposes, that's not going to be considered an impure function, except for the fact that that variable now can change from out underneath this function, so the overall reasonability is less pure. But the function itself we still call pure because it does not have a side effect. So if you take precautions to make sure that you do not have side effects in a particular piece of your program, then that piece of your program is pure. On this program, I get z as undefined or something. Well, here we're not---the question was z is undefined. The way I rewrote this, I'm not using z anymore externally. Remember, z is only being used internally. That was part of the point. Z was a set of state that we didn't want to change on the outside, so we encapsulated it as a local variable inside of bar. We do use it for the brief period of time between when we declare it and while foo is running, it's changing, and then we return it. So we do have it there, but it's not exposed to the outside world. That's intentional. This might have been---I don't know if this is out of order, but they're asking, Why do we return z if it's not being passed in and changed? But I don't think we are anymore, right? So that's actually a very good question. We are returning z. We're not passing z in. Z is not an input. Z is an output. Z is how I observe the output of this particular program. If I didn't give you z, it wouldn't be a terribly useful program, would it? So this is, again, we're not going to get way deep into the weeds with functional programming, but this is one of the ways that you can think about as a functional programmer even doing side effects without side effects is to simply pass in everything that's needed and return everything back that's needed to be observed. Pass in the inputs, receive the outputs. So here one of the direct outputs is the z variable, and one of the side effect outputs, one of the state that we may or may not choose to track and use later is that y variable. So I'm returning both of those, and that's why I need some container around it. Are we good? Alright, excellent! Those were fantastic questions. Thanks very much for that. That's exactly the kind of thing that we want to pay our attention to.
Composition and Immutability
Manual Composition
Our next topic, Composition, and I know I said we'd try to stay away from fancy-sounding words, so let me try to help explain what composition really means. Composition in this sense is about taking the output of one function and putting it directly in as the input to another function. So in other words, instead of calling one function and then calling another function, we're going to call one function, and its output is going to become at least part of the input for another function. And then the output of that could become part of the input for another function. That's what we mean by composition from a functional programming perspective at a very, very simple basic level. So what would be an example of a program that might need some composition? Well, if I had a function called sum that takes two variables and adds them together, that's a super useful function. You can all add that to your utility libraries now. Underscore--I don't even know if they have that yet. I invented the sum function, and I invented the mult function, which multiplies--even more powerful. I know, silly functions. We got a---I don't have very much room on these slides to show you code, so I have to use simple concepts here. But if I wanted to perform an operation like 5 + (3 * 4), I'm going to have to compose these two functions in some way. I might compose them with state. I might save them into a variable like I'm doing with z. But now you can see that I'm doing sort of an impure thing. I have this side effect state. If, on the other hand, I were to take the result of mult and pass it directly into sum, never saving it into some side variable, then I haven't created an impure action. I've simply composed two pure functions together. So right now this is an impure program because there is some side effect state that's happening. Is it a side effect in the sense that our program is going to return different results? No, because we don't use that Z variable, but there's still a side effect that's happening. Z is still getting changed. That is still a complexity that could create issues for your debugging later. So how can I reorganize this program so that I wasn't assigning to some variable? Well, I can simply pass in the result of mult, calling in mult with 3 and 4, and pass in its result directly into sum. In a functional programming sense, this is kind of a manual composition. So I didn't assign to some side variable state, I just took the end result and pumped it directly into this next program. So the outer program here is pure even though technically at a programmatic sense, technically our programming language has to temporarily store that variable somewhere, but from the perspective of our program, we've written a pure operation. So is everybody following that? That's why these concepts build upon each other. So composition is a way of reducing the amount of side effects we have. It is absolutely true that this is not some magic silver bullet that works everyone because you're not always in control of the design of your function signatures. And if our function signature happened to not work in such a nice way where we could pass in a value like that, or if one of our functions needed to return two values but the thing we needed to pass it to only needed one of those, it gets a little more complicated. Granted, that is absolutely true. This is why we call this functional-light programming. We're looking for ways to develop those instincts. And to whatever extent that you can develop that instinct, it may guide your design decisions for the way you write your functions. You may write your functions assuming that somebody may want to compose that with a different function. That is not only the argument signature but also the return value signature. So if we wanted to create a pure wrapper around this to do this composition for us, we could do it manually. We could call it multAndSum and pass in all of the values, x, y, and z, and simply call mult and then pass its value into sum. That's a manual composition. We could create ourselves a little utility as a manual composition.
Composition Utility
There are ways to extrapolate composition to a very general sense. And we're not going to try to go very deep down that rabbit hole because there's actually a whole bunch of complexity here that I have not personally found to be very useful in my programs. But I do just want to take you slightly into the rabbit hole so you understand a little bit about how that mindset works. What if I wanted to create myself a slightly more generic utility, a compose utility or, rather, I'll call it a compose2 utility, which composes just two functions, and it makes some assumptions about what arguments are passed. What if I wanted to make that sort of reusable utility? I could declare a compose2 function as I'm doing here on line 9. It takes a fn1 and a fn2. And you'll notice that the first thing it does is create a new function that it returns back. Oh-oh, that's a different pattern that we've seen so far yet. That's a pattern that's going to hearken forward to what we're talking about when we talk about closure. The ability for compose2 to continue to access fn1 and fn2 at a later time, that's closure. So we'll see that a little bit later. But we're making a function as a result of calling compose2. We haven't called fn1 and fn2 yet. We've simply made a function that will call them at a later time. Now, I have an args variable here, and I'm doing some of that crazy, crappy JavaScript nonsense that we have to do to turn the arguments object into a real arguments array. Sucks, right? There's hope, I promise. On the next slide, we're going to see there's some hope here. But that's the old school, ugly way of doing it. And then we have args.shift and args.shift. I'm simply taking the first two off of the array and passing those into fn1, and then its result, and then I'm taking the third argument and passing it into fn2. This was a way of cheating, saving some space for some slides. You might have chosen to assign those out into separate variables and pass them in. That would have been entirely fine. But I just make an array, and then I shift some things off that array. And you notice here that I made some assumptions. I assume that you passed in two and only two functions and that you passed in three and only three arguments and that you wanted the first two to go to the first function and then the results of that to go to---sorry, you wanted the first two to go to the second function and the results of that plus the third one to go to the first function. I made some assumptions here. Now, when you choose to make utilities like this, you can create any sort of utility your brain can come up with. What a functional programmer does is look at principles of mathematics, principles of programming logic, and say, What are the most common patterns for how people compose stuff? And, in fact, all functional programming is, literally, it's a turtles all the way down sort of thing. It's at this most basic level, let me define a pattern that's really very commonly used. Here's how we compose some stuff, and then we make a slightly higher wrapper, and that's how those two things get composed. And then now that I have things of that, when I want two of those, that's how they normally compose. That's really all what functional programming is. We could dive much deeper down that rabbit hole and spend our whole day and probably not get a whole lot of practical stuff for our JavaScript programming. So I just wanted to tease you with that notion that you can create your own compositions, but when you do that, you want to look for ways that it makes your life easier. Don't make a pattern just simply because it looks cool or you've learned some new trick or something like that. Make one of these compositional patterns because it's something that you're doing over and over again, and it's going to help you. And, by the way, you might end up discovering for yourself some of those very common compositional patterns that have those fancy names like monads and functors and all that other stuff because that's all they've done in functional programming is observe the most common of those patterns and make utilities for them. Alright, so, compose2, we pass in the function mult and sum, and then when we call multAndSum down there on line 21, you notice that it does correctly for us, it produces our 17 value result. Any questions about composition so far? I've got a question. Why are we returning function comp instead of just function? Why did I give it a name? Basically, that's a side thing in JavaScript. I tend to not like anonymous functions. In fact I very much don't like anonymous function expressions. I've covered that in the advanced JavaScript course, as well as my books, so I won't dive too deeply into it. But a named function expression is always more preferable to an anonymous function expression. And I know there're probably a bunch of people listening that'll be like, I don't like you. You should use the arrow functions or whatever. That's a whole other side thing that we don't want to get into today. But I just simply gave it a name because in a stack trace, that variable will show up if I have an error. And if I didn't give it a name, it wouldn't show up. That's the basic point. You'll see a whole bunch of places where I don't give them names because I'm cheating or I'm trying to save space on the slides or anything like that, so this is a definitely do as I say, not as I do sort of thing. But I do try to have the discipline of always naming my function expressions. Yes? I have just a quick comment. On your previous slide, you have 5 plus the multiplication, and then every place else, you're actually adding the 5 afterwards. In my comment, I say it in the opposite. And the reason I do that is to motivate why the parentheses are necessary for order of operation. I wanted the numbers to strictly increasing 3, 4, 5 because that's pleasant to people. That's basically it. Yes, you're right. Is there a question? There's a question about random values. It seems that even wrapping those wouldn't make them pure, just like wrapping a random number generator. Is a random number generator a pure utility? That is also going down a rabbit hole. So pseudo-random number generators are based upon a seed, which is a state, and if that state can be repeated in some way, if it is a seedable system, and it changes based upon that seed, that's an impure system. I guess at a very basic sense, I'd have to tell you, Yes, random numbers would be impure. And then they're just curious about, Do you think the functional approach better fits the whole functions as values aspect of JavaScript? Say the question again, I'm sorry. Do you think a functional approach is better, better fits the whole functions as values aspect of JavaScript? So functions as values is what most people---when most people say that, they're talking about functions as first-class citizens, first-class values with closure. And the question then is, Is closure more closely related to functional programming? Absolutely! Closure's a very important piece of functional programming. On the other hand, I have used a ton of closure without even remotely having programs that look like functional programming. So closure is not only a functional programming concept but is absolutely, positively, without a doubt one of the most important things that comes from functional programming.
Immutability
The next thing we want to talk about is immutability. Now mutability, mutation, immutability means non-mutation, non-ability to mutate. But this term evokes different things in people's heads. So I want to show you one thing that is strictly speaking immutability but isn't exactly the same thing as what a function programmer means by immutability. So, if I declare a variable called x, if I say var x = 2, and I try to change it as I do on line 2, that's allowed. But if I declare a constant, const being a new keyword added in the ES6 version of JavaScript, and I try to change it on line 5, that will not be allowed. If I'm in strict mode, it'll throw an error. If I'm in non-strict mode, it'll just simply ignore the operation and not allow it to occur. So a lot of people will say, well, const gives us immutable values, and that's completely and utterly false. The const keyword is not about giving you immutable values. It is about giving you an immutable assignment or to be a little bit more programmer-speak about it, it's an immutable binding to a value. So what we mean here is that y can never be assigned to some other value. But we're actually not saying anything at all about the value itself being unchangeable. In fact, in JavaScript, primitives are by definition immutable. The value 3 can never actually be changed to mean something different than 3. And a string, even a string we might think of as being an immutable thing because you might think, I could go and change a character. No you can't. Strings, primitive strings are immutable. Primitive numbers are immutable. Primitive Booleans are immutable. So the const keyword actually has nothing to do with declaring a mutable or immutable value any more than var does. Var and const are controlling the assignment or the binding of a variable to some particular value. This is illustrated on line 7. I do const z and I give it some object. Now we know, probably most of you, that variables that hold values which are non-simple primitives, that's objects, arrays, function, those are always held by reference rather than by value. So z is actually a reference to the array rather than holding the array itself. And what we're saying here is only that z will never point to any other array than exactly that array. But we're not saying anything at all about the nature of the array. In fact, as we see, line 8 would not allow us to change z to point somewhere else, even to a different array, but we can reach into the contents of z and change them all day long like we do on line 9. So const is not declaring an immutable array. It is declaring an immutable variable called z bound to that particular array by a reference. So I want to make sure we clear that up, and I've written some blog posts about this if you want to search on const and getify. You'll find blog posts about that. But it's really important to not allow those two things to get messed up in your mind. So if I wanted to declare an immutable array, it turns out there's another utility, which is Object.freeze. Object.freeze, a utility built in as of ES5. It reaches into an object, and at a shallow level only, that is the top level of all of its properties, it makes all of those properties read-only. In an array, that means all of its numerically indexed positions. In an object, it means all of its named properties. It makes all of those property assignments read-only. It's kind of like applying const to each of those properties retroactively. It makes the properties themselves bound immutably. But it doesn't say anything at all about the values that they're pointing to. So if I had an array with an array inside of it, it's not going to make the array inside of it frozen. It's just simply going to say that array has to always be in that particular position in the outer array. So Object.freeze is a way of doing shallow immutability. If you want more sophisticated immutability, there are all kinds of--- there are several really, really good libraries, some stuff that has come out of the ClojureScript world and Facebook's done. There's an immutable, I'm forgetting exactly what it's called, but there's an immutable data structure library written in JavaScript that gives you immutability by default. It's very powerful, very useful. But I just want you to know that the built-in language utilities are really not quite exactly the same thing as what's meant by those. Const is all about an immutable binding, an immutable assignment is really the best way to think about it. Object.freeze is about a shallow set of immutable bindings at the property level. Yes? So other than Object.freeze making it immutable, so what is really const doing there? That's a fantastic question. The question is, If Object.freeze is all about making the value immutable, what is const doing? Const is saying that w will always and forever without any chance of being incorrect, it will always point to that exact array. So I cannot do line 12. I'm not allowed to make w point somewhere else. That's all const is saying. Now, just as a side note since you brought up the question, between the two, between making the value itself immutable and making the binding immutable, are there different value propositions here? Are there different benefits to our code? Many people are of the opinion that the benefit of const is gigantic in their program because their concern seems to be, I don't know whether or not a variable's going to get changed. But I actually think that that's completely a mischaracterization of the issue because I actually think the real problem that we have is not that our bindings are getting changed but that our values are getting changed out from underneath us, and here's why, because bindings are always localized. They're always within the program that you can see immediately in front of you. Even if it's a thousand lines long, it's always within the program that you can see, and you can lexically analyze it. Values are portable. They can be shipped elsewhere. And if you can ship a value elsewhere, and somebody can change that value, that's a much bigger problem. That's an action-at-a-distance problem. So in the world of immutability, immutable bindings to me are not all that useful or interesting. Immutable values are a lot more interesting. And const has nothing to say about that.
Questions on Immutability
So if z and w both pointed to the same array, and you froze w, z would also be frozen, the contents of z, right? So if you have w equal--- If you had a line 11, const w = Object.freeze, and then on line 11-1/2, you said z = w, is that what you're asking? But put the other way, if you say w = z, but then you freeze w--- If line 11 said const w = Object.freeze(z), is that the question? The answer to that question is all about references. Z is simply a reference to the array, so you would be taking a reference to the array, passing it in Object.freeze. It would be making a change to the actual array, and now z and w would be constant references to the same array, which now had been frozen. So z would be frozen also? It's not appropriate to think of it as being frozen also because there's only one. What we're saying is that we've made that change at a distance in a sense. We made it on line 11 and affected the value that we saw on line 7. Yes? I think you just answered this, but I understand the concept of 7 and 10, I'm sorry, 7 and 9, but would you ever do 9 except by accident? Oh, absolutely! You know where this happens the most? When somebody says z.length = 0. The question was, Would line 9 ever happen intentionally? Not in that exact representation where I'm changing one of the values, although that can happen. But what would happen is when somebody tried to empty out your array. You sent in an array, and somebody said .length = 0, and now they've mutated the contents of the array to empty it out, and maybe they then started adding more stuff to it. And they thought, I'm working on my own copy of an array. And they weren't, they were working on your array. So absolutely. It's one of the hazards of having references to values because when you pass a reference to someone, you're giving them the keys to the kingdom. Yes? They asked, So Object.freeze does not return a copy of the object? It does not. It returns the object itself having now been frozen. Is there a way to unfreeze that or something? Is there a way to unfreeze objects? You cannot unfreeze an object, but there are multiple levels of freezing. This is a little bit further than we're going to get into, but freeze is like the top level. It's like a permanent sort of irrevocable thing. But there are other things that you can do to set them all as read-only and things like that, and those are undoable. But freeze is sort of like the... Alright, fantastic questions. Now, the other thing that we mean about immutability, and this is the much more important thing to take away from a functional programming perspective, what we mean about immutability is actually a discipline, which is that whatever values we're given, we choose not to change them. Whether or not the value itself is actually unchangeable is not really the point. The point really is that we choose not to change it. If we chose to change it, and it was immutable like it was frozen, we'd get an error. If we chose to change it, and it wasn't, we'd create a side effect. So in either case, we don't want to do it. So we say, I'm going to take somebody's value, like an array, and I'm going to not change it but instead produce either a copy of it or produce my own set of values. I'm not going to mutate in place. That's really what a functional programmer is more talking about with immutability, not a characteristic of the value itself, but a characteristic of how we choose to use the value, what we choose and choose not to do. In a functional programming language, they don't let you do it. JavaScript lets you do it unless it's been frozen, but we can still be functional programmers in the sense that we choose not to mutate it. So let's illustrate that. What if I had a function like doubleThemMutable? It changes the array in place. You notice I pass in an array (3,4,5), I call doubleThemMutable, and it's actually changing the array in place doubling all of those values. We can see why this would also be considered an impure function because it's having a side effect on the outside world not by lexical side effect but by reference side effect because we passed in a reference that's able to make a change to a value that's actually not entirely within itself. So this is an impure function in that sense. We're making a change on the outside world. You've probably written hundreds of functions like this. This is extremely common. So I'm not saying it's bad, but I'm saying it's impure. It doesn't match with the principles of functional programming. And to whatever extent that we're trying to use functional programming, notions of immutability are things that we want to pay very close attention to. It's one of the core key concepts. So, how could we change this to do the same action but to do it in an immutable way, that is, to produce another value, another list rather than changing the list in place? So, my superly, awesomely named function doubleThemImmutable. You notice that the major thing here is that I'm setting these values into a new list array, an entirely separate array. It has the same number of values in it, but when I return it back on line 12, arr is still the original untouched array. It does not matter in this program whether an array was actually immutable. What matters is that I used the principle of immutability not to change arr. Very subtle but very, very important difference. So one of the chatroom questions was, Are arrays passed as references? Yes, so the way JavaScript does it, and this is actually covered in one of my other workshops, but the way JavaScript does it is all simple primitives--that's the null, undefined, Boolean, string, and number--those five simple primitives, those are always held by value, that means a variable holds that value itself, and that means assignments are by value copy, meaning the value itself is copied. So x = 2, y = x, y got a copy of 2, and that. Non-simple primitives, non-primitives like the object, the array, the function, those are always held by reference, meaning the variable doesn't hold the value itself, but it only holds the reference, and, therefore, assignments are by what we call reference copy. So if I say y = an array, and then I say z = y, I didn't copy the array, but I copied a reference to the array such that there are now two separate references to the same array. So if that's true of assignment, then all we need to do to answer the question about arrays being passed by reference is to say passing an argument is the same thing as assigning it to a parameter. So all assignments happen by reference copy for reference-held values. They happen by value copy for value-held values. And there's no other way to change that. There're no operators or anything. It's just simply an intrinsic nature of how the values are held based on what gets copied. So, bottom line, yes, arrays are passed by reference. They're actually reference copy is really the best way to say it. There are languages where by reference means different things, so I try to make sure we're clear on it. With JavaScript, it's value copy versus reference copy. Are there any other questions? There were some questions about people not seeing it throw errors or whatever, so not seeing Object.freeze when we were talking about immutability. So, it's important to note that in non-strict mode, which is the default, in non-strict mode, it will throw an error. It will just simply ignore what you did. In strict mode, it will actually throw an error. So if you're not in strict mode, you're not going to be able to see it. And, by the way, if you're programming at the console in your browser, turning on strict mode in the console statement in your browser is not as easy as you might think. You're going to have to wrap everything in an iffy and put use strict in it. It's not as simple as just putting it on the first line. It's really annoying, but consoles don't work like real JavaScript environments. So, be careful with your strict versus non-strict if you're trying to learn stuff about how the errors are thrown in those immutability cases.
Closure and Recursion
Closure
Let's now turn our attention to closure. We already sort of mentioned this several times. I wanted to give you a slightly more formal but still very, very informal definition for closure. This maps somewhat to another definition that I've given this in another one of my workshops. But, again, this is all about trying to boil this down to whatever kind of basic takeaways we can get. So closure is when a function "remembers" the variables around it even when they function is executed elsewhere. If a function accesses a variable outside of itself, and then you take that function and you send it somewhere else where it would normally have access to that variable, the fact that it still has access to that variable is closure. I'm not going to go to extreme depth, length to explain all of that. I wrote a whole book about scope and closure if you're interested in digging more into that topic. But we'll just illustrate in a very basic sense how closure's going to be something we're going to use or see being used in our functional programming. So here's an example. I have a function foo, and inside of it, I have a variable called count. And then inside of that, I have another inner function, in this case it happens to get returned. So I'm returning a function back. We've already seen the returning function pattern before, so we're just seeing that again. But you'll notice that I have that count variable inside of the function, and it's referencing a variable outside of itself. Strictly speaking, that's lexical scope. But what's important here is that that function gets transported outside of the function foo, and even when foo is finished, because foo finishes on line 9, and there's microseconds of time that happened between line 9 and lines 11, 12, and 13, and we would normally think about the whole state of foo going away, that foo would finish, and all of its internal state would just get garbage collected. But that doesn't happen in this case, and the reason it doesn't is because that inner function has a closure over that variable count. Just by simply referencing it, it gets a closure over it. And that means that that scope, that state is going to be kept. It's going to be preserved. So we return that function back. We give it the name x on line 9. And then we call it multiple times. And you'll notice that we are actually updating that variable every time. So we're getting out a new value each time. That's an exercise of closure. There are many, many other ways to think about closure. I'm sure you've seen them with your assigning click handlers to functions and referencing variables, setting up timers, ajax callbacks, hundreds of other examples. Here we see a closure because a function is remembering and able to access a variable that was around it, that is outside of it even though we take that function elsewhere, in this case, returning it out. Now something interesting about this closure that I want to point out is I that while this is closure, this is not strictly speaking what you're probably going to see as a very common pattern in functional programming because every time I call x, I get a different answer. So there's an internal state that's happening, which means I still have to track that state over time. I have to understand how many times x has been called. It's not an external state that's changing something where that state can be changed in an entirely different way. So it's not some free-range variable like count hanging out on the outside, it's hidden within a closure, but there're still side effects happening in the strictly pure sense. So the way we're using this function, we have two very, very functional concepts being married together here, but the way we've married them together is not as functional. It's a bit impure. That doesn't make it bad. I use this all the time, and many of you use this in your programming. But you should be aware if you're trying to use a functional concept, this usage of closure is perhaps not as functional, not as functional pure. Now, how could we use closure in a more effective way as a functional programmer? Same principle will apply, closure is the same principle regardless of how you look at it. But how we use it can be different. So in this case, I declare a function called sumX. It receives an x variable. And you'll notice that I'm closing over that x just like I was in the previous slide. I'm closing over that variable, and I'm returning that function. But what's the key difference here? The key difference is that x never changes. Does everybody see that? For the lifetime of my reference of that function, which I happen to call on line 7, I call it add10, for the lifetime of that function, every single time I call it, the x value is always going to be 10. Same principle, still closure, but here I'm not allowing it to make some side effecting change where I'm going to get a different answer every time. This is more functional. This is a technique for applying more functional programming idioms to our programs to make them more reasonable. So here I'm making a function, an incredibly useful function, and another one you should add to your utility library and get rich and famous, I'm making the add10 function, which takes anything that you add to it, like the value 3, and it adds it to the value 10 that it's already saved in its state, saved in its closure. It turns out that sort of technique of creating something sort of as a specialized thing that it doesn't have other stuff, this goes by fancier terms, so bear with me for just a moment. It goes by currying or partial application. Taking a function that would normally need multiple variables and presetting some of those using closure, that's called currying or sometimes depending on how you're using it, whether it's currying or partial application. They're kind of two sides of a Rubik's cube if you will. So there's some crazy complex terminology, but let's not worry about currying and partial application. Let's just worry about this idea that a function can remember the variables that it has around it. And we can use that to our advantage to save some state along with the function. The fact that that state doesn't change over time is what makes this easier to reason about.
Exercise 2
Pull exercise 2 up with me. Again, pull up the README and the ex2.js. I'll go ahead and close exercise 1 since we're not using that anymore. Let's look at the README for exercise 2. It says, Define foo so that it produces a function (we've already seen that), and that function needs to remember only the first two arguments that were passed to foo, and then the function that we've returned back, it needs to always add those two together. Parse through that README. Make sure you fully understand what it's asking for. Here's the setup. I should be able to make a function called x that's already remembering the 3 and the 4, and every time I call x, I'm going to get 7 out. Not very many lines of code, but let's make sure you have your brains wrapped around how we can use closure.
Exercise 2 Solution
First off, the README says we're going to need to care about only the first two parameters passed in. The quick and easiest way to do that is to give those names. You could've messed around with the arguments array if you really wanted to, but here I'm going to just cheat and use just the two names, which means if you pass in any other arguments, I'm just going to ignore them. Now I need to return a function. I'm going to be lazy and not give it a name because it doesn't really matter here, but function expressions generally should have names. That function as we see the way it's being used, if we look at the test cases of all the ways it's being used doesn't take any arguments, so I don't need an argument here. And the README said that what we wanted to do with that is add those two values together and return it. So that's all we have to do. Can we see that it's closed over both x and y? The parameters act in this case like local variables that have a value. Those values don't change over time. So over time as I'm calling x over and over and over again, x and y are still always the 3 and the 4 values respectively, which is what gives us that value 7 each time. As silly and stupid as this is, being able to do that at a whim, being able to put a function and wrap it around some state, and maintain that state is one of the most important things you can get if you want to use functional programming techniques, functional-light, if you will, in your programs. Having a mastery over closure is really important. That's why I said at the beginning that it's so fundamental to programming. It's so much the most important thing. I literally can't count how many different places that closure shows up in programming techniques.
Recursion
Recursion is one of those things that tends to bend people's brains. I find that sad. It's kind of like if we look at the state of most adults. Now I know in a developer crowd, we have some selection bias here. If I asked most developers, What do you think about math? I'm probably not going to get a lot of negative reactions from things. Some people are probably very pro math. I certainly as a computer scientist, I took a whole bunch of math classes. So math doesn't scare me, but if you asked the general populace about math, some people will just have a visceral reaction. And I find that so sad that math is not just something that some people, that many people would say, You know math's fine, but I don't really care that much about it. That would be fine to me. What's sad is that math has this negative connotation in some of these minds. I think recursion is similar. Recursion is oftentimes one of those things, kind of like regular expressions is another example, it's a very polarizing topics. Some developers will say, Yeah, I'm okay with regular. Maybe I don't use them that often, but I'm okay with them. And some people are like, Oh no! Absolutely not! Regular expressions are way too confusing. And recursion seems to be similar. It's another one of those things that we want to try to practice wrapping our brains around. So put simply, recursion means a function that you define. It's going to perform some action, and you want for that function to stop calling itself. It's going to call itself as part of the solution, but you want for it to stop calling itself when it reaches what we call a base case. So the two characteristics of a recursive function are that there's a base case which stops the recursion, and there's a recursive call, meaning the function is literally using its same name to call itself, to invoke itself from inside of itself. Now a closely related concept is something called mutual recursion. If recursion is a single function calling itself recursively until it reaches a base case, mutual recursion is two or more functions calling each other until they reach a base case. Same principle, just whether there's one or more. Recursion is incredibly important and incredibly useful. Make a little side note about recursion for a moment. One of the reasons I think programmers don't really spend much time, at least programmers in the JavaScript community don't spend much time thinking about recursion, one of those, of course, is it can be hard to wrap your brains around, but another reason is because many people have observed that even if you understand recursion, it may not be very practical to use in your programs for certain types of problems because there are physical limitations applied to our programs because of the nature of how recursion works. Recursion is one of those great, great, great examples of works in theory, sometimes doesn't work in practice. What is that practice? Well, in theory, you write a recursive program assuming unlimited CPU and unlimited memory. But in practice, has anybody in here got unlimited CPU and unlimited memory? Absolutely not. Doesn't exist. Even in the Amazon Cloud, it's not unlimited. So recursion seems great in theory. I can make every single thing I do recursive. But in practice, certain things can be done recursively, and other things are going to fall apart. Now there's a reason for that, and the primary reason for it is not even the CPU, the primary reason is memory. When one function calls another function, even if that function is itself, when one function calls another function, the first function call allocates in memory what we call a stack frame. Try not to get too complex with it, but a stack frame is a place in memory where all the variables and state are held, and the program counter as it's walking through. When it finishes with that stack frame, it throws the stack frame away. So when a function finishes, it throws that memory away, and it reclaims it and reuses it. So when one function calls a second function, a second stack frame allocation happens. So if you have A calling B and B calling C and C calling D and D calling E, you now have five stack frames that have been allocated, each one taking up a little more memory. Now in your programs, you probably have functions calling other functions calling other functions, but I'm willing to guess that you probably at most have, let's just arbitrarily say you probably at most have 10 levels deep in a run stack, even when you factor in your libraries. Ten, maybe 15, it's just not very common to go beyond that. So back in the day, old IE had an actual arbitrary limitation of 13. You could go to 13, but once you went beyond 13, it would just simply cut you off and say, The call stack is too deep. It had nothing to do with whether it was recursion or not. Just simply you had too many stack frames allocated, and we're just going to physically cut you off at that point. I don't know where they arrived at that 13 number. I don't know if that was something physically with the way the engine worked or whether they just arbitrarily said that's too much memory to give away. I don't know. But 13 was the arbitrary limit. Now modern browsers, the limit's up to 10,000 or 20,000. But there has to be a limit because if they didn't have a limit, you could have a runaway program that ran out of all the memory. And especially when we're talking about mobile devices. The single worst sin that you can commit is running a device out of memory because guess what happens when it completely runs out of memory? It shuts off. That's the worst possible user experience. Have you ever had an app that crashed your phone, and your phone literally restarted as a result of it? You know what they did? They ran out of memory. They didn't have any kind of memory controls, and they ran out. So JavaScript said, That's unacceptable to just simply run unbounded without memory, without any kind of memory restrictions. Because programmers can't think about memory restrictions in terms of amount of memory, they had to restrict it in terms of depth of the call stack. So in Chrome, I think it's 10,000 or 20,000 or something like that. Other browsers have different arbitrary ones. You're never going to write a program that has intentionally 10,000 calls. That would be crazy. But it's super easy to get into that case when you're dealing with recursion. If you have a program size that's 100, your call stack's going to at maximum be 100. When your program size grows to 1000, now your call stack's going to be a maximum of 1000. When it grows to 10,000, we're starting to get close to the point where you might actually, it might fail. The engine might throw an error and say you went too deep. So that's one of the main reasons why recursion has been this theoretical thing that some people could deal with, but in practice doesn't seem terribly easy for us to figure out, How do I reason about a problem that has a bounds to it? Because as computer scientists, we typically like to reason in unbounded form. We like to assume that our function, our program would run regardless of how much data there is. And if we know that there's some arbitrary limit, we just simply don't do recursion. That's one of the main reasons why this has been not a technique that has been used. It's incredibly powerful. It is powerful from a functionality perspective? Not so much. Do you know what it's powerful for? An expressivity. It makes more graceful expression of many sorts of problems that we want to solve. The recursive definition of a problem is often much more graceful to look at and understand and wrap your brain around than the iterative version, which has the overhead of a loop or whatever. They're isomorphic of each other. The iterative version and the recursive version of solving a problem, if they come up with the same result, they're isomorphic. You can transform between the two. And in fact many compilers in non-JavaScript languages do in fact do that. If you write a recursive program, they do what's called unrolling. They write a loop basically, and they keep track of the state in a loop so it's not allocating all those stack frames. That's how many of the compilers handle recursion. But JavaScript does not have that sort of technique in it, and so for the longest time, we've had this arbitrary limit. The reason I bring all this up is to say recursion's a tool that we ought to have, and it's something that we can reclaim back into our scope, back into our mindset of tools available to us as of ES6 because there's an optimization that's required in ES6. And that's actually a unique thing. I'm not sure if I know of any other example in JavaScript, and I can't really speak for any other languages, but I'm not sure I've ever heard of a case where a language specification requires a particular optimization. Optimizations are typically thought of as purely implementation details. That's up to the JavaScript engine developers how to make it happen. They very rarely get into discussions about optimization except for when a browser engine author might say something like---they're on TC39, they may say, Well, we're not going to implement that because it would be too difficult to implement based on the way our engine works. That's the sort of feedback they typically give, but you usually would never see the specification actually say, You are required to do this particular kind of optimization, so this is a very special case. It's one that we ought to pay attention to. Essentially the optimization goes by the name TCO, tail call optimization. There's another formal term for it, proper tail calls. What this says is if I have a function that calls another function, whether it's recursive or not is irrelevant, if I have one function, function A, calling function B, if the way that it's calling function B is at the very last statement of that code path, and it's returning whatever B returns. So in other words, if in A at the very end of that code path I say return B(whatever);, that's what a proper tail call looks like. When there's a return with a function call and nothing else after the function call, that's a proper tail call. And what that means is if the JavaScript engine can identify that that's the case, it can say the current stack frame that I'm using, I don't need anymore. So I didn't need to make a new stack frame for B's call. I can reuse the stack frame for A, which means if all of my recursion is done with proper tail calls, then I can do arbitrarily deep recursion with O(1) memory usage, constant memory usage, just one stack frame reused over and over again. It's massively important. It's not new to JavaScript. That's been around for decades. But it's a massively important optimization that reclaims recursion as something that we ought to care about because now it becomes practical to use if you write with proper tail calls. Not all recursion is normally expressed in proper tail calls. Virtually all recursion can be rewritten to use proper tail calls, but that technique is difficult, and we're not going to really dive into that. But I do cover that in my ES6 book. So that's why I'm talking about recursion because it's something that we should care about, not just theoretically but now practically. As engines start to get TCO implemented, it's something that we'll start to be able to revisit and use more practically in our programs. And, by the way, I do cover in that ES6 book, I do cover a technique by which you can write a function in a certain pattern where if TCO is there, it will just simply run unbounded relying upon proper tail call recursion for an arbitrary depth. But it will detect if an error occurs because you're in a non-TCO engine, and it will redo in batches. So there is a pattern for which you can write this code now that will still run okay in older browsers. It's a little more manual, but it is possible to do if you need to bridge the gap.
Recursive Example
Here's the non-recursive way to sum up three numbers. I'm going to loop over the list of them, have a running sum that starts with 0, just add them together, I end up with 12 at the end. Pretty straightforward. How can we express this same program recursively? Well, this isn't as pretty as recursive functions usually look, and part of the reason for that is that there's some JavaScript nonsense here like changing arguments into the args and that sort of thing. That's sort of ugly. But I promise on the very next slide I'm about to show you a prettier way of expressing it now that we have ES6. But importantly here, we have a base case. Our if statement is a base case, in which case we return an immediate operation that doesn't need to call any other function. And then we have a recursive call. So we have our two characteristics that make this a recursive solution. And you'll notice that what I'm saying here is that the summation of any list of numbers is the same thing as the first number plus the summation of everything else. So if I then look at that question and I say the summation of that is the same as the first number plus the summation of everything else. That's what mathematically is so graceful about recursion is that oftentimes you can express it in a very simple concept like this plus everything else. So that's what we're doing here. Line 7 is args(0). Line 8 is the summation of everything else. So I promised that we could clean up some of the code and make it look a little bit more graceful using some ES6 techniques. This isn't an ES6 workshop, but I like ES6, and I certainly like it in places where it helps us express code more clearly. So ES6 for the win! I have that ...args up in line 1. That's the rest parameter syntax, which collects all of the arguments into a real array for us so I don't have to do that crazy .slice. call crap. And then on line 7, I have that dot-dot-dot operator happening again, but now it's used in a value rather than in an assignment context. In the value context, the dot-dot-dot operator spreads, so it's going to spread out the array of args.slice into the sumRecur. I don't even need to use apply here because it automatically is going to spread out the array into those argument positions for me. ES6 is pretty cool. It allows us to express things without getting into the unnecessary details. All that array operation crap is useless. It's not helping us understand the solution. So here's a place where ES6 shines because it gets that stuff out of the way and lets us understand the solution better. And it's clearer to see where our base case and our recursive call is. So let's practice recursion now. Exercise 3. I'm sorry, you have a question for me. So this one would let us put any number of parameters, not just 3,4,5, but you could do 3,4,5,6,7,8,9.slice? Yup, it would've worked with as many arguments as we passed it. By the way, just as a little nuance nitpick, you've heard me use these terms already, but I just want to make sure everybody understands why I'm choosing the different term. The argument is what you pass in, the parameter is what receives it. So in the function declaration signature on line 1, args is a parameter. Line 11, the 3, 4, and 5, those are arguments. Just make sure we understand arguments are passed in and assigned to parameters.
Exercise 3
Exercise 3, let's pull up exercise 3 and do a tad bit of experimentation with our understanding of recursion. Exercise 3--Take mult, which is already defined, turn it into a recursive function that can work on as many arguments as necessary. So what is mult, how is mult currently defined? It only works with three parameters, x, y, and Z. Line 7 would create a problem for us because it's still going to return 60 instead of 360 the way we expect. You could do this iteratively with a for loop, and if that helps you, turn it first into an iterative solution if that helps. But the exercise here is to ask yourself, What is the multiplication of all these arguments? And, by the way, it's going to look an awful lot like the last slide we just looked at.
Exercise 3 Solution
First off, the main goal of this is to be able to handle an arbitrary number of arguments. So I'm going to take those off, those named ones, and in their place, I'm going to take advantage of that ES6 spread, in this case it's called the rest operator. I like to call it the gather operator by the way. But ...args, that's my gather operator, so I'm going to gather everything together into the array args. Now, all recursive functions, the main defining characteristic is that they have a base case, some way to know that it's time to stop so they don't run forever, and they have a recursive call. Let's deal with the base case. If the length of args is less than or equal to 2. That means I have all that I need to make a multiplication between two elements using the multiplication operator. So I'm just going to take args of 0 and args of 1, multiply them together, and return that result. So I'm going to simply say return args(0) * args(1). I was asked on chat over the work break what about other of those trivial-based cases like if you passed no arguments or only one, we're not worrying about those trivial-based cases here. Obviously, those are easy to deal with. So that's the path we're returning if we have the base case. Otherwise, what we want to do is return the args(0) * the recursive call, the mult recursive for the rest of the list. So I'm going to simply say return args(0) *, and the way I call mult with the rest of the list is to say mult, and I'm going to use dot-dot-dot to spread out an array, and the array that I'm going to spread out is not just args, but it's actually args with the first value sliced off. Something interesting to note here, by the way, this will be something that we look at more later, but you know how we talked about immutability? We already start to see some principles of that happening in the JavaScript methods that are built in to the language. Slice, for example, is a prototypal method available to all arrays, all real arrays, but what does it do? Does it mutate the existing array? Actually, it creates a whole new array. So slice is one of those immutable methods because it takes the method as its context, but it doesn't change that array, that context. It produces a whole new one with the subset of what you've asked for. So slice is one of those immutable methods. There are other methods like, for example, splice--that's with a 'p' in there. Splice actually changes the array in place. It doesn't create a new one. So we can see even built into the language, we see differences between using an immutable principle and returning a new value versus changing something in place. So slice is one of those cases. Questions about exercise 3? Is this now tail call optimized because you do mult at the end? Perfect question! Thank you for asking that. No, this is not a proper tail call. Great question and a good segue back to what we were talking about before the work break. The reason why this is not a proper tail call is because after mult is called, another multiplication has to occur. It's only a proper tail call if the very last thing to happen is the function call, and then its return value is immediately returned back. Can this be rewritten in a proper tail call form? Yes. Am I going to do that now? No. If you want to know more about that technique, how to rewrite into proper tail call form, check out the ES6 book. I think it's Chapter 7 if I remember correctly. So recursion--nice graceful way of expressing a problem, kind of getting some of the unnecessary details out of the way. ES6 helps us out a bit with the rest and spread operators.
List Operations
List Transformation
These next four I mentioned we're going to talk about dealing with lists. This is a very common refrain that you'll hear from a functional programmer. We oftentimes will express an operation that we're going to do as a list operation. That's true even if there's only one value, which is interesting. Even if there're no values. You could have an empty list that you express an operation on, in which case nothing occurs. You could have that same list have one value in it, and now that operation only occurs on that one item, or that list could have a million items in it, and the operation's going to be performed across all one million. But the nice graceful thing is if I always deal with an operation in terms of a list context, I don't need to special case all those places where it might have no value or only one value or a million values or a billion. I don't to special case anything because the operation is, if it's defined as a pure operation, it doesn't really matter, does it? So, again, you see that refrain back to the notion of pure functions. If we could define an operation that we want to apply to every single item in a list, and we define that as a pure operation, then it doesn't matter how many items are or are not in the list. It also doesn't matter--this isn't an implementation detail but just to tweak your brain a little bit--it also doesn't matter what order those things happen in. If I'm going to do a list transformation, does it really matter whether I go left or right or start from the middle and go outward? If I'm using pure functions, it doesn't matter. As an optimization, if you had a gigantic, gigantic list, like billions of elements, and you wanted to take advantage of a bunch of multiprocessing, like parallel processing, a map operation, a transformation operation with pure functions is something that you could send each one of those things to entirely different threads and do a whole bunch of stuff in parallel and then assemble everything back together at the end. So you can start to see why list operations are an attractive abstraction for our programming. Here we want to talk about the transformation operation. Transformation--I don't mean anything more complex than this--a transformation is taking one value, doing something to it so that you get a different value out. That's all that I mean. So what does that entail? Well, the types of transformations that you could define, the types of pure transformations that you could define, are almost limitless. We've been dealing with numbers because they're a convenient way of doing so. So what's a transformation that we could do on a number? Well, you could take any number and double it. That's a transformation, double is a transformation. You could take any number and divide it by 3. That's a transformation. You could take any number and increment it by 1. You get the point. You could define a whole bunch of different mathematic transformations, pure transformations on a value. Key characteristic here--a transformation, what is called in functional programming a mapping or a map, is you are going to end up with a new list at the end that has exactly the same number of values as the original list, whatever they are, empty, one, or a billion, and for every single value in the new list, there's a one-to-one mapping from the original value to the new value, and that mapping is done with the function that you provide, your pure transformation. That's what a transformation, that's what a list transformation looks like. What if we wanted to write our own transform function? How would we do that? Probably not too surprising. We first define on line 1 a doubleIt. There's our pure transform function. Functional programming terms, they often use this fancy word called predicate. Who cares? It's just a function that does something. It's a pure function. So I'm not going to talk about predicates. But if you've read functional programming, you might have seen that word before. So doubleIt takes in v, multiples by 2, returns the value back. Doesn't get any more simple than that. I'm sure somebody out there is like, Why didn't he use an arrow function? I've got a lot of writing about why I didn't use an arrow function. But transform's the one we want to pay attention to. Transform takes an array and a function reference, whatever, it doesn't matter what the function is. This is a general utility that we call transform. And you notice, by the way, that I intentionally use an immutable programming technique, which is that I declare a new value, a new list, and I add to that new list as opposed to operating in place, changing the existing array. If I operated in place, I could come up with the same end result, but I would not be doing functional programming because I would not have used that immutable technique. Do you get the point? Everything that we're talking about is all building upon itself now. So now we're starting to see why immutable was useful to us to understand, not as a characteristic of a value but as a characteristic of how we use a value. So here we choose to use it in an immutable fashion, not change it but just simply iterate over it with a for loop. So I can pass in my array 1,2,3,4,5 like I do down on line 12, and a reference to any given function, in this case doubleIt, and what I get back is a whole new array where that transformation has been applied across all of them. And for our intents and purposes, we don't care how the engine did that. We don't care that it was a linear loop. We could have done it recursively. I bet if I gave you the exercise to write a recursive map, you could probably figure out how to write a recursive map. Iterative is a little bit more illustrative here, but this could have been done recursively. It could have been done massively parallel on different threads. It doesn't matter to us, does it? All that matters is that it's a pure function. It's a pure transform. On the end result, we get a whole new list with the transforms having occurred. And every single time we do that exact same transform, we're going to get the same result. So we could make our own transform utility. Thankfully, the language provides one for us. Now some people have quibbles about this particular utility because it was implemented as a prototype method on the array. It's called map. I like the name. I can see why it's very JavaScript-y to have it be a method so it automatically knows its context. You don't have to pass in the value. Functional programmers would tell you, I have to pass it as a context method instead of passing in the array that I care about? You know why this is considered to be a violation of functional programming? What did I talk about earlier today where this would create a problem for us? Can anybody spot it? Changing the array itself and not returning --- No, it's not actually. It's actually returning us a whole new array. That's good. So it's not the immutability thing. There's a different problem here. It cannot be curried? What's that? It cannot be curried? I think you're on the right lines. Let me just take that one step further. It makes it harder to compose, doesn't it? Composing with these is done through chaining, which is a different kind of composition that we talked about before, which is we take one function, and we take its result and put it in another function. If I take this function and I try to pump it in as the parameter to another function, it doesn't really work that well, does it? So I end up having to chain off of the value. That's the way, the JavaScript-y way that we do composition. And functional programmers always look at that and say it's yucky. You can accomplish many of the same things, but I understand the argument why this form of composition, this prototypal method type of composition can be less attractive. There are places where you end up having to do a little bit extra work in the wrappers that you create that a truly functional programming language wouldn't have to worry about those details. So, just something to be aware of. But the nice thing is that it's super easy to use. It's a method that's available on all of our arrays. And if you're familiar with that fluent style of API, that chaining style where you call one method, then call another method, and all of that, if you're familiar with that method, that form of composition, it's pretty straightforward. That's exactly what you're hoping for. So, map is going to, yes, create a whole new array for us just like our transform did, and return that as the return value here, which means if we wanted to do two mappings, we could do one map, and then we could call .map again at the end of line 3, we could call .map again, and we would be operating on the second array, the newly created array, and we would return a third array. So you can chain these things together. That's how you can compose these operations. Side note: Functional programming talks about this notion of being able to take two mappings, for example, two mapping functions like doubleIt---so maybe there's a doubleIt function, and maybe there's an addOne function, two independent mappings that we might produce over a set of lists. You could say .map(doubleIt) .map(addOne). You're going to get the end result. You're going to get an array that has everything doubled plus one. But implementation-wise, you're going to have to run through that list twice, and you're going to create an intermediary array that nobody cares about because it just gets thrown away. So implementation-wise, there are some things to be desired. So functional programming will talk to you more in depth about this notion of composing the doubleIt with an addOne function. You compose those two functions into one single function so that I do both mappings at the same time. That also has fancy terminology that we won't get into. But just so your brain knows where to be looking, I'm putting little bookmarks there for you as you study functional programming more. That's one of those places. If you see yourself doing .map, .map, .map, .filter, doing a big old chain of things, there are ways to compose those different steps together and have a higher efficiency with your implementation. So there's our transformation. Any questions about transformation? Mapping? It's pretty straightforward. I use maps a lot. It's very, very useful. Transformations can be almost anything that you can imagine. So here we're transformation numerically. What would a transformation of strings be? Well, we could do string concatenation, or we could uppercase everything, all the characters in a string. So if you had an array of strings and you wanted to lowercase everything. You could pass in a function that takes a value and returns the lowercase of it. That would be your predicate. That would be your transformer function. And then just simply map over your array of strings. We can have even higher order transformations. This is hearkening to some stuff that we'll talk about when we do an asynchronous programming workshop. But in the asynchronous programming workshop, we'll talk about even the notion that higher order ideas like, What about transforming a function? What would it mean to transform a function? We might need to wrap that function in something else that does something different. So I'd take a function in, and I get a new function out that does something different. What would it mean to transform a promise? Well, transforming a promise could be like taking a promise and wrapping it around another promise or performing some operation. So whatever value you start with, there is a set of things that you can do with that value that are transformations. And you might want to model that in your program as simply putting one or more of those values into a list and using one of these functional tools like map to do the transforms. Does it make sense? Excellent! So, that's transformation.
List Exclusion
Let's talk about exclusion. I take a list, and there are certain items that I want to keep in the list and certain items that I want to exclude from the list. I want to filter those items out. You might end up with a list that has a whole bunch of numbers in it, and in certain places because of whatever you were using to create that list of numbers, you might have some null values or some undefined values, and you want to filter those things out because you only want to produce your operations across a list of real numbers. So you have some things that you want to filter out. You might want to filter out anything that's not an odd number. You might want to filter out the evens by saying my predicate is odd. So here, line 1, my isOdd function takes advantage of the mathematical modulus. Modulus 2 is going to return 1 for an odd number, 0 for an even number. So what I'm saying here is if the number returns 1, then my expression there on line 1 there, that expression returns true. So the way that we're going to create an exclude function is your predicate, your function should always return true if you want to keep it and non-true, false, if you don't want to keep it. So you're making some decision based only on that value. This is important. You don't have any other context by which to make that decision other than the value itself. That's when a filter applies. If you have to make the decision about inclusion versus exclusion based upon more complex pieces of data than just the value itself, you're going to want to reach for a different tool than filter. We'll talk about that in a little bit. But if all we need to do is look at the value itself and decide whether or not we want to keep it, then we can simply use a utility like exclude here. Exclude looks an awful lot like our transform did before. It takes in an array, takes in a predicate function called fn. You'll notice that I'm producing a new list. Again, this is an immutable operation, an immutable principled operation. So I create a new list, and that new list has potentially fewer items in it, maybe even none. So I only push into the list on line 7, I only push into the list if the function that I call with that value returns me a true value. So line 6, I call the function that you passed in. I give it the value that I'm currently iterating over. And if you give me a true value back, I'm going to include it. Otherwise, I'm going to leave it out. Pretty straightforward. So we call exclude down here at the bottom, and we end up with an array list of 1,3,5. Questions about our exclude utility? Now could you do exclude as a recursion? Think about that for a moment. I bet you could. Or I hope you could. I won't assign that as a formal exercise, but maybe over the next break, think about these operations. I'm doing them in an iterative format with for loops because it's very illustrative, but how would you do it recursively? So we're going to exclude anything that's not odd. We're going to exclude the evens from our operation. Turns out this exclusion, this filtering is built into the language by way of the method called filter. So we don't have to write our own. It's available to us. So now we have map and we have filter. Now that you've seen the implementations of them, it makes a little more sense to you how to use them, right? By the way, just as a little side note, my implementation of exclude and transform so far has a little bit of corner cutting for illustrative purposes. They're not exactly the same. There are other parameter values that are passed in. There're other corner case handlings or whatever. The purpose is to illustrate the higher-level concept rather than to create a replacement for these mappings. Exclusion's a very common thing. So let's say you start out with a list that's produced by some--- you're a consumer of some list or somebody passed you a list. The first thing you might want to do is transform all the values. So you might call a map on it. And then you might want to say after that transformation, there's some characteristic of those things where some of those are not ones I'd want anymore, so you'd call .map, and then you'd chain off of that and call .filter. And you'd end up with an array that potentially had fewer items in it. So that's how we can compose those two operations together. That's one way of composing. Of course, you could probably think about ways to compose map and filter together. That's much more complex than composing two maps together, but it's not impossible to do. Questions about exclusion? Did we have a question about transformation in the --- There was kind of just a more general one. He's basically asking, Are map, filter, reduce, some, every, forEach the common functional approaches to problems, or are they notions that come from a different more functional domain? I'm not sure I understand the question. Are map, filter, reduce, some, every, forEach common functional approaches to problems. What's confusing me about this question is that it seems to be asking the same thing in both clauses. Are they common functional, or do they come from a functional domain? That seems to be the same thing to me. So I'm not sure I can directly answer the question. They're a few seconds behind here, so he might add to this. So let me just speak to it more generally if we're waiting for another question. Some of those in that list are very functional. Some of them are more JavaScript-y. Map, and we're about to talk about reduce, are very functional. You'll find that in virtually all functional implementations. Filter--pretty common. Sometimes filter isn't added, and you can just make filter as a--- filter can be implemented based upon reduce, which is what we're going to talk about in just a minute. So some implementations will only provide you the two primitives of map and reduce, and then you build other higher-level primitives on top of that. So he added to that there. They do absolutely come from functional programming techniques. The question, Do they come from functional programming or are they just convenience methods? The implementation of them as chained prototype methods is about convenience. But the way that they do the thing they do, that is very, very functional. That's an adaptation of that functional mindset into a JavaScript idiom.
List Composition
Another thing we might want to do with a list of values is to compose those values together. Remember I said earlier, so I want to remind you, we're not talking---when I say list composition, I don't mean composing two lists together. We have a method for that, it's called concat. But that isn't as traditionally thought of from a functional perspective. So what we're talking about here is composition of items within a list. Either a full composition, which is what most people mean by it, or sometimes it's just a partial composition. That's also valid. And that goes by the name in functional programming of typically it's called a reduce. So what are some examples of a reduction that we might do, a composition that we might do across a list? Say I have a list of numbers. Remember I had a transform where I multiplied all those numbers by 2. What if now I wanted to say I want to take this list of numbers and add all of them together. I want the summation of all the items in my list. You can express the summation of items in a list as a reduction. It is a composition of values. Now what do I mean by that. I'm not trying to be too terminology heavy here. Composition is when I take value A and value B and I smoosh them together into value C, whatever that means. So for numbers, it might be adding or it might be multiplying or it might be dividing. For functions, it might be passing one function into another function. For promises, it might be chaining one promise off of another promise. There's a nearly infinite number of ways that you could think about taking any two arbitrary values and composing them into some third value. Again for illustration purposes, we're using numbers, but this is a more general concept, composition of values. And we turn to the reduce function to do that. Now, just as a quick heads-up, the reduce function is more complex than the previous two. That's why I did the first two. It's more complex than the first two, and it is possible to use reduce in a non-pure way. In fact, it's possible to do all of these, but it's kind of easy to do non-pure programming with the reduce function. So it's a tool that has more power to it but also a tool that you have to use responsibly if you want to functionally program. So assuming that you're trying to be functionally program-minded about it or, as I call it, functional-light programming, assuming that's your goal, you have to be a little bit more careful about how you use it. So list composition, reducing. What would that look like? Well, I could have a predicate function like mult. And importantly where because I'm doing a composition, that means I need two values. So I'm going to have an initial value, which I'm calling x here, and another value calling y. Now I've named them x and y because in the problem domain of multiplying, we usually think about x * y, but if your problem domain was something different, you might name those parameters differently. The most common name for those parameters to the predicate that's used in the compose option or the reduce function, the most common name is they call the first one the accumulator and the second one the value. So let me explain how that works by way of looking at the implementation of it that I have here called compose. You'll notice compose actually takes three parameters, not two. It takes an array, it takes a predicate, but it also takes an initial value. Why? Because this is still going to be called against every element in our list, but the first element in the list needs to have something to compose with, doesn't it? We haven't done any composition yet. If we're going to pass it into a composition, we need to pass something else in. Do you know what we're going to pass? An initial value. And then those will be composed together, and now we have a running total, which is why this is oftentimes referred to as an accumulator. We have a value now that was the result of the first composition, and then we have the second item in the list. So we take those two and pass them in. And those two get composed. And now we have this as our value and the third item in the list, and those two get composed. So that's how it works. That's why we need an initial value. What would the initial value, the most appropriate initial value be for summation? 0. What would be the most appropriate initial value for multiplication? 1. Everybody see why? Because those are both the identity values in those operations. Zero doesn't make a change to the summation. One doesn't make a change to the multiplication or the division. So great, excellent! What about a function composition? What would be a good initial function? A noop function, which returns everything back that it got. So the more complex your value types, the more you have to think carefully about what would my initial value be? If I were doing a composition of promises, like chaining them together, what would my initial value be? An initially resolved promise for example. So, when we're thinking about composition, you have to also think about the initial value. That's an extra complication. I've got my total here, which because I'm doing math, I can just do mathematics here. I would need to have a more complex implementation if I were doing something non-mathematic. Well, maybe not a different implementation, but I'd probably call the variables different, and I wouldn't call it total or whatever. But you notice that I call the fn function with whatever the current value of total is plus the new value, and then that's what gets assigned to total. So that's how I do that whole compose, compose, compose thing. And then I return total back out. So down I'm calling it on line 12, I just pass in the array 1,2,3,4,5, my predicate mult, and I start with 1 because that's the most appropriate initial value for this operation. It's important to note that this is what we would call a full reduction or a full composition meaning that we started out with a list, and what did we end up with? Something very much not list, in this case a number. It's interesting to note that the reduction here when it's a full reduction like that, it's not really chainable with the other functional programming things, is it? Here's where we start to see the nitpicks that functional programmers have because now we have a breakdown in the consistency of our compositional pattern because if you needed to do some other mathematic operation like a map on this, and the end result was a number, you can't just call map directly off of it. You're going to have to take that and put it in an array and then call map on the array or something. So there's some weird inconsistency. Usually your reduction, especially if it's a full one, usually that's the last step in your chain anyway, so it's usually not a big deal. But there have been places where I've had this issue. So the other way to think about it, and I don't have a code snippet for this, but it is possible that a reduce is not a full reduce. In other words, the end result of your reduction might just be another list. An example, we're going to come back to this later, but while you're thinking about it, an example--what if I wanted to perform a unique operation on a list? What if I wanted to filter out the list and say this list needs to only have unique values? I need to turn it from a list into a set basically. Well, we couldn't use filter. Why couldn't we use filter? What was the one characteristic I said about how filter gets used to solve problems? Remember filter can only make its decision based upon the value itself. That's the appropriate way of using it. There're ways to bastardize it, of course, but that's the appropriate way of doing it is making a decision based on the value itself. Well, we can't unique an array based on knowing the value itself, can we? We need something more context. So actually uniqueing can be implemented more effectively and more traditionally with the reduce because what we could do is our running accumulator could be an empty list, the initial value could be an empty list, and we could stick something in the list only if it's not already in the list. So we could run through the entire original list looking at values, and every time we're about to put one into the new list, we ask, Is it not in the list yet? Is it not in the set yet? That's an example of uniqueing. So that's what I would call a partial reduction because the end result is still a list, but it's not a one-to-one mapping, it wasn't a filtering based upon one individual value, it was something more complex. That's what I meant early when I said reduce is way more complex, more powerful, but there's also a bunch of ways that you can use it in impure ways. So you have to be careful about that.
List Iteration
List iteration, I have the exclamation marks here because I want to remind you that even though this is one of those helpers and it oftentimes get lumped in with the other functional programming things, most people say don't do this one, and you'll see very quickly why. If I made an iterate function like, for example, I wanted to take my array and log out all the value in the array because you notice I'm not actually performing an operation on the list to change something. I'm just performing an operation with the value of the list. Well, what's the only way that doing that would mean anything? It's if my predicate had a side effect, right? Some side effect, whether it's a console.log or changing it in the original array like an in-place change or something like that. The only reason that a list iteration would make any sense and do anything useful to us at all is if the predicate had a side effect. But you can probably spot why that's not the favorite tool in a functional programmer's bag. So most functional programmers will tell you never use the forEach because it's all about side effects. Now the problem with that is that sometimes I do want side effects, which is why I call this functional-light programming because I don't want to be restricted to only doing purely functional stuff. There are times when I want to iterate over a list and have some side effects, and that's why I use forEach. It is a tool in my bag. But I use it knowing that it's not the same thing as my other functional tricks. It's different, and I use it that way on purpose. There are people that will tell you, No, no, no, you should iterate with a map. Can you iterate with map? Of course you can. You can iterate with map where its predicate returns the same value. Whatever function you pass as that predicate, if it returns the same thing it got, that's the same thing as iterating, right? That's what a functional programmer tells you that that's what you ought to do. No, that's not what I would do. ForEach is the better tool if I want to produce a side effect for each thing. So that's the forEach function. Any questions about our list operations? Those are other tools that we have. So if we go back over it, we've gone over a whole bunch of these little basic things. We haven't really fully covered how we put all of them together. That's what exercise 4 is going to try to bring some of that to more concreteness, and we'll do that after our lunch break. But I just wanted to point out that we've covered quite a bit of ground here in just a couple of hours. We've talked---from the very beginning, we talked about pure versus impure and side effects. And then we talked about functional composition and how we can compose the function to take it from impure to pure. We can compose two functions so that we're not creating impure side effects. Then we talked about immutability, and we talked about the value of, not just the immutable value but the immutable way that use the value, that's the more important part to a functional programmer. Then we talked about closure, keeping state inside a function. We talked about recursion and why recursion can be useful, a graceful way of expressing our programs. And then we talked about these list operations--transform, exclude, transform's called map, exclude's called filter. What's compose called? Reduce. And finally iterate is called forEach.
Exercise 4
Hopefully some of those ideas of functional-light programming have been stirring around in your head. Closures and recursions and immutable value usage and things like that. We're going to finish up our workshop with an exercise, exercise 4. This is basically the last thing we'll do with this workshop. And the goal here is to sort of walk through some steps that make you practice that and to borrow the term to compose those ideas together a bit into some---it's still silly problem solving. We would be here all day if I tried to give you like a real program and tried to in context do that. So they're still silly, simple academic-like examples, but hopefully they're a little more concrete, and we can start to figure out how these things fit together. So we're going to start with this empty ex4.js. And rather than this being an exercise that I just turn you loose on for the next 30 minutes and have you do yourself, we're going to kind of make this a little bit interactive. I'm going to say some stuff, and I'll stop for a minute and let you type. And then I'll say some more stuff, and that kind of thing. So this'll be an interactive thing, roughly probably 30-40 minutes-ish of us discussing, and we'll figure out when we've gotten all of the usefulness out of the exercise, and then we'll wrap up. So the first thing that we're going to start with, and if you follow along with the README, that's fine. If you want to jump ahead, you may miss a few of the points that we're making, so I would encourage you maybe not to do too much of the jumping ahead in the README unless you feel you're ready to go on to the next step. But you can follow along with the README because that's basically what I'm going to do. So what we're going to start out with---and one other thing to kind of give you a heads-up on. This is another one of those things that in functional programming I've observed from the outside. I'm coming at this all from the reverse angle. I didn't start out learning functional programming. Douglas Crawford has a great quote on this topic, I don't know if it's originally him, but he has a great quote on the topic that the problem with functional programming is that as soon as you understand it, you stop being able to teach it. And there's humor there, but there's also some truth there because as soon as you really understand it and you get steeped in all the academic tradition and the terminology, it's really hard to switch back. So rather than go that direction and be ineffective at teaching, my goal is to come at it from the back-end, from the reverse. So I don't whether---I'm not going to get certified as a functional programmer any time soon, but hopefully some of these things are starting to fit in a little more concretely for you. But one of the things that I have observed from that back-end perspective, coming at it from the reverse, is that there are sometimes when an abstraction gets created, and you look at the abstraction, and you think, What even remotely prompted somebody to come up with that? And the best metaphor I can give you for it is if you remember back maybe in high school or whenever when you took an algebra class or maybe it was a calculus class or something like that, but you'd be working along with some problem, and you're trying to solve for x on one side of the thing, and it's not clear how you're going to---you get to some point, and the teacher's working through the problem with you, and it's not clear looking at it that there's an easy way to isolate the x to solve for x or whatever. And all of the sudden, she's like, Well, if we multiplied by y-cubed on both sides, and you're like, What? But she does it, and all of the sudden, it just magically makes everything---it unsticks the log jam and then you can move on. And that used to drive me insane. How did you just pull this rabbit out of thin air that just magically worked? What was it that did that? And I don't have a terribly great answer for it, but it is to say this, I think functional programming, my perspective, one of the most important techniques that you can get or one of the most important skills that you can get at functional programming is developing instincts for things. And that's basically what my algebra teacher said was, I've done it enough that when I see a problem that kind of looks like this, I just remember that I can invoke this pattern, and it helps to unstick the solution. So really functional programming is kind of like that. Sometimes you invoke things out of thin air that don't logically flow directly from what you just did, but they unstick you. They create an abstraction that unsticks you and makes a solution to a problem work. So there're going to be a couple of these things where I'm going to pull something out of thin air, and you're going to be like, Why are you passing that function in that way? It's really more based upon those instincts. That's really what I'm trying to get across with this whole workshop is to help you understand what those basic principles are in functional programming and let you try to weave those together into instincts. When you look at a problem, I want you to have the instinct to say, Oh, I've seen that before. That's a transform. I know how to do that. I can pull out map. Or even if it doesn't look yet like a transform, but you're trying to figure out the cleanest and most straightforward way of expressing a solution to the problem, maybe something will go off in your head, and you'll say I have an instinct that this is probably something I could do with map. I can wrap this value in an array and do it with a map, and that will make more sense. It's those instincts. It's those pulling solutions out of thin air thing that seems to be how functional programmers are so good at what they do. That's just an observation that I'll make, and we will see a little bit of that as we go through.
Exercise 4 Solution, Part 1
So to start out with as the README says what we're going to have you do is write two functions, I don't care, just call them foo and bar for now. And each one of those functions should return a different number. So have one of them return 10 and one of them return 42. I don't care. Pick your favorite numbers. But literally just have it return a number. Your functions probably look something roughly like that. Now there's something interesting in functional programming, there is something interesting about the nature of having a value wrapped in a function. Kind of like I said earlier that there's an instinct to say, well, if given the option, I try to stuff values into a list because I know lists can---I can do certain sorts of operations on lists that are quite handy. In the same way, you also sometimes will say, If I can wrap a value in a function, and I know if calling that function will give me a value, that starts to produce patterns of more composability in our programs. It's easier to compose two functions together that return numbers than it is to simply compose the two numbers. That's what we're getting at. So this was one of those pull-out-of-thin-air things. I didn't tell you to just write the two values 42 and 10. I told you to wrap them in functions that produce those values. It won't be clear why yet, but as we go along, maybe you'll see a little bit more of a reason why. So item #2 in the README. What we're going to do, we're going to write a function, which we call add, and needs to take two numbers and return the result. We'll just leave our function bar and function foo along for a little bit. Add needs to take in two numbers and add the two together. So let me give you your 15 seconds to write that. I'm going to take away points from you if you pulled out underscore and you're like, Oh, there's some underscore function I can do for this. We're writing our own code here. We want to learn stuff. That was just a little dig. I apologize. I'm sure there probably is an add function in underscore. Add(x,y) is what I'm going to name them. How does add work? Pretty straightforward, x + y. Not rocket science here. This step progression that I'm going through is intentional. It's intentional to lead you two places where you start to get some of those instincts, so don't jump too quickly beyond what I'm doing and think that it's too remedial. This is a very carefully, intentionally designed exercise. The next thing we're going to do, I could observe that an add that takes two numbers, but an add that takes two functions would be even cooler. So I'm going to make an add2. And that function needs to take in two functions, and it needs to call both of those to get their values out and then return the sum. So your function might look something a little like this. I'm going to call it fn1 and fn2, and it's going to use the add function. I'm going to call fn1, I'm going to call fn2, pass those to the add function. Again, not totally clear to you why I decided to make add2 use add rather than just do its own addition, but in functional programming, you see this all the time. Every abstraction that you define is a combination, it's a composition of some lower level abstractions. We're always looking for that turtles all the way down sort of path to defining our utilities. So why repeat the plus operator when I already have a function that can compute that? Now I know this stuff seems totally remedial. It's about to get, here in a couple of steps, about to get a little more challenging, so just hang on with us. So add2 passes those values in just like we did. Now let's move on to item #4. Those two functions that we defined that had---before we do that, let's just verify that we know how we can use this. How do we use the add2 function? What would the call set look like? Add2, what am I going to pass into it? Foo and bar. And what am I going to get back out in this case? 52. So everybody make sure we understand that. Now, let's take the foo and bar functions and replace them with a new function. Now I don't care what you call this function. I'm going to still call mine foo, but if you have a different, better name that you like, the name doesn't matter here. But here's what this function needs to do. This function needs to take a value, a single value, and it needs to return back a function that when that function is called will give you the value of it. So take a moment to figure out how you would define function foo in that way. So your function foo might take a value like x, and what does it need to do? It needs to return a function that doesn't need to receive any parameters. And what does that function need to do? Return x. There's our closure again, right? We're producing a function that will remember that x value, and every time we call it, it will give us that x value back. Here's a little hint preview towards the async workshop--that's called a thunk, and we'll talk about thunks. But when we get to thunks, I want you to remember back to this idea of a function wrapped around a value. So now we don't have a foo and a bar anymore like that that we can just pass in. What do we need to do to call add2 now if we want to still produce our 52 result? What do we need to do? Foo(10) and foo(42). Everybody see that? I'm actually calling those functions and producing functions that get passed in.
Exercise 4 Solution, Part 2
Do we not call add inside that? We are calling add right here on line 12. Now it starts to get more interesting and little more challenging. We'll see how close you've been paying attention. I now want you---because we have this add2 function, which takes two functions, and if I wanted to be able to take three functions, I could make an add3 or an add4 or an add5, but I want you to make an addn. I want you to make a function that takes a list of an arbitrary number of functions and adds all of those function return results together, but that function has to use add2. There're a variety of ways that you could define your addn. Let's just leave the add2 here so that we see that in comments. There're a variety of ways to define the addn that we're talking about. But what I want you to do is instead of it receiving parameters, it's going to receive a single array, so I'll give you that hint. It's going to receive a single array. That array is going to have one or more, actually zero or more (but we don't care about those trivial cases) functions in it. And you need to go through and calculate all of the sums of all those things using only add2. Of course, you're going to have to call add2 quite a few times, right? So you might choose to set this up as a for loop, and that might be the first way that you want to write it. So, try it with a loop first. Try yourself setting up your addn function to calculate it with a loop. And then you'll notice the README says after you've done it with a loop, try it with recursion. And, finally, you may try it with map and reduce. So here's what I'm going to do. I'm going to stop talking for like 5 minutes. I'll let you try it with a loop first and maybe move on to your recursion attempt at it. But then we're going to talk through looping, recursion, and then how we might use map and reduce. So addn--I might have slightly misspoken before I gave you the work break. We want this to receive an array of values, not an array of functions, but either way, the solution's going to end up being mostly the same. So if you sent in an array of functions, then there's just one less step to do. If you're going to send in an array of values, there's one extra step to do. Either way. Let's assume an array of values is what we're going to do, so let's assume we're going to call something like addn, and we're going to pass in the values 10 and 42 and 56. And maybe we want another value like 73. So we expect that to give us 181 is the sum. How are we going to do this with a loop, with an iteration? Well, of course, we're going to need to keep track of it. We're going to need an accumulator. We'll call it sum and start it out at 0. We're going to say for (var i=0; i less than arr.length, i++). I'm going to take the entry in array, and I'm going to call it with foo so that's going to produce me a function wrapping around the value. That's the step you don't need to do if you're assuming you've already received functions, so either way. Actually, I did have to do the accumulator. So if you already have a function, then that step won't be necessary. But what we're going to have is the sum is going to need to call the function that it gets back, so what we're going to do is say---I'm sorry, not call the function, we're going to say add2, and one of the things that we're going to need to pass in is the sum, the current sum wrapped in a function, so we're going to need to say foo(sum). So that particular part is optional if you're already passing in functions. This part is not optional. And sum is equal to add2. You could've also stepped through the loop two at a time passing two elements in the loop in and getting their sum and just doing sum plus equals and add2 and passing those in. That's also an equally valid way of solving it with a loop. But at the end of the day, we can simply return sum. Well, what about if we wanted to do this with recursion? In recursion, we're looking for a base case. And what's our recursive definition for the addition of a list? The addition of a list is the addition of the first two items plus the addition of everything else. So it's the item plus everything else, and then it's the item plus everything else. That's how we add those two together. So our base case would be if the array that we've been given is length 2 because then we know what to do with that. So arr.length is 2. And you could for good measure if you wanted to say less than or equal to 2. But if we were given an array of at least in this base case of at least having 2 in it, then all we need to do is call add2, and if we've been given a set of values, of course, we're going to need to wrap those. So we're going to need to say foo(arr(0) and foo(arr(1))). You don't need those foo calls if you're passing in the functions already prewrapped. What's our recursion case? We're going to say return, and we're going to call arr(0) as a function, right? And then addn, so this is addn. We're going to need to have an array that has---sorry, we're not going to call it. We're going to need to add in an array, pass in an array. The first element in the array is going to be what was in array position 0, and the second one is going to be everything else in the list. So, that's going to be arr.slice(1). Now, addn returns us back a number, so we're going to get numbers and numbers and numbers coming back and back and back to us. If we're dealing with---I'm getting a little bit ahead of myself, so I'm going to try to not jump too far ahead. I don't mean to pass these separately, that's not what I meant to do. I meant to call apply. That's why I'm getting ahead of myself. So we want to pass in a single---we want to spread out a single array with that as the first value and then add2 in it. I'm sorry, getting a little bit off track. I think I've got my brain wrapped wrong, so let me just double-check here. There's that function. We'll start back here. I'm going to call addn with a single array where the first element in the array is a function that returns add2 with arr(0) and arr(1). So that's our first element. That's an array with a single element in it. And we want to concatenate that with everything else that's at the starting position 2. So I've taken the two items off the front of the list, passed them to add2, and replaced them with a single calculation for it, and then I'm going to keep doing that recursively. So I can concatenate that with the rest of the list, arr.slice(2). That should be what I show. So, single array with a function in it. That function is going to calculate the addition of the first two elements in the list. And then we are going to pass the rest of the arguments in. So if we do that the first time, we're going to go from a list of four elements to a list of three elements where the first item in the list is a function and the rest of the elements are either the values or the wrapped functions depending on which one you did. So we've gone from four down to three, and then we're going to do it again where we take that function and we add in another function, and so we go from three down to two, and then we're going to hit this base case, which is just saying simply pass in those functions, and we get the value back out. On 22, couldn't you call foo? The reason we don't want---if you call foo, you're going to be calculating the add2 now, so you could do it that way. What I'm trying to show is creating a function that will sort of defer the calculation until the final endpoint. So we can also observe as I have done here in the comments that---I mean, the other way of writing this is we only need to do the recursive call if the array length is already greater than 2.
Exercise 4 Solution, Part 3
Either iteratively or recursively, this problem can be solved. Finally, let's try to solve it with map and reduce. So with map and reduce, what we can observe is that the calculation--- the changing of a value to a function is a transformation, is it not? So I can take arr and simply call map with foo as my function. That's going to make sure to wrap all of my values into functions. And now I want to perform a composition of all those functions. Does everybody see that? How do I do a composition? Reduce. Right. What does reduce take? What's its first parameter or its first argument? The array. It takes a predicate function, remember, because it's operating on the array as a prototype method, so we don't have to pass the array in. It's going to get that context already. So the first argument to .reduce is going to be the predicate function, the function that's going to do the work. So we'll define that function. We know that function's going to get two arguments. I'm just going to temporarily call them x and y, but we're going to rename them. So let's do our function there. And then what else does it get? Initial value. It gets an initial value. What initial value should we use here? Well, there're a couple of different ways to think about this. We are doing a map across the entire list, right? So that's one way of doing it. But the other way of doing it is to slice off---let me just double-check to make sure I'm not---I'm trying to keep close to---my brain works differently when I solve this, and I'm trying to make sure to not get too off track of your solution file. So, we could do slice(1) because we want to only perform the reduce transformation across the second half of the list. We have the first item, and we want to deal with the first item, and then whatever happened, we're going to reduce across the second list. So if we did it that way, what's our good initial value? Possibly arr(0). Arr(0). You see how I did that. I took the first element of the list and made it my initial value and did a reduction against the rest of the list. That's just the shortest way of doing it. That may not always be the way you want to do it. You might have just started it out with a good initial value like a function that returns 0, so you could have said foo(0) here. But now we want to ask, What is our accumulation? And the typical names for these, as I said, the typical names in the reduce predicate are accumulator, acc, and current. That's how most people will call them. I don't love accumulator and current, so I'm going to actually use previous and current because I think previous is a little bit easier to understand than accumulator. But let's analyze what is going to happen with this first call? With this very first call, what's going to happen is that I'm going to---let's just for simplicity's sake go ahead and pass in these as functions so that I'm not having to do that map there. Of course we know we could do the map, but let's just for simplicity's sake pass those in. So, array of 0 is what? A function now. It's a function, right? And cur is going to be array in the 1 position, which is also a function, right? So I have two functions. How am I going to compose these two functions together? I'm going to create a new function. That's one of the most common ways that we compose functions is to wrap the two of them into one bigger function. I'm going to create a new function, and I'm returning it, which is then going to make it be the next previous. So what is this function going to do? It's going to actually call add2 with previous and current. So we're not calculating that sum now, we're just making a bigger and bigger wrapped function. The end result of our reduction here is not to have a sum, it's to have one giant function that when we call that function, it's going to calculate all those intermediate sums and put them all together. We're making a bigger and bigger function. The first time we make one function, and we replace the first two elements in our array with one function that's set when it's called, it will calculate the sum of those two functions. The second iteration of our reduction, we take that composed function plus a function in a list, we compose those two together. The third iteration, we take a bigger compose and the third item in the list, and we compose those until we get to the very end, we have one giant function. So if I just simply said return arr.slice, we would not have what we want here in the sense that we would not have calculated the sum yet. We simply would have produced a function. So what do we do? We then call that function. And that will call it and call it and call it and call it, however down it needs to go until we end up with our actual sum result. _____ Yes? There're a couple of questions here? Is it possible to leave off the initial value in reduce? If you leave off the initial value, it's a little more complicated. It will default to undefined, which might be an okay initial value depending upon what you're doing. That might be an okay initial value. But you should also be aware that if you leave off the initial value and your list has one or fewer items, that throws an error. So it's not really ever a good idea to leave off the initial value. You'll almost always want to give it some useful initial value. He's kind of just having a little---he's kind of wondering why you're--if you maybe could explain again, removing the first value and why are we doing the original slice I guess. The reason we do the slice(1) here is because what we want is to perform a reduction where we have two items, and we could have done a slice---not done a slice at all, and then we could have had an initial value here of foo(0). If I did foo(0), does everybody see that that would have been a suitable initial value? It would have been a function that when called just simply returns 0, which is going to have a noop. So I could have done my solution like that. Operate a reduction across the entire list where my initial value is simply basically a noop function. That's one way of doing it. Why is the other one preferable? One less function call wrapping layer. So all I was trying to do, I've already shown you starting out with an initial value, here I'm saying---actually, we already know that array(0) is one of our---is a suitable initial value, which is why we put array(0) there, and we do the reduction across the rest of the list. Either way is entirely valid. (Working) It's important to keep track---I know that you kind of get in this nesting thing, and it's hard to keep track of the reduce predicate. That's the one right here. It returns a function. So every time we pass in two functions, we get a third function out that's a composition of those two functions. And then when we have another function and another function, and this one is already a composition, we get a super-composition of a composed function with a regular function. And then over here on the fourth item, we get a super-super-composed function with a regular function, and we've just made functions wrapping together up the list. So if we were to unroll all of that, if you were to print out this thing, and we passed in a list of four items, we'd have four levels of function call. This function returns this function call returns this function call returns this function call returns the add2 of two values. And it would just simply call all those functions and unwrap them. So this isn't recursive. This is simply unrolling recursion into a call stack as many levels deep as we need. And just as a little side trivia note, has anyone ever heard of the Y combinator. The Y combinator is a general pattern for doing exactly that. I don't fully understand it, but I know that the Y combinator takes recursion and unrolls it to a progressively wrapped function loop, so we could have solved our problem exactly like we did here. We could have solved it with the Y combinator and got the exact same result. We would have had the exact same set of functions wrapped in other functions wrapped in other functions. That's how Y combinator gets around recursion or gets around the---the problem with recursion is allocating all that new stack depth. The Y combinator gets around it by doing fixed proper tail call function calls inside of function calls inside of function calls. So we make sure to call that function if we're going to end up... Let's just double-check this. This is my ex4.js. I'm going to---actually, let me just copy it in the console. You can run it in your browser using the HTML file if you'd like. I'm just going to run it in the browser and make sure I didn't miss anything. What did I do here? I forgot to point out that we're supposed to be passing in an array. Oh, by the way, ES6, how do I get it back to where I do want to pass them in as individual ones instead of as an array? I could've just done the dot dot dot. That's all I needed to do there. So if you want to have a call signature where you don't pass in an array, just declare your call signature as basically gathering together all arguments into an array for you. Let's try it again and see if I didn't mess something up. And there we get the 181.
Exercise 4 Solution, Part 4
I apologize if it got a little muddy because I started my brain down a different way of solving this, and I got you confused. So hopefully we backed up the stack enough to show this. But the point that I want to get across is that the expression with a reduce, and we could've taken care of this part with a map, but we just backed that part out. I was going too far down to the complication tree. But a single reduce here replaces the complexities of recursion or iteration for us, just simply knowing how to use that tool. It's like if you walk into a workshop, and I've got a hammer in one hand and a mallet in the other. They both fundamentally can be used to swing things and drive some nail-looking object in, but you're going to use a mallet differently than you're going to use a hammer. You're going to use them for different kinds of nails. So that's the distinction that we're making here. We're not talking about the difference between a hammer and a screwdriver. We're talking about the difference between a hammer and a mallet. Recursion was an equally reasonable solution, but the way we ended up expressing it might have twisted some brains. I hope that what I'm showing you here is being able to use functional programming oftentimes can give us also a graceful solution even when recursion doesn't give us a graceful solution. Yes? Someone still wants you to kind of explain---he doesn't understand why the first value had to be removed? Look, let's not do it. If that helps, we're going to do a reduction across the entire array, but we need a good suitable initial value. So tell me what my good suitable initial value is going to be for arithmetic sum. It's got to be 0. But I can't just pass in 0 because everything else here is functions. So I have to pass in a function that will produce 0, which is how I call foo(0). So if that works a little bit better with your brain, I feel weirder about that solution, which is why I didn't lead with it. It feels weird to me to create a function only for the purposes of returning a 0. What I think is that something got misaligned when you started applying map, like my brain started to go, okay, this is what we're doing, and now --- That's why I took the map back out because I realized I was going down the wrong rabbit trail. That's my fault for confusion you. --- it didn't quite reset enough right now. I apologize for the confusion. Trying to pop the stack back to look at, I could delete the file and start over, but trying to pop the stack back to thinking about passing this in as a list of functions rather than a list of values. I just got myself one step ahead because our next step is going to be to generalize this to where I can start out with a list of values. But I wasn't supposed to do the map so early. So my fault, apologies. Does this one perhaps make more sense? This expression of it where we're not doing a slice of the array, but we do need an initial value, so here's how I produced my initial value. This will work exactly the same way if I pop this out, drop this in. We're still going to end up with 181. Fundamentally, we ended up with one extra function call there because we made a function call for 0 when in reality we don't even actually need that. We know the properties of our problem are that the initial value really could just start out as the beginning of the list. There's more than one function call because it also does another iteration. Call foo, and then there's---anyway. Alright, I'll take your word for it rather than going down the rabbit trail. I've probably already confused everybody enough, so I apologize. Anymore questions about the reduce? On the live stream on the chat, did we answer those questions about difference in solution. I think we---they're saying they're good there. Some people are saying that their browser may not have the ES6 features turned on. So if your browser doesn't have the ability to do the dot dot dot, take that part out and just put a bracket around that list, and it'll work the same way. So now we get to where I was going to talk about map. I just did it too early. So now if I start out with a list of functions, that's great. But if I start out with a values, and I want to pump those things into an addn, addn is expecting a list of functions, not a list of values. So if I started out with a list of 10, 42, 56, and 73, how do I make that into a list of functions? You map it with foo. I map it with foo. Do you see why foo is a suitable predicate for us? Look at the foo function. What does it do? It receives a value, and it transforms that value by what? Wrapping it in a closed over function. That's a valid transformation for a value to wrap it in a function that produces that value. So that's why foo is a suitable transformation predicate for us. So we would now have at the end of that an array of functions. So that value could be passed in to addn. And that's where I was actually trying to go earlier. That's a suitable array. And because that's an array, we can either take that dot dot dot off or leave that one there if we want to and simply spread that array out. Either way. You're not always going to be in control of all the signatures of all the functions you work with, so part of the point that I'm trying to make here is that there are facilities available to us to make the adaptations between different signatures.
Exercise 4 Solution, Part 5
Next step in our README, let's define a slightly, let's put a few more values in here. So let me come up with 15 and 2 and 98 and 7. I just added some extra values. I've now lost my ability to know what that running total will be, but we'll cheat and let JavaScript tell us what the total ought to be. So, if that was our list, we might want to exclude some items from the list. So the README suggests that we want to filter the array and exclude all of the odd numbers. So how are we going to exclude the odd numbers from our list if we start out without a list? Let's make this a little easier. Let's start this out with this array here, and then let's perform the operations like we're doing. So right now we have arr = arr.map(foo). So all I did was split those out so we're dealing with separate lines now. So let's add an additional step in here, which before we are mapping to functions, we want to look at the values themselves and filter out any of those values that are odd. So how do we do that? Write a function there. What utility am I going to reach for if I need to do an exclusion? Take a filter. I'll reach for a filter. So I need to do an exclusion. I'm going to call arr.filter, and I'm going to need a predicate--a function to pass in that returns true or false. Remember before in our slides, we passed in an isOdd, and it returned true if the value is odd. Is that the one that we want to use? We might do 0 instead of 1. We actually want the negation of the isOdd, don't we? We want an isEven. So we could write our own isEven, but another functional programming technique is to observe that isEven is just a composition of the negation with the isOdd function call. So let's assume I had an isOdd, that's all I had. Remember the way it works is to mod by 2 and say that it's got to be equal to 1. So let's say I only had an isOdd. Can I make an isEven? Yeah. I could also from a functional programming perspective, and if I had functional utilities that did composition, this would end up being pretty easy because you could make a function that just negates a Boolean. But if I wanted to make an isEven, one way of making an isEven is to actually do the modulus calculation. The other way of doing it is to call the negation of isOdd. Does everybody see that? That's doing it manually. Or I could look at that pattern and say, You know what? I do that an awful lot. I negate my Boolean utilities an awful lot, so I'm going to make myself a compositional pattern that automatically lets me take in a function, call it with a value, and negate its return response. So let's just pretend that we have one like that, composeNeg is what we'd call it. I could say isEven = composeNeg(isOdd). So that would produce me a new function, which called isOdd and negated its result before returning it back to me. Do you see how that could end up being a useful utility in my functional programming library? But only if that was a pattern I ended up doing a lot. So that's what functional programmers have done. They've done enough functional programming, and they've run across enough of these compositional patterns that they've said, Here're the common ones that we're going to provide to you. And they gave them a bunch of fancy names. All I'm suggesting to you is that you can start with nothing and build yourself up a little set of these things that you're regularly using, and that's basically what I've done. That's how I've made effective use of functional programming. That's a way to get at the same principles of functional programming instead of coming at it from the other direction and having to learn all the academics around it. And there are tons of really great functional programming libraries out there--Ramda.js, Bacon.js, several other ones out there. So if you do want to go the route of just using somebody's predefined library, and you want to take on the task of learning some of those more sophisticated terminology and techniques and what patterns functional programmers use, that's a great next step from what I'm teaching you. But I hope that I'm teaching you that there is a way to get some of the benefit here without having to go through all of that effort. And that's personally how I've woven functional programming techniques in. So let's just assume we're going to have an isEven. We just pass in an isEven. However you end up defining it, we want to filter. So I can end up doing a reassignment here as I'm doing, or this can all just be a set of chains. So arr is a chain of those two commands. Does everybody see how that works? Let's just double-check to make sure I didn't mess something up. I won't be able to run that because I haven't defined an isEven. So let's make one just for testing purposes. (Working) You have semicolons after the isEven. Oh, I did, didn't I? So that's telling us that it's 208. Let's just sanity check that. We want to add up only the evens. So 52, 108, 110, 208. There we go. So 208's our expected end result. The last step in this exercise, and then we'll wrap up. We've already done the last step. The item that I wanted to call your attention to was #9, which is now we can try to stretch our brains into thinking, How would we write tests for all this composition? We're visually looking at it and saying I can tell you that the end result is supposed to be 208. How would you write tests for this? That's not an answer I'm expecting a concrete---that's not a question I'm expecting a concrete answer to. It's a rhetorical for you to ponder as we go forward because functional programming does end up creating some very powerful expressions of our programming, but I think you will find as I have found that when you use functional programming techniques, and you try to plug those not your traditional testing frameworks, you might find some incompatibilities. So pondering how you are going to test the end result of these different things---are you going to tend only the end results? Or are you going to test each layer of all this composition? Those are questions that you should ponder.
Wrap Up
We started with basically not a lot of assumption about functional programming, and we observed side effects. Side effects are when a function does something in addition to its directive effect and in addition to its direct return value, it has some other effect, and that can be as simple as printing something to console or as complicated as changing some state, changing some variable in the other parts of our program. That's complicated not only because it means that if that function relies on that variable, we have to know how many times a function has been called to be able to predict its outcome, but it's also complicated because if there's a variable hanging out in free space that our function's relying upon, somebody else can change that variable and change out from underneath us. It's like pulling the rug out from underneath you, changing what you expect. So that's one of the fundamental motivations for functional programming is to eliminate all that complexity by doing what we call what? What's the opposite of these side effect functions? Pure functions. Pure functions. Pure functions operate only on the stuff that they're given. They have a direct return result if necessary. So they don't have any side effects in the outside world, which means all that complexity that I just talked about becomes a nonissue. Can you write an entirely pure program? Theoretically, yeah. Is it going to do anything? Not observable. The fundamental definition of observation is a side effect, right? So we're going to have to have some sort of cheat in there, and as we talked about functional programming languages, they have, for example, in Haskell, they call it the IO monad. It's a special exception in there that allows you to do some IO in a non-functional or non-pure way. In JavaScript, we have a lot more freedom I guess. Languages like Scala are the same way. More freedom; we can choose how much or how little of functional programming we wrap in. So after we talked about pure functions, we talked about composing functions together. So if you had an impure function, you can wrap a function around it, produce a pure interface, and now you've preserved the notion of purity. And we pass in the universe. And then we can compose multiple pure functions together by, instead of assigning the end result to some variable that some state that can be changed, simply calling the result of that and passing it into some other function. And we can do that manually by the call signature, or we can create compositional patterns that we know will take two functions and compose them in some specific way. So we talked about I called it compose(2), a way to specifically compose two functions and assume two parameters to each one. So that's one way of defining a composition pattern. After we talked about composition, we moved into immutability. We talked about not only immutable bindings and immutable values, but more importantly the usage of values in an immutably sufficient way. So we had utilities that could produce a list that was changed in place or produce a whole new list. We actually started to see that in JavaScript, didn't we? We saw the difference in something like slice. It doesn't change the function, I mean it doesn't change the array in place. It produces a new array, a new list that's a subset of the previous list. So using the principles of immutable values, we create a new value every time, we create a new list every time rather than mutating a list in place. After we talked about immutability, we moved into a discussion of closure. Closure being a way to wrap state around--- wrap a value, some state, in a function, and then that function can move around. And any time we call that function, we can get that state back out. And then we talked about recursion and different ways of using recursion. And then we moved into our list operations. And I hope by this point you now have all those little fundamental Legos, those building blocks to start using some of the principles of functional programming but without being overburdened by all the traditional academic terminology and understanding. That is not to disparage those things and to say that functional programming is bad. I think that it's fantastic. I've been at this 17 years, and every time I have tried to learn it from that path, I've failed. I've failed to be able to understand all that terminology. So at some point I said, Can I get some of the benefits here but just kind of in a basic way. And that's where functional-light programming comes from. So hopefully that's another tool that you can add to your bag of tricks. And it should be something that you can compose into more sophisticated understanding as you continue your learning path. Are there any other questions that we can answer before we wrap up this workshop? Not a single one? Maybe it's that afternoon lull, all that good Thai food. Well it's been an honor teaching you about functional-light programming. I hope that you will experiment with some of these things. If you have any thoughts or feedback, again, this is my contact info. I'd love for you to reach out for that. So we'll call it a wrap. Thank you very much!
Course author
Kyle Simpson
Kyle is a freelance developer based in Austin, TX. He runs several open-source projects (such as LabJS), writes books, and speaks at meetups and conferences.
Course info
LevelIntermediate
Rating
(34)
My rating
Duration3h 9m
Released22 Feb 2018
Share course