Memorized Function, a Simple Implementation of Reselect

High school math tells us that a function is a map between input and output, f(x)=>y. If the same x value is input into the function, the output y should be the same.
Motivation
Reselect is a simple supporting library for Redux to do memorized selection from the state in the store. It is important for Redux since the state in the store is just a MINIMAL representation of the state of the app. The extra data for rendering the UI might be derived from that minimal state by some transformations. Those transformations, such as filtering and mapping, could be expensive. To solve the problem, Reselect provides a way to create memorized selector function that can memorize the input and output. If the input is not changed, it will not run the transformation, instead, it just returns the memorized result.
Memorized Function
The key to making a function memorizable is to store its last input and output. This can be done by creating a scope.
As you can see from the above gist, memorize
is a higher order function that converts the normal function into a memorizable function. It creates a scope for caching the last input and output. When the memorizable function is called, its input(arguments) is compared to the cached arguments by shallow comparison. (of course, we use immutable data in redux). If all the input params are the same, the cached result will be returned without running the actual function.
Selector and Dependencies
In Reselect, a selector is just a function. As its name indicates, it is used to “select” some data from the input arguments. Some simple selectors do not transform the input data and work like getter functions. For example, we can have a person
object with firstName
and lastName
properties.
var person = {
firstName: 'kj',
lastName: 'huang'
}
Then we can have two simple selectors for getting its firstName
and lastName
.
function getFirstName(person) { return person.firstName; }
function getLastName(person) { return person.lastName; }
What if we want to get the full name of a person? The person
object does not have a fullName
properties. It must be derived from the other properties by some transformations.
function getFullName(person) {
var firstName = getFirstName(person);
var lastName = getLastName(person);
//some transformations
var fullName = firstName + ' ' + lastName;
return fullName;
}
As shown above, getFullName
depends on the results from the previous two functions (getFirstName
, getLastName
). And thus those functions are regarded as dependencies for the getFullName
function.
Now, what if we want to include the middle name? We can include another dependency called getMiddleName
, and then modify the transformation part to include the middle name in the string concatenation. However, we can also write a general function to abstract this pattern.
The higher order function, makeCompositeSelector
, can create a composite selector function based on its dependencies and actual transformation. To getFullName
, each dependency function will be called and its result will be stored in the params
array, which serves as the arguments for the later transformation.
Memorized Selector
Remember the memorize
at the beginning? We can make our selector function memorizable as well. There are two places for memorization.
- First, we can make the
transformation
function memorizable. If the person’sfirstName
,middleName
, andlastName
remain the same, he/she should have the same full name for sure. There is no need to do the ‘expensive’ string concatenation every time. - Second, we can make the return selector function itself memorizable. If the same person is input, there is no need to traverse all the dependencies. Instead, just return the last result since the input is exactly the same.
Notice:
- The created selector is just a function. Thus it can be used as a dependency to composite another selector as well.
- You can’t share a single selector among different instances since each memorized function only has one single scope.
If you check the source code of Reselect, you can see that the makeCompositeSelector
is just a simplified version of the createSelector
function.