Toward modular, reusable code
4.3 Curried function evaluation
else {
return new Status(true, 'Success!');
} }
isValid(normalize(strim('444-44-4444'))); //-> (true, 'Success!')
The occurrence of 2-tuples is so frequent in software that it’s worth making them first-class objects. When combined with JavaScript ES6 support for destructured assignment, you can map tuple values to variables in a clean manner. Using tuples, the following code creates an object called StringPair.
const StringPair = Tuple(String, String);
const name = new StringPair('Barkley', 'Rosser');
[first, last] = name.values();
first; //-> 'Barkley' last; //-> 'Rosser'
const fullname = new StringPair('J', 'Barkley', 'Rosser');
Tuples are one way to reduce a function’s arity, but there’s a better alternative for cases in which tuples aren’t sufficient. Let’s spice things up a bit by introducing function curry-ing, which not only abstracts arity but also encourages modularity and reusability.
4.3 Curried function evaluation
Passing a function’s return value as input to a unary function is straightforward, but what if the target function expects more parameters? In order to understand currying in JavaScript, first you must understand the difference between a curried and a regular (non-curried) evaluation. In JavaScript, a regular or non-curried function call is per-mitted to execute with missing arguments. In other words, if you define a function f(a,b,c) and call it with just a, the evaluation proceeds, and the JavaScript runtime
Listing 4.3 Using tuples for the isValid function
Listing 4.4 StringPair type
Declares a Status type that holds values for status (Boolean) and message (String)
Throws an arity mismatch error
sets b and c to undefined, as shown in figure 4.5. This is unfortunate and most likely the reason why currying isn’t a built-in feature of the language. As you can imagine, not declaring any arguments and relying on the arguments object within functions only exacerbates this issue.
On the other hand, a curried function is one where all arguments have been explicitly defined so that, when called with a subset of the arguments, it returns a new function that waits for the rest of the parameters to be supplied before running. Figure 4.6 rep-resents this visually.
Currying is a technique that converts a multivariable function into a stepwise sequence of unary functions by suspending or “procrastinating” its execution until all arguments have been provided, which could happen later. Here’s the formal defini-tion of a curry of three parameters:
curry(f) :: (a,b,c) -> f(a) -> f(b)-> f(c)
This formal notation suggests that curry is a mapping from functions to functions that deconstructs the input (a,b,c) into separate single-argument invocations. In pure functional programming languages, like Haskell, currying is a built-in feature and automatically part of all function definitions. Because JavaScript doesn’t auto-matically curry functions, you need to write some supporting code to enable this.
Before we go into auto-currying, let’s start with a simple scenario of manually curry-ing two arguments.
Evaluating: Runs as:
f(a) f(a, undefined, undefined)
Figure 4.5 Calling a non-curried function with missing arguments causes the function to eagerly evaluate missing parameters and fill them with undefined.
result
Evaluating: Returns:
f(a) f(a, b)
f(a, b, c)
f(b, c)
f(c)
Figure 4.6 Evaluating a curried function f. The function produces a concrete result only when all arguments have been provided; otherwise, it returns another function that waits for these parameters to be passed in.function curry2(fn) {
return function(firstArg) { return function(secondArg) {
return fn(firstArg, secondArg);
};
};
}
As you can see, currying is another case of a lexical scope (a closure) where the returned functions are nothing more than trivial nested function wrappers to capture the arguments for later use. Here’s a simple example:
const name = curry2(function (last, first) { return new StringPair('Barkley', 'Rosser');
});
[first, last] = name('Curry')('Haskell').values();
first;//-> 'Curry' last; //-> 'Haskell'
name('Curry'); //-> Function
Let’s take another look at curry2, implementing the checkType function used in the Tuple type shown in listing 4.2. This example use functions from another functional library called Ramda.js.
Once it’s installed, you can use the global variable R to access all of its functionality, such as R.is:
// checkType :: Type -> Type -> Type | TypeError
const checkType = curry2(function(typeDef, actualType) { if(R.is(typeDef, actualType)) {
return actualType;
}
Listing 4.5 Manual currying with two arguments
Another functional library?
Like Lodash, Ramda.js provides lots of useful functions to connect functional pro-grams and also enables a pure functional style of coding. The reason for using it is that its parameters are conveniently arranged to facilitate currying, partial applica-tion, and composiapplica-tion, which I’ll cover later in this chapter. For more details about setting up Ramda, see the appendix.
First invocation of curry2 captures the first argument
Second invocation captures the second argument Returns the result of applying this function with both arguments
When supplied both arguments, evaluates the function completely When supplied only one argument, returns another function rather than evaluating with undefined
Uses R.is() to check type information
else {
throw new TypeError('Type mismatch.
Expected [' + typeDef + '] but found [' + typeof actualType + ']');
} });
checkType(String)('Curry'); //-> String checkType(Number)(3); //-> Number checkType(Date)(new Date()); //-> Date checkType(Object)({}); //-> Object
checkType(String)(42); //-> Throws TypeError
For simple tasks, curry2 is adequate; but as you start building more-complex function-ality, you’ll need to handle any number of arguments automatically. Normally, I’d show you the function internals, but curry is a particularly long and convoluted func-tion to explain, so I’ll spare you the headache and move into a more useful discussion (you can find curry and its flavors—curryRight, curryN, and so on—implemented in both Lodash and Ramda).
You can use R.curry to simulate the automatic currying mechanism in pure func-tional languages that works on any number of arguments. You can imagine automatic currying as artificially creating nested function scopes corresponding to the number of arguments declared. This example curries fullname:
// fullname :: (String, String) -> String const fullname = function (first, last) { ...
}
The multiple arguments are transformed into unary functions of this form:
// fullname :: String -> String -> String const fullname =
function (first) {
return function (last) { ...
} }
Now let’s jump into some of the practical applications of currying. In particular, it can be used to implement popular design patterns:
■ Emulating function interfaces
■ Implementing reusable, modular function templates 4.3.1 Emulating function factories
In the object-oriented world, interfaces are abstract types used to define a contract that classes must implement. If you create an interface with the function findStudent(ssn),
concrete implementers of this interface must implement this function. Consider the following “short” Java example to illustrate this concept:
public interface StudentStore { Student findStudent(String ssn);
}
public class DbStudentStore implements StudentStore { public Student findStudent(String ssn) {
// ...
ResultSet rs = jdbcStmt.executeQuery(sql);
while(rs.next()){
String ssn = rs.getString("ssn");
String name = rs.getString("firstname") + rs.getString("lastanme");
return new Student(ssn, name);
} } }
public class CacheStudentStore implements StudentStore { public Student findStudent(String ssn) {
// ...
return cache.get(ssn);
} }
Sorry for the long-winded code snippet (Java is that verbose!). This code shows two implementations of the same interface: one that reads students from a database and the other that reads from a cache. But from the point of the view of the calling code, it cares only about calling the method and not where the object came from. This is the beauty of object-oriented design via the factory method pattern. Using a function factory, you can obtain the proper implementation:
StudentStore store = getStudentStore();
store.findStudent("444-44-4444");
You have no reason to miss out in the functional programming world, and currying is the solution. Translating the Java code into JavaScript, you can create a function that looks up student objects in a data store as well as an array (these are the two implementers):
// fetchStudentFromDb :: DB -> (String -> Student) const fetchStudentFromDb = R.curry(function (db, ssn) { return find(db, ssn);
});
// fetchStudentFromArray :: Array -> (String -> Student) const fetchStudentFromArray = R.curry(function (arr, ssn) { return arr[ssn];
Because the functions are curried, you can separate the function definition from eval-uation with a generic factory method findStudent, whose implementation details could have originated from either implementation:
const findStudent = useDb ? fetchStudentFromDb(db) : fetchStudentFromArray(arr);
findStudent('444-44-4444');
Now, findStudent can be passed to other modules without the caller knowing the concrete implementation (this will be important in chapter 6 for unit testing to mock interaction with the object store). In matters of reuse, currying also allows you to cre-ate a family of function templcre-ates.
4.3.2 Implementing reusable function templates
Suppose you need to configure different logging functions to handle different states in your application, such as errors, warnings, debug, and so on. Function templates define a family of related functions based on the number of arguments that are cur-ried at the moment of creation. This example will use the popular library Log4js, a logging framework for JavaScript that is far superior to the typical console.log. You can find installation information in the appendix. Here’s the basic setup:
const logger = new Log4js.getLogger('StudentEvents');
logger.info('Student added successfully!');
But with Log4js, you can do much more. Suppose you need instead to display mes-sages on the screen in a pop-up. You can configure an appender to do so:
logger.addAppender(new Log4js.JSAlertAppender());
You can also change the layout by configuring the layout provider so that it outputs messages in JSON format instead of plain text:
appender.setLayout(new Log4js.JSONLayout());
There are many settings you can configure, and copying and pasting this code into each file causes lots of duplication. Instead, let’s use currying to define a reusable function template (a logger module, if you will), which will give you the utmost flexi-bility and reuse.
const logger = function(appender, layout, name, level, message) { const appenders = {
'alert': new Log4js.JSAlertAppender(),
'console': new Log4js.BrowserConsoleAppender() };
Listing 4.6 Creating a logger function template
Defines a set of canned appenders
const layouts = {
'basic': new Log4js.BasicLayout(), 'json': new Log4js.JSONLayout(), 'xml' : new Log4js.XMLLayout() };
const appender = appenders[appender];
appender.setLayout(layouts[layout]);
const logger = new Log4js.getLogger(name);
logger.addAppender(appender);
logger.log(level, message, null);
};
Now, by currying logger, you can centrally manage and reuse appropriate loggers for each occasion:
const log = R.curry(logger)('alert', 'json', 'FJS');
log('ERROR', 'Error condition detected!!');
// -> this will popup an alert dialog with the requested message
If you’re implementing multiple error-handling statements into one function or file, you also have the flexibility of partially setting all but the last parameter:
const logError = R.curry(logger)('console', 'basic', 'FJS', 'ERROR');
logError('Error code 404 detected!!');
logError('Error code 402 detected!!');
Behind the scenes, subsequent calls to curry are called on this function, finally yield-ing a unary function. The fact that you’re able to create new functions from existyield-ing ones and pass any number of parameters to them leads to easily building functions in steps as arguments are defined.
In addition to gaining lots of reusability in your code, as I mentioned, the principal motivation behind currying is to convert multiargument functions into unary func-tions. Alternatives to currying are partial function application and parameter binding, which are moderately supported by the JavaScript language, to produce functions of smaller arity that also work well when plugged into function pipelines.