Transcript from the "Generalized to Specialized" Lesson
>> Kyle Simpson: We talked a bit earlier in the course about this idea of function specialization, we briefly glimpsed it. And I mentioned things like partial application and currying. We're gonna come back and dig into that fully. So that's what we're gonna talk about, the idea of going from more generalized functions to more specialized functions.
[00:00:18] This is a key concept in functional programming. All right, so let's imagine I have a function called Ajax. It takes three inputs, a URL, some sort of data parameter, and then a callback. And I might call it like you see on line three, which is I call it with some variable called CUSTOMER_API, pass it a data object with id 42.
[00:00:38] And then I pass it a callback that's gonna render that customer to the screen, for example. Now, there's nothing wrong with line three, it does get the job done. And you've probably written code like that on many occasions in your applications. But what I want to point out is that, that line has a lot of detail in it that may not be necessary, for the reader to see at that exact moment.
[00:01:00] And that's really, it's not just that we want to make the line shorter or make it longer or something. But we really want every line of code to have only the amount of information, no more, no less, that the reader needs to see at that point. So let's consider some intermediary steps which might allow the reader to understand things a little bit more clearly without having to read through a long line of a bunch of different data.
[00:01:25] So one thing that I could do is I could make an intermediary function called getCustomer. Now, getCustomer already knows that it's gonna make all of its ajax calls against CUSTOMER_API. And it only needs to receive data and callbacks, but it doesn't need to receive a URL, because that's sort of hard coded, okay?
[00:01:46] Now, on line 9, I'm calling getCustomer. And I'm providing that same object with the id:42, and that same renderCustomer. So, compare line 3 and line 9 in your mind. Which one of those two lines is more clearly describing to the reader of the code what the purpose and intent of that line of code is?
[00:02:09] And I think most of you would say line 9. It has a semantic function name there, which is better than this general function name ajax with a CUSTOMER_API. It describes something more clearly. So making that function may not ever be called more than once. And you're used to, we talked about this DRY thing, you're used to thinking about, well, I only make something into a function if I'm going to call it three, or five, or ten times.
[00:02:37] I think Uncle Bob said, only make it into a function when you've copied it the third time, or whatever. But I'm here to tell you that one of the reasons why you make something into a function is so that the code is more semantic. The name of a function call describes its purpose.
[00:02:54] And purpose is the key thing that we want to communicate to our readers. So even if I never was going to call it ever again, there still might be benefit to defining getCustomer so that I have a more clear name for what's happening on line 9. And if that's true, then we can extend that even further.
[00:03:12] Because there might be places in our code where we need to get customers. But there might be other places in the code where we need to get a very specific customer. Like for example, the currently logged in customer.
>> Kyle Simpson: So we might define a further specialization function called getCurrentUser.
[00:03:32] And getCurrentUser is hard coded to the ID of the currently logged in session, or whatever, and all it needs is a callback.
>> Kyle Simpson: So line 16 compared to line 9. And, therefore, compared also to line 3. Comparing those three lines of code, can we see that line 16, for the places where that would be what we wanted, line 16 is even more semantically self-descriptive than is line 9, which itself was more descriptive than line 3.
>> Kyle Simpson: We haven't changed any of the functionality here. That's a key component to understand. We have not changed the functionality, we have simply changed the stylization of this code to be more semantic.
>> Kyle Simpson: Now, defining getCustomer manually the way I did, and then defining getCurrentUser manually the way that I did, that's a bit of extra overhead work.
[00:04:32] And you might say, it's unfortunate that I have to do that definition of a function just so that I can get a nice clean function call on the call signature. By the way, line 12, I've commented that line out. Because line 13 defines getCurrentUser in terms of getCustomer.
[00:04:50] But I could have defined getCurrentUser in terms of just the ajax call, would have had the exact same outcome. Can anybody tell me why we might choose line 13 over line 12?
>> Speaker 2: It's more declarative.
>> Kyle Simpson: It is more declarative. But more specifically than that?
>> Kyle Simpson: It creates a clearer relationship.
[00:05:17] It says getCurrentUser is the specialization of getCustomer, as opposed to saying it is the specialization of ajax. Saying it's the specialization of ajax is a weaker statement relationally than saying it is the specialization of getCustomer. So even though both might have the same outcome, the choice of line 13 makes a clearer, more obvious signal about the relationships between these entities.
>> Speaker 3: It just seems that currentUser also kind of implies that it is a customer, because of-
>> Kyle Simpson: Yes, basically. I could have called it getCurrentCustomer or whatever, but yes, basically, right? So in our domain model that would be obvious to the reader, right? But defining these functions manually certainly has the downside which is that we're cluttering up this code.
[00:06:10] You might have to take those and put them somewhere else or whatever. So is there some way that we could define these more specialized versions of functions, but without having to define manually pointed functions like this. These are pointed definitions rather than pointfree. How could we define them more clearly?
>> Kyle Simpson: First thing you need to understand before we move into that solution is, as I said earlier, the parameter order matters greatly. When you're going to specialize a function, that is, an n-ary function that takes 3, 4, 5, 10, 20, whatever, inputs, the order of those inputs matters.
[00:06:55] And basically, the order should always be from the most general of the parameter on the left to the most specific of parameters on the right. Because we're going to provide those inputs one at a time, the way we were doing manually, and you want to unfold them in that order.
[00:07:16] If you had the most specific one on the left, well, now it's gonna be harder to provide that input, because you're trying to provide a more general input first. And you're having to skip over a more specific input. So it's important when we design our functions to design them so that their parameter orders are going from general to specific.
[00:07:34] By the way, you will encounter this in your functional libraries. When you're exploring something like Ramda or whatever. For example, the map function. Most of us, I think, are used to thinking in terms of a utility like map, which takes two inputs. We're used to thinking, okay I'll pass the array as the first input.
[00:07:55] And I'll pass the function as the second input. But you'll notice that every functional library in existence will do that reversed. It will pass the function first and then it will pass the array. You may never have stopped to wonder, why is it that they do that? They do that very specifically because [LAUGH] the array values, the data, is the most specific of all of the inputs that could possibly go in.
[00:08:24] And then the callback is more general of an input. So we want, if we're gonna specialize a map call, we're almost always gonna provide a mapper function and then provide new pieces of data, rather than provide a single array and provide different mapper functions. Are you with me?
[00:08:40] So parameter order does matter when we shape these things. And sometimes the parameters are correct, in the right order. Sometimes we have to juggle them with those utilities like flip and stuff.