A (mostly) Practical Guide to Functional Programming (in Javascript) Part 6

Posted on November 17, 2018

I didn’t scare you off with the talk of Monads, did I? Good. We’ll clarify more on them at some point. In this article I’ll go back over some more fundamental structures and show you several examples of how to use a special structure called, Validation, in order to validate data and collect errors.

A Short Digression into Theoryland

We talked a lot about Boxes so far and showed practical examples of how to map over their values and combine them together. This series of posts has been focused on the practical application of many theoretical concepts. In this post I am going to start giving you some of their formal names and describe what’s going on under the hood.

You may hear academics and other functional programmers mention the word, algebra. You may have encountered the subject of algebra in your education. In mathematics it refers to the manipulation of symbols using rules and operations. In the context of functional programming, and type theory in particular, an algebra refers to some operations and the set they operate over.

Let’s unpack that a bit:

In type theory a type can be thought of as a set (at first). A set is a collection of unique members. So the type “Integer” would refer to the set of all integers. And the type “String” would refer to the set of all strings.

There are certain operations one can perform over the set of integers: sum, multiply, etc. Those operations combined with the set form an algebra. In this example it’s the algebra of arithmetic.

However there are more sets than just strings and integers. There are structures such as Functor and Applicative which also have operations that form algebras. The nice thing about knowing how this all works is that we can use much of the same reasoning we learned from mathematical algebra in our programming!

Functor

We’ve already seen this one in action! Our Box is a functor.

Box(4).map(x => x * x)
// => 16

Functors are really powerful and allow us to build more interesting abstractions but they are, at their core, one of the more simple structures to understand. A Functor is any type (or set if you will) that implements one operation: map. As long as that implementation of map follows some rules you can say you have a Functor.

Those rules are:

// identity
Box(4).map(x => x) === Box(4)

// composition
Box(4).map(x => f(g(x))) === Box(4).map(g).map(f)

Our Box type implements the .map method we’ve seen so much of in our examples. Box is a functor. We’ve been using this algebra all along. By giving it a name we can associate that name with these rules and know that if something is a Functor we can call map on it and it will behave following the rules I have described.

The intuition to use when dealing with functors is that they allow us to apply a function to a value that is nested in a structure without changing the structure.

[4, 5, 6].map(x => x * 2)
// => [8, 10, 12]

The array is a Functor. The function I passed map changed the values nested in the structure of the array but the array I get back hasn’t changed: it’s still of length 3.

Applicative

This is another one but we haven’t seen much of it yet in our examples. An Applicative implements the Functor algebra and adds one more method: apply. An Applicative must also implement Functor but not all Functors are Applicatives. Simple, right? Don’t worry too much if that doesn’t make sense right now. Keep it in your back pocket when implementing your own types.

So remember in Javascript how we say functions are values?

const f = x => x * x
typeof f
// => 'function'

Well an Applicative allows us to apply some functions nested in a structure to a value while preserving structure. If Array in Javascript implemented the Applicative algebra it would have a method called, ap, and using it would look like this:

const f = x => x * x
const g = x => x + x
[f, g].ap([2, 3])
// => [4, 9, 4, 6]

There’s a lot more theory behind this to understand but for now know that if you have an Applicative you must also have a structure that is a Functor too.

Validation

Enough with the theory! Why does any of this matter? How can we use it to solve real problems we have on the job?

The rest of this article is going to look at a common problem we have when writing programs: validating some input data.

I’ve often come across code that needs to validate an email address or that a password meets some requirements. This seems like a straight-forward task and we might often come across code that looks like this:

import R from 'ramda'

const validateForm = formData => {
    const [email, password] = R.pick(['email', 'password'], formData)
    if (!isValidEmail(email))
        throw new Error(`Invalid email: ${email}`)
    if (!isValidPassword(password))
        throw new Error(`You need a stronger password`)
    return formData
}

And this might seem fine. Although your product manager comes back and says that we should be able to display all of the errors. As it stands our function will return the first error it encounters by throwing an exception! A validation error isn’t exceptional to begin with… we can solve this problem with functional programming!

First we need a Validation type. This is a data structure that needs to implement our Applicative algebra! Unlike in previous posts I will elide the definition of Validation and its methods as we will be using a library that does it for us: data.validation.

npm install data.validation ramda

This library gives us a Validation type which has two primary constructor functions: Success and Failure. We can use these functions to give definitions for isValidEmail and isValidPassword from our example:

import Validation from 'data.validation'

const {Success, Failure} = Validation

