Transcript from the "List Transformations" Lesson
>> Kyle Simpson: Our next unit of discussion, lists. And I've mentioned right away, actually this discussion, we're gonna focus on the array, the lists. But really this is about data structures. Because really, these concepts apply to any data structure or can be adapted to any data structure. It just happens to be that lists, arrays are the most convenient ways of talking about list oriented or data structure oriented operations.
[00:00:29] So my tongue in cheek way of describing this is, everything that we've done up to this point in the course has been about figuring out a single discrete value. And then with recursion we started doing stuff iteratively like over a list of values. And now what we're saying is, if you can keep do something awesome to a single value, keep doing it repeatedly.
[00:00:51] Model it as a list of values and do that awesome thing for each item in your list. That's just a high-level way of thinking. And this is how we categorize what these things are. These are operations that are fundamentally designed to operate on a single value. Like think about taking a number and doubling it, that's an operation that behaves on a single value.
[00:01:14] And now say, I want to lift or adapt that operation so that it can work across a data structure, across all the values in a data structure. For example, all the values in a list. So in a sense, we wanna adapt these single data operations to data structures to all the values in a data structure.
[00:01:39] We're gonna talk about three fundamental list operations or data structure operations. The three most common ones that you'll run into when you're doing functional programming. These are by far not the only ones, there are many others out there. But these are kinda the base ones that you really need to have your head wrapped around.
[00:01:55] So we'll start with map, that's the first of our operations. And I'm gonna assert that map is fundamentally a transformation kind of operation. It takes a value in some state, transforms it to a new state. And we adapt, when we map over a data structure, what we're saying is, create a new data structure where all the values in the new data structure are those transform values having been projected from the old to the new.
[00:02:23] So it looks a little bit like this, we start with the original, we transform, and then we project that over to the new data structure. We did not change in place, it's very important to understand. We are not changing in place. We are projecting those newly transformed values over to the new data structure.
>> Kyle Simpson: [COUGH] So transformation or mapping is usually illustrated with numbers. If I start out with a list of numbers one, two, three, four, five and I wanna double those numbers, then I could transform them with a function like doubleIt on line 1 which knows how to double a single number.
[00:03:04] And I could define a transform utility, which adapts that utility to apply across the values on the list. Or the official functional programming term is that it 'lifts' the doubleIt function to the entire list. So, pretty straightforward, we started with a new list there on line 4. We loop over the existing list and we call that mapper function, in this case, the doubleIt.
[00:03:30] We call that mapper function for each value in the old list. And whatever that transform value that's returned back is what we stick into the new list of that same position. Now I just want yo think, cuz we're gonna be coming up on this a little bit later in this unit of discussion.
[00:03:48] What would it look if I wasn't operating on a list? What if I was operating on an object? How would I map one object to another object? Well, I'd probably start out with a list of all the properties in the object, and loop over those properties and call the napper function.
[00:04:05] That's not any more complex than what we're doing here, it's just a little bit more syntax. What if it was a more sophisticated data structure like some custom data structure in your application? You could still map across a set of values in that data structure. You'd just need to provide your own transform, your own lift, that lifted a single mapper like doubleIt to work against the values that you need it to.
>> Kyle Simpson: So we can call an array and call a .map method on it, give it the doubleIt mapper function.
[00:04:51] And it'll automatically lift that, execute it across all the values in the array, give us a new array back with the values 2, 4, 6, 8, and 10.
>> Kyle Simpson: Now mapping a list of numbers, it's a cool, nice, easy way to illustrate this. But you're not always gonna be working with a list of numbers that you need to double it.
[00:05:13] So let's talk about some other examples of lists of values that you might work with. Let's imagine that you had a list of strings, maybe that's a list of strings that you got from the server or people typed in or whatever. Give me some examples of things that you might map with a list of strings.
[00:05:33] What are some operations you might do against a list of strings? Okay.
>> Speaker 2: Sort.
>> Kyle Simpson: So the answer was sort. Map as a fundamental operation, if you go back to animation, we had a couple of slides ago. Map takes a new structure of the same size, and there is a one-to-one correspondence in the same position.
[00:05:58] So sort would not be a good description of the map operation. It is a fundamentally useful thing to do, but it's not an example of mapping.
>> Speaker 3: Can it be upper case or lower case?
>> Kyle Simpson: Sure, if we had a list of strings, we could produce a list of all those strings having been upper cased or lower cased.
[00:06:15] What's another example that we could do with a list of strings.
>> Speaker 2: Concatenation?
>> Kyle Simpson: That's a different kind of operation, because that would result in not a list of each of the individual strings. Unless you had a fixed thing that you wanted to like, prefix all the strings with or suffix all the strings, that would be something that you could map.
>> Speaker 2: What about length?
>> Kyle Simpson: So you could map the list of strings to list of their numeric lengths, that's true. You have a list of numbers that represents their lengths. What about mapping a list of strings to their translations in a foreign language? That could translate function that you gave a list of words to and it gave you, looked them up on a table and spit out the translated.
[00:06:55] What about taking a list of strings and cutting them off at a certain length, like truncating into a certain length, or other sorts of things like that? Prefixing and suffixing. So those are things we might do with a list of strings. What about a list of functions? What if we had an array that had a bunch of functions in it?
[00:07:15] What are some mappings that we could do of that list of functions?
>> Speaker 4: Would it be mapping them if you're running all of them?
>> Kyle Simpson: Sure, we could take the list of functions and map that to a list of their return values or we called each function, whatever it returned is that new item in the new list.
[00:07:40] So we could take a map of list of functions and map it to a list of return values. What about the other way around? What about a list of functions that we compose with another function, like a give and a set function that we compose every item in the list with.
[00:07:55] So now, we go from a list of functions to a list of composed functions. Say, for example, if I had a list of functions and I wanted to run binary against all of them so that each one of them had been limited to only accepting two arguments. So now, I have list of original functions, we've got our length in order.
[00:08:17] And now I have a list of binary functions wrapped around them.
>> Kyle Simpson: The data type that you're gonna be working against is going to dictate the kinds of operations that would make sense to do with them. So, I wanna broaden your thought process beyond, well, this only works if I am dealing with numbers.
[00:08:36] There is lots of places you'll end up finding map to be very useful in the nitty gritty parts of your code. We're not talking about async right now, we're gonna be talking about that later. But one place where I've used map is in the asynchronous programming context. You might have heard of promises.
[00:08:57] Promises are a modeling of a future value. Well, let's say you have an AJAX function that returns you a promise when it makes a call. I can take a list of URLs and map that to a list of promises for those ajax responses. So I started out with the list of strings and I got now a list of promises.
[00:09:16] And now that I have that list of promises, I can do something like pass him to promise that all or something like that. So the data structures, the data types that you're working with, will dictate lots of different usages for something like math.
>> Kyle Simpson: Okay, questions about the map transform operation?
[00:09:37] You'll see much more with map as we go forward. We've got a couple of exercises coming up to test here working with that. The second-
>> Speaker 5: There was one question online about mapping through a multidimensional array.
>> Speaker 6: I'm not sure what [INAUDIBLE].
>> Kyle Simpson: So map is a shallow operation where it operates against just the top values in the list.
[00:10:05] If the item in that list is a sublist, which is basically what a multidimensional array is. You could have each mapping operation be a submapping operation. But it's entirely gonna be up to you to do that sublevel, or into the third dimension, or fourth dimension, or however many it is.
[00:10:24] It's gonna be entirely up to you, because each mapping operation is only gonna operate on that top dimension of value that you've given it. So you could do a multidimensional map, but it would be a map of mappings to make that happen.
>> Kyle Simpson: Good question.
>> Kyle Simpson: Since that question was brought up, let me point out one other thing before we move on.
[00:10:49] It's exceedingly important that you think about these mapper functions, like for example, the doublet. It's exceedingly important and it might be too obvious, but I wanna call it out, it needs to be a pure function. It needs to be a function that takes in an input and returns an output, that's its shape should not have any side effects at all.
[00:11:10] If you are relying upon mapping to do side effects, you are doing it wrong. Stop doing that. I've heard people say, well, I can take a list of values and then call .map, and I can put a console.log in it or I can print stuff out or whatever.
[00:11:24] That's not what map is for. Map is for transforming a list to another list of transformed values. There are what I would say less functionally programming friendly utilities like, for example, foreach. Foreach is a utility that given a list, you can do a set of side effects with that items of the list.
[00:11:45] So if you really wanna do console lot logging of the list of items, use foreach. Don't use map for that because that will create confusing code. Map should be used only for these pure operations. And one of the reasons why functional programmers like map, and why they like this no side effect programming can be seen when you think about the fact that the map operations that we're talking about.
[00:12:07] We implemented it as a for loop, we just went one item at a time. But there's no reason that it has to be done in that order, and there's no reason that they have to be done only one at a time. If we were in a multi-threaded environment, we could theoretically spin off an entirely separate thread for each transformation, and they could happen in any order.
[00:12:29] And when all of them finish, they just slot themselves back into the new list at the correct location, and then we're done. So if you started out with an array of 10 million items in it and you were gonna loop through one at a time, that might take a while.
[00:12:41] But if you were in a threaded environment, functional programmers love the fact that that compiler could spin off 1,000 threads and do 1,000 of those at a time, and pick different parts, and segment it out, and break up the work, do all kinds of stuff to get that work done a lot quicker.
[00:12:57] That's one of the advantages of the mapping operation is that each operation is entirely independent of any other operation. By the way, that's why sort is not an example of map, because sorting obviously could not be done completely parallelized, where things slot themselves is dependent on other slots.