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

Posted on November 20, 2017

This is the fifth part of a series of posts where I show you how to use functional programming techniques to improve every day code. If you want to follow along I suggest starting from the beginning:

In the last couple of posts we discovered a pattern for structuring code that other people have discovered and called, monads. It turns out that this pattern is in many places you might not even realize. In Javascript we have Promises, which while not implemented as a monad in the strict sense, do behave a lot like monads do. We saw a practical example of using this pattern to structure UIs. In this post we’re going to look at patterns you might find in imperative code and how you can use monads to refactor that code to be pure and composable.

Parsing User Input

Almost every program we work with has to deal with input from somewhere. When this input comes from users our code should be defensive to prevent errors and unexpected behavior. In imperative programs this often means that a good amount of our code is spent checking and validating input at the edges of our program.

You might find code like this:

const moment = require('moment')

const covertUnixTimestamp = timeStamp =>
    moment(new Date(parseInt(timeStamp)))
    .format('YYYY-MM-DD HH:mm Z')

What will this return when timeStamp is undefined? It will return the string, “Invalid date.” And if we give it a valid integer or string-like integer we should get an timestamp formatted according to that constant on the last line.

Either

It turns out the pattern we have here is a function that can either succeed or fail, do one thing or another, etc. It sounds so mundane when we say it like that. Well it turns out that if we use the Either pattern we can control a lof the complexity of validating input and we can compose our creations from smaller functions.

First let’s use a few helper libraries: ramda and fantasy-eithers. We’ll start with a basic example:

const {Left: Fail, Right: Ok} = require('fantasy-eithers')
const ramda = require('ramda')

const fromNullable = v => R.isNil(v) ? Fail(v) : Ok(v)

What are Left and Right? You can think of them like constructors for the Either type. Here we rename them using ES6 destructuring syntax into Fail and Ok to make our code a little more clear. The original names were just a covention based on their position in the type-signature of Either.

Our function, fromNullable, creates an Either type. If the paramter is null or undefined then we get Fail and otherwise we get Ok. Pretty simple so far, right?

Let’s test it out:

> fromNullable(undefined)
definitions { l: undefined }
> fromNullable(null)
definitions { l: null }
> fromNullable('foo')
definitions { r: 'foo' }

The value being printed out here is an implementation detail of the library we’re using. The l means that the object being returned is constructed from the Left constructor. The cool thing is that we can safely .map over the result:

> fromNullable(undefined).map(x => x + '!')
definitions { l: undefined }
> fromNullable('foo').map(x => x + '!')
definitions { r: 'foo!' }
> fromNullable('world').map(x => x + '!').map(x => `Hello, ${x}`)
definitions { r: 'Hello, world!' }
> fromNullable(undefined).map(x => x + '!').map(x => `Hello, ${x}`)
definitions { l: undefined }

Well this is Javascript so it’s not exactly type safe:

> fromNullable({}).map(x => x + '!')
definitions { r: '[object Object]!' }

But it does give us some structure for our refactoring. We need to write a similar function to construct an Either from our timeStamp parameter. We need to either parse a date or return an error string:

const eitherDate = timeStamp => {
    const m = moment(new Date(parseInt(timeStamp)))
    return m.isValid() ? Ok(m) : Fail(`Invalid date: ${timeStamp}`)
}

We just took the line from our original imperative implementation and we use the .isValid method to determine if we need to return Ok or Fail. And now if we have a date we want to format it back to a string. We can do that safely with .map:

const parseTimestamp = timeStamp =>
    eitherDate(timeStamp)
    .map(dt => dt.format('YYYY-MM-DD HH:mm Z'))

And if we test it out:

> parseTimestamp(100)
definitions { r: 'definitions { r: '1969-12-31 19:00 -05:00' }' }

Let’s just do one small tweak to take that constant and make it a default parameter:

const parseTimestamp = (timeStamp, {format = 'YYYY-MM-DD HH:mm Z'} = {}) =>
    eitherDate(timeStamp)
    .map(dt => dt.format(format))

And now I think we’re good:

> eitherParseDate(100, {format: 'YYYY-MM-DD'})
definitions { r: '1969-12-31' }
> parseTimestamp(100)
definitions { r: 'definitions { r: '1969-12-31 19:00 -05:00' }' }

How does this compare to our original implementation? For one, our new, functional API is more explicit about what to do in the failure case. The user cannot forget to handle the failure case either! Let’s see what I mean:

> var old = convertUnixTimeStamp(userInput)

What is the value of old if userInput is undefined?

Right: it is the string "Invalid date.".

What is the value of old if userInput is a Number?

Right: it is a string that is a parsed and formatted time stamp.

How do I know that when I’m calling convertUnixTimestamp which kind of result I have? Maybe I could try:

if (old === 'Invalid date.') {
    console.log('Oops!')
} else {
    console.log(`Got: ${old}!`)
}

Which is fine. I have an intermediate variable and an if statement. Not bad.

My parseTimestamp function makes you think about this case up-front when you want to get a value out of the Either, like we learned, with .fold:

const defaultTimestamp = parseTimestamp(userInput)
    .fold(err => console.log(err),
          result => console.log(result))

Which is the equivalent to the if-statement solution… but we can do more here. The Fail case could mean we need to return a default timestamp in some cases. Or maybe our user needs to do more intermediate transformations on the input before checking for a result in .fold. Or they need to compose it with other functions. All of these scenarios are trivial with this pattern.

Whenever you see a pattern of conditionals in your code, especially when those conditionals deal with user input, it’s a good use case for refactoring your code to use Either.