// Note this is an incorrect implementation of isValidEmail
// it is intended for demonstration purposes only
const isValidEmail = email =>
    (email.length >= 3 && email.includes('@')
    ? Success(email)
    : Failure([`Invalid email: ${email}`])

isValidEmail('a@b')
// => Success { value = 'a@b' }

isValidEmail('foobar')
// => Failure { value = ['Invalid email: foobar'] }

So far this is hopefully all straight-forward and boring. This will start moving faster in the next example. For passwords we might have more than one requirement. To borrow the example from the data.validation documentation: a valid password is at least 8 characters long and contains at least one non-alphabet character.

const isPasswordLongEnough = password =>
    password.length >= 8
    ? Success(password)
    : Failure([`Password must be at least 8 characters long`])

const isPasswordStrongEnough = password =>
    /^[\W]/.test(password)
    ? Success(password)
    : Failure([`Password must contain at least one non-alpha character`])

We now have functions that validate both requirements. How do we combine these together and collect the results so that when a user gives us a password we can return all of the errors or a success value?

Enter the Applicative

This is where we’re going to start using some operations on our Validation type from the Applicative algebra. We’re going to use that ap (for apply) method I mentioned. Recall that Validation is a type that is both an Applicative and a Functor. Therefore values created with either the Success or Failure constructors have a method called ap.

What ap does on Validation objects is take our value containing a function, our isPasswordLongEnough and apply it to a value in another structure of the same type: Validation. This will preserve the structure and we’ll get back a new Validation. The cool thing that happens with the values however is what makes this all work!

Validation will collect the results together of the Failure objects into an array. Otherwise it will return the Success object with the success value. Let’s see how that comes together to validate passwords:

const isValidPassword = password =>
    R.curryN(2, (x, y) =>
        Validation.of(password)
        .ap(isPasswordLongEnough(password))
        .ap(isPasswordStrongEnough(password))

isValidPassword('abc')
// => Failure { value = [
//     'Password must be at least 8 characters long',
//     'Password must contain at least one non-alpha character'
// ] }

isValidPassword('abc!')
// => Failure { value = ['Password myst be at least 8 characters long'] }

isValidPassword('abc!abc!')
// => Success { value = 'abc!abc!' }

Even More Validation

Back to our original example:

import R from 'ramda'

const validateForm = formData => {
    const [email, password] = R.pick(['email', 'password'], formData)
    if (!isValidEmail(email))
        throw new Error(`Invalid email: ${email}`)
    if (!isValidPassword(password))
        throw new Error(`You need a stronger password`)
    return formData
}

How can we re-write this using our new definitions of isValidEmail and isValidPassword to validate our form data? First we can validate each of the parameters:

const validateForm = formData => {
    const [email, password] = R.pick(['email', 'password'], formData)
    const validations = [isValidEmail(email), isValidPassword(password)]
    ...
}

Here we collect together the results of our validations into an array. How do we combine together these two objects? We’ll need a little help. When we need to combine things we need combinators. Fortunately ramda has a helpful combinator that works with Applicatives called, sequence.

It’s a little outside the scope of this article to explain sequence but what we need to know is that it collects together several Validation objects and combines either their Failure or Success values.

Continuing on…

const validateForm = formData => {
    ...
    return R.sequence(Validation.of, validations)
}

validateForm({email: 'foo@bar.com', password: 'abc!abc!'})
// => Success { value = ['foo@bar.com', 'abc!abc!'] }

validateForm({email: 'foo', password: 'abc!abc!'})
// => Failure { value = ['Invalid email: foo'] }

validateForm({email: 'foo', password: 'abc!'})
// => Failure { value = [
//     'Invalid email: 'foo',
//     'Password must be at least 8 characters long'
// ] }

Conclusion

Using some algebras we were able to re-write our exception-throwing validation code to return a data structure that collects our results together. When there are validation errors we get a list of errors. When we get success we get a list of valid values.

We also reduced the amount of code in our validation function:

const validateForm = formData => {
    const [email, password] = R.pick(['email', 'password'], formData)
    const validations = [isValidEmail(email), isValidPassword(password)]
    return R.sequence(Validation.of, validations]
}

We didn’t introduce any more control structures, intermediate variables, or state into our program and in return we got a pure function with a much more descriptive result.

The real lesson here is that we should prefer to return data structures instead of throwing exceptions! Our new code is much easier to test and has no side effects. This is a good thing.

Enjoy data.validation! Read more about Applicative from the fantasyland specs. Remember that ramda has functions for working with data structures that meet the fantasyland specs.