Abstractions via Functions
Proponents of functional programming are loud. They preach the gospel of immutability and simplicity at every opportunity, and make me feel like I need to repent for my stateful ways.
Needless to say, I am intrigued.
When so many smart people speak so adamantly about something, and the only other side of the argument is from people who haven’t tried it, I tend to believe there’s something to their beliefs.
So, I tried to pick up Clojure. It’s my sixth language, so it wasn’t a struggle to pick up the syntax. But it has been a huge struggle to learn how to write web apps without objects to provide layers of abstractions for my code.
In fact, I failed so completely at this that I gave up trying to learn Clojure. I rewrote the app I had started in Clojure and Clojurescript with Rails and React.
Fortunately, a friend recommended I read the book Functional Programming in Javascript by Michael Fogus. This book was perfect! I already knew Javascript, so it let me focus on functional programming without being slowed down by unfamiliar syntax, libraries and parentheses (oh god, the parentheses!).
This book helped me understand the fundamental concept I was missing when I got fed up with Clojure: how to use functions to create layers of abstraction in code.
An example
The example that drove the idea home for me was when he shows an implementation of input validation. I’ve implented those several times before in Javascript, and they always look something like this:
$("#sign-up-form").submit(function(event) {
var params = {
email: $(this).find("#email").val(),
password: $(this).find("#password").val(),
passwordConfirmation: $(this).find("#passwordConfirmation").val(),
};
var errors = validateSignUpForm(params);
if (errors.length > 0) {
event.preventDefault();
// display errors...
}
});
function validateSignUpForm(params) {
var errors = [];
if (params.email.length > 0) {
errors.push("Email is required");
}
if (params.password.length > 0) {
errors.push("Password is required");
}
if (params.password !== params.passwordConfirmation) {
errors.push("Password confirmation doesn't match");
}
return errors;
};
This code is procedural, full of duplication, and hard to read.
Using the full power of functional programming, we can reimplement validateSignUpForm
like this:
function validateSignUpForm(params) {
return validate(
params,
presenceValidation("email", "Email is required"),
presenceValidation("password", "Password is required"),
confirmationValidation("password", "passwordConfirmation", "Password confirmation doesn't match")
);
}
Much better! This code is declarative instead of procedural. Duplication has been reduced, and it’s much easier to understand. In fact, it’s strikingly similar to ActiveRecord validations in Ruby. The key to these wins is simple: higher-order functions! Let’s take a deeper look.
Higher-order functions to create abstractions
In our second version, all validateSignUpForm
has to do is call the function validate
with the input from the user and a list of the validations it wants to check. But what are those validation functions presenceValidation
and confirmationValidation
?
Notice the input to these functions. Specifically, note how they don’t receive the user input data. How the heck are those functions going to validate the user’s input then? Well, they won’t. But their return values will!
The validation functions will return new functions that will do the actual work of performing the validation against the user input. These are higher-order functions at work. Let’s look at one:
function presenceValidation(field, errorMessage) {
return function(params) {
if (params[field].length < 0) {
return errorMessage;
}
};
}
So we return a function that takes the user input data and returns an error message if there was an error, or nothing otherwise. Now we’re getting somewhere.
The final piece to put this all together is the validate
function. As we saw, it receives the user input data and a list of validator functions (making it a higher-order function as well). So it has all the pieces it needs to return the array of error messages we’re after.
function validate(params /*, validators */) {
var errors = [], i = 1;
for (i; i < arguments.length; i++) {
var errorMessage = arguments[i](params);
if (errorMessage) {
errors.push(errorMessage);
}
}
return errors;
}
Voila! The validate
function loops through the validators it’s given, calls each one with the user input data, and collects the error messages to return.
How far we’ve come
Alone, the pieces are simple. Composed, they provide a powerful, elegant, flexible abstraction for validating user input! It’s also completely stateless, by the way, so it’s a cinch to test.
I think I finally understand how to use functional programming paradigms to solve real-world web application problems. The next time I try to tackle Clojure, I’ll be ready :)