Check out a free preview of the full Functional JavaScript First Steps course
The "Function Composition Solution" Lesson is part of the full, Functional JavaScript First Steps course featured in this preview video. Here's what you'd learn in this lesson:
Anjana demonstrates the solution to the Function Composition exercise.
Transcript from the "Function Composition Solution" Lesson
[00:00:00]
>> Again your mileage may vary, but here are some of the ways that I was able to implement some of these functions. Again, you can find this as a fork of the exercise notebook by clicking on that 1 fork at the top of the exercise. So this pipeline function, how are we gonna do it?
[00:00:21]
We can use recursion, our bff now that we are functional programmers and we can start out with some base cases. So we can say, look, I have some unknown number of functions here. And we're using JavaScript spread syntax dot dot dot to spread out however many functions we have, we're gonna kind of get an array of functions.
[00:00:45]
It could be two, it could be one, it could be five, it could be bajillions. We're gonna take that functions array and see. If there's nothing in it, cool we don't have anything to do, the input is going to be unchanged. There's no operations. If there's one thing in it, then we're gonna return a simple function which is going to take whatever input we pass in.
[00:01:07]
It's gonna take the first thing out of the functions array, which only has one thing in it. So this is a little bit just basically like accessing the first element of that array, the only element and then call that on the input. But then in the more interesting, perhaps in the recursive case, we're going to return a new function which takes in whatever input and we're going to return a recursive application of the pipeline function.
[00:01:37]
Where? We are going to create a pipeline from the tail, the rest of those functions. We're gonna get the tail those functions and pass those in to the pipeline function. And that is gonna give us kind of the rest of the pipeline which then we can pass in whatever the transformation was on the input that the first element of our functions array wants to make.
[00:02:07]
So similarly to how if we only have one function that we're putting into our pipeline here, we're going to apply whatever function is at the head of functions, apply it or call it on input, pass input into it. That's what we're doing here. We get whatever the result of that is.
[00:02:29]
That output becomes the input to the rest of our pipeline. So what we've done is again we've kind of recursively chopped off the first thing that we need to do in our pipeline so that then we get a full pipeline of all of the transformations that we need to make.
[00:02:50]
And so now once we've implemented that, we should see that once we've defined all of our little single element functions here, I can then pipeline them like this. Where first the input will go through exclaim, then it will go through heart, then it will go through pluralize. Oops, excuse me, that's the wrong order.
[00:03:08]
First it'll go [LAUGH] through pluralize, then it'll go through heart, then it'll go through exclaim so that I get the correct output of I heart pipelines, yay. And similarly, we wanna make sure that order matters. So later we can double check that if I pass in those arguments in the wrong order, those functions in the wrong order, pipeline doesn't know.
[00:03:28]
It's just gonna apply them in whatever order I pass them in. And so hopefully this is a little bit more readable than if we were to to pass in the result of each function, if we were to have something like okay, first apply pluralize, And then take that and pass it into heart in this case and then take that, excuse me, pass it into explain.
[00:04:01]
In this case, I kind of have to read the series of operations backwards as it were. I kind of have to read from the inside out to know that pluralize is gonna get applied first, and then heart, and then explain. So in my pipeline helper now, I now have an easier way to compose these functions together more readably so I can see kind of in reading order where the data is gonna flow through.
[00:04:31]
Make sense?
>> Is this kind of the concept of Unix pipes where you just pipe standard out into the next program so again?
>> Is this like the concept of Unix pipes where you can pipe the output of one command on the command line, pipe it into another command?
[00:04:53]
Yes, it is sort of similar to that. You could create this kind of pipeline where the output from one function flows in to the next function. It's a very similar concept to piping things from one command into the next on a Unix command line. Great point. Okay. How about the reduced pipeline, was anybody able to get to this?
[00:05:19]
So you again, your mileage may vary. But what's interesting is that with our reduced function, we can actually condense this down. We can take whatever functions are coming in, return a new function which takes input. And calls reduce on our little function that is going to take an accumulator, which is basically going to be our output so far.
[00:05:48]
And the next function to apply, the next function to call on that output, the next function in our pipeline. And it's going to call that function on the output that we've seen so far, our accumulated output. And so we start this off with our initial value is whatever the first input was, that's gonna be the first value coming in.
[00:06:10]
And then we can reduce over that array of functions to successively call them on the result of all of the calls that we have made so far. So hopefully this goes to show that once we start getting used to working with these helper functions like reduce, and map, and things like that.
[00:06:31]
And once we start to get really used to concepts of, okay, how am I gonna conceive of this as an accumulated value all of the outputs that I've seen so far. For example, we can start to get really, really small little functions. And that means that there's less lines of code to get confused by.
[00:06:51]
And hopefully there's less lines of codes to introduce bugs in. So [LAUGH] this is a little challenge to show how we could do this in a one line kind of function. And we could even turn this whole thing into an arrow function and put it all on a single line if we wanna do.
[00:07:06]
Although sometimes trying to condense everything into a single line also hurts readability. So, buyer beware on that front, okay? And the next exercise we were trying to do some snake charming here where we are transforming data in one shape in this snake case strings into data and another shape to camel case strings.
[00:07:30]
And what you may have found, and again your mileage may vary, and this is very much we're starting to introduce kind of creativity of the actual practice of functional programming. Starting to introduce, how am I gonna think about this in terms of small single operation functions? So your mileage may totally vary, your functions may look very different.
[00:07:50]
But what I've done is I started out with that desnake function which splits up the string into an array of strings that I'm gonna put back together later. I've got some other little single argument functions here. I can have a function that capitalizes the first letter. And here I'm using some JavaScript string methods to basically do this, to capitalize just the first letter of the lowercase string.
[00:08:18]
And then I can map that function that capitalizeFirstLetter function over an array of strings to capitalize, excuse me, I think I have the wrong comment on that. I can capitalize all of the words in a string. Or I can take a a camel case approach where I'm only going to capitalize the tail of an array of strings.
[00:08:51]
Only gonna capitalize everything except the first element. And the first element I'm just gonna leave unchanged and not capitalize it. Now I said we're gonna have to put those things back together so I can make a function like concatenate which is going to do a reduce operation on an array of strings and add them all together.
[00:09:11]
Concatenate them together to produce a single string, so a single value. So reduce that array of strings into a single value. And then I can do something like create a pipeline where I'm gonna first pass a string in desnake. And then the output of that is gonna be an array of strings which I'm gonna pass into camelize.
[00:09:32]
And then that's going to be an array of strings that I'm going to pass into concatenate to merge them back together into a single string again. And so my snakeToCamel function becomes that pipeline applied to whatever snake_case_string I have coming in. And I've just got some extra cases there to make sure it's working.
[00:09:58]
And similarly, and in this case this is just goes to show that everything is functions, right? So I could create a similar function called snakeToTrain out of a pipeline of my desnake function, my capitalizeAll functions. So I didn't have to write anything new. I could just reuse those single arguments, single operation functions.
[00:10:18]
And then another function I defined called hyphenate, which is similar to concatenate where it reduces an array of strings to a single one. But in this case, it's gonna put them together, joined by a little hyphens by those little minus symbols. And so that is going to give me something.
[00:10:35]
When I call it on a snake_case_string, it's going to pipeline that string through all these functions and I'll end up with a string in a different case. So, I'm not gonna go through all of the different possibilities for this. But hopefully as a challenge and a little bit of a mental exercise, you can go through and try to reuse those single argument functions and to create more different types of transforming case functions.
[00:11:06]
And again the idea here is that in functional programming, we really start to think about our programs as data transformations. We start to think about how we can flow data through a series of operations to get from the inputs that we have to the outputs that we want, okay?
[00:11:23]
So the question is can you do this with asynchronous operations? Can you do this with promises or perhaps with async await? So that is an amazing challenge to think about. How could you write a version of the pipeline function that is intended to work with asynchronous functions, with functions that might take a while to complete?
[00:11:48]
Spoiler alert, yes you can. And especially the await keywords make that easier to do without thinking about the promise chaining and whatnot. So that I'm gonna leave as an exercise for the reader. How could you change your pipeline function so that it works with functions that might not return immediately, but it might need to wait for the output of?
[00:12:13]
And how can you make sure that that output has completed, has fulfilled its promise before you pass it in to the next function? That's a great point. But yes, in functional JavaScript we can do this by awaiting asynchronous functions.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops