Transcript from the "List Transformation" Lesson
>> Kyle: All right, these next four I mention, we're gonna talk about dealing with lists. This is a very common refrain that you'll hear from a functional programmer. We often times 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.
[00:00:27] Even if there's no values. You can have an empty list that you express an operation on, in which case, nothing occurs. You can 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.
[00:00:47] 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 no value, or only one value, or a million values, or a billion. I don't need to special case anything because the operation is, if it's defined as a peer operation, it doesn't really matter, does it?
[00:01:09] So again, you see that referring 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 peer operation, then it doesn't matter how many items are or are not in the list.
[00:01:26] It also doesn't even matter. This is 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?
[00:01:42] 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 multi-processing and 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, into a whole bunch of stuff in parallel, and then assemble everything back together at the end.
[00:02:09] So you can start to see why list operations are an attraction abstraction for our programming. Here we want to talk about the transformation operation. Okay? 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.
[00:02:30] 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 we can do on a number?
[00:02:49] Well, you could take any number and double it. That's a transformation. Double is a transformation. You can take any number and divide it by three. That's a transformation. You can take any number and increment it by one. You get the point? You get to find a whole bunch of different mathematic transformations, pure transformations on a value.
[00:03:09] A 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.
[00:03:28] 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, okay? That's what a transformation, that's what a list transformation looks like. What if we wanted to write our own transform function?
[00:03:45] How would we do that? Probably not too surprising. We first define on line 1 a doubleIt. There's our pure transform function. Okay? Functional programming terms, they often use this fancy word called predicate, who cares, right? It's just a function that does something. It's a pure function. So I'm not going to talk about predicates but that if you've read functional programming you might have seen that word before.
[00:04:12] All right so double it. Takes in v multiplies by 2 returns the value back. Doesn't get anymore 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 is the one we wanna pay attention to.
[00:04:28] Transform takes an array, and a function reference. Whatever, doesn't matter what the function is, right? This is a general utility that we call transform. And you notice, by the way, that I intentionally use an immutable programming technique here. Which is, that I declare a new value, a new list, and I add to that new list.
[00:04:48] 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 we are talking about is all building upon itself here.
[00:05:06] We are starting to see why immutable is important for 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.
[00:05:23] So I can pass in my array, 1, 2, 3, 4, 5 like I do 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.
[00:05:41] We don't care that it was a linear loop. Could have done it recursively. I bet if I have you the exercise to write a recursive map you could probably figure out how to write a recursive map. Either if it's a little bit more illustrative here but this could have been done recursively, it could have been done massively parallel on different threads.
[00:06:01] 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 outside, on the end result, we get a whole new list with the transforms having them occurred in every single time we do that exact same transform, we're going to get the same result.
[00:06:45] Function programmers will 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?
[00:07:02] Can anybody spot it?
>> Speaker 2: Exchanging the array itself, and not returning?
>> Kyle: No, it's not actually. It's actually returning us a whole new array. That's good, so it's not the thing. There's a different problem here.
>> Speaker 2: It can not be counted?
>> Kyle: What's that?
>> Speaker 2: It can not be counted?
>> Kyle: 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 than what we talked about before, which is we take one function and we take its result and pump it into another function.
[00:07:38] 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 Java Scripty way, we do composition and functional programmers always look at that as yucky.
[00:07:52] You can accomplish many of the same things. I can understand why this form of composition, the method type of composition can be less attractive. There are places where you end up having to do extra work in the wrappers you create that a truly functional programming language wouldn't have to worry about those details.
[00:08:10] Just something to be aware of but the nice thing is it's super easy to use. It's a method 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 that.
[00:08:22] If you're familiar with that method that form of composition pretty straight forward. That's exactly what your hoping for right. 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.
[00:08:44] At the end of line three we could call dot map again and we'd be operating on the second array, the newly created array, and we'd return a third array. 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 double it, so maybe there's a double it function and maybe there's an add one function.
[00:09:12] Two independent mappings that we might produce over a set of lists. You could say .map(doubleIt).map add 1. You're gonna get the end result. You're gonna get an array that has everything doubled plus 1. but implementation wise you're going to have to run through that list twice. You're going to create an intermediary ray that nobody cares about because it just gets thrown away.
[00:09:36] 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 with an add one 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 at putting little bookmarks there for you as you study functional programming more.
[00:10:03] That's one of those places. If you see your self doing dot map, dot map, dot map, dot filter, and doing a big old chain of things. There are ways to compose those different steps together and have higher efficiency with your implementation, okay? So there's our transformation. Any questions about transformation, mapping?
[00:10:23] 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 transforming numerically. What would a transformation of strings be. Well, we could do string concatenation or we could upper case everything, all the characters in a string.
[00:10:46] So if you had an array of strings and you wanted to lower case everything you could pass in a function that takes a value and returns the lower case of it. That would be your predicate, that would be your transformer function, and then just simply map over your array strings.
[00:11:00] You can have even hierarchical transformations. This is harkening to some stuff that we'll talk about when we do an a synchronous programming workshop. But in the a synchronous programming workshop, we'll talk about even the notion that ideas like, what about transforming a function? What would it mean to transform a function?
[00:11:21] What might need to wrap that function in something else that does something different. So I 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.
[00:11:39] 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 wanna model that in your program and simply putting one or more of those values into a list and using one of these functional tools like math to do the transforms.
[00:11:55] So, makes sense? Excellent. All right. So, that's transformation.