Call me Maybe

Replacing the built-in OR and TERNARY operators with Maybe monads.

This post is a little addendum to the excellent Safely Accessing Deeply Nested Values In JavaScript by A. Sharif. Please read his post first, it has some useful things!

In this post I will describe how I think about the functional code from the user's perspective.

The Problem

How do you access nested properties in an object? Let us say we have an object with user comments and we want to get comments for the first post

1
2
3
4
5
6
7
8
9
10
const props = {
user: {
posts: [
{ title: 'Foo', comments: [ 'Good one!', 'Interesting...' ] },
{ title: 'Bar', comments: [ 'Ok' ] },
{ title: 'Baz', comments: [] },
]
}
}
console.log(props.user.posts[0].comments)

What happens if a deeply nested property does not exist? The program crashes and burns!

1
2
3
console.log(props.users.posts[0].comments)
// 🔥🔥🔥
// it was props.user, not props.users!

Manual solution

So we need to check at every step if a property exists before accessing it. It gets tedious really really quickly. We can write a helpful utility function for safely getting a property. Just give this function a path and it will get you the deep value or a null if the path leads nowhere.

1
2
3
4
5
6
7
const get = (path, object) =>
path.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, object)
// safe access
const pathToComments = ['user', 'posts', '0', 'comments']
console.log(get(pathToComments, props)) //> ["Good one!","Interesting..."]
console.log(get(pathToComments, {})) //> null

Hint: I am using comment-value to embed output values as source comments.

User friendly API design

Notice how user-friendly our function get is because it puts the path argument first and object argument second. I always give this advice when designing the order of function's arguments

Place the argument most likely to be known first on the left

This advice plays very nicely with the built-in JavaScript partial application mechanism. We are likely to know the path we want to access before we get the actual object. Thus we can create a nice intermediate function for getting us the nested value in a particular case.

1
2
3
4
const pathToComments = ['user', 'posts', '0', 'comments']
const firstPostComments = get.bind(null, pathToComments)
console.log(firstPostComments(props)) //> ["Good one!","Interesting..."]
console.log(firstPostComments({})) //> null

Even friendlier API design

If we need to use the expression get.bind(null, ...) to create an intermediate access function every time we need it we start complaining. And we can complain to the library's authors and have them embed the partial application inside the get function itself. JavaScript functions are first class values, so we can easily split the get definition to accept its two arguments path and object in two calls. The change is so small with fat arrows functions, I will only show before and after 😀

1
2
const get = (path, object) => // before
const get = path => object => // after

Tiny change, yet it makes the user's life so much simpler.

1
2
const firstPostComments = get.bind(null, pathToComments) // before
const firstPostComments = get(pathToComments) // after

The savings in code length AND readability are now accruing every time someone calls get to create an intermediate deep access function, while there was no increase in complexity to the get code. We could go one step further and curry get using Ramda.curry function.

1
2
3
4
5
6
7
const {curry} = require('ramda')
const get = curry((path, object) => ...) // just wrap our "get" in "curry"
const pathToComments = ['user', 'posts', '0', 'comments']
// if we just know the path, give 1 argument
const firstPostComments = get(pathToComments)
console.log(firstPostComments(props)) //> ["Good one!","Interesting..."]
console.log(firstPostComments({})) //> null

The benefit of using 3rd party library to curry function, is that we can provide any number of arguments at once.

1
2
// provide both arguments together!
console.log(get(pathToComments, props)) //> ["Good one!","Interesting..."]

Nice!

Default value

If we have started designing a "get" function to be user-friendly, we can think about other common use cases. For example, what happens if the value is not there? We just return null, which looks bad. The user can specify an alternative using built-in JavaScript OR operator ||, but that again means it has to be done at the caller site.

1
2
console.log(firstPostComments(props) || 'no comment') //> ["Good one!","Interesting..."]
console.log(firstPostComments({}) || 'no comment') //> "no comment"

We cannot partially apply the built-in OR operator, right? Otherwise we could write something like (well, if the partial application were done from the right)

1
2
3
const ||NoComment = ||.bind(null, 'no comment')
console.log(firstPostComments(props) ||NoComment) //> ["Good one!","Interesting..."]
console.log(firstPostComments({}) ||NoComment) //> "no comment"

But we have to live within the standard JavaScript, so we have to find a way to specify the default value inside the get function. This is similar to how we added the partial application inside the function - we have noticed a common client use case and implemented it in the function itself by using curry.

We can go several ways about providing the default value. The simplest is to let the user specify the default value right inside the get function. Again we have to think about the signature design. When are we likely to know the default value to return? Consider the choices derived from the use cases

1
2
3
get('no comment', path, object) // 1
get(path, 'no comment', object) // 2
get(path, object, 'no comment') // 3

If we go with approach // 3, we have to provide the default value after the object, which means specifying it every time. While flexible, it quickly becomes verbose, and there is no built-in way to partially apply the right argument (there are library functions we could use like Ramda.partialRight)

1
2
console.log(firstPostComments(props, 'no comment')) //> ["Good one!","Interesting..."]
console.log(firstPostComments({}, 'no comment')) //> "no comment"

If we place the default value as the first or second argument in get signature, we can easily apply it. To me these two positions seem equivalent. Let us place the default value at first position, so the user has to think about "unhappy path" right away.

1
2
3
4
5
6
7
const get = curry((defaultValue, path, object) =>
path.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : defaultValue, object))
const pathToComments = ['user', 'posts', '0', 'comments']
const firstPostComments = get('no comment', pathToComments)
console.log(firstPostComments(props)) //> ["Good one!","Interesting..."]
console.log(firstPostComments({})) //> "no comment"

Perfect. Yet we now have a question of what the function get does. On one hand it goes through the object to grab the deeply nested value. On the other hand, it also returns the default value if the path does not exist, or the resulting value is falsy. This breaks my heuristic for determining when a code fragment grows too large:

You have to use words like "AND / OR" to describe what the code does

In our case, the "OR" functionality can be thought as the second step that runs after the original get returns a value from an object. Let us write or and use it to return the default value if the original get (now renamed to _get) returns nothing.

1
2
3
4
5
6
7
8
9
10
11
12
13
const _get = (path, object) =>
path.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, object)

const or = (defaultValue, x) => x || defaultValue

const get = curry((defaultValue, path, object) =>
or(defaultValue, _get(path, object)))

const pathToComments = ['user', 'posts', '0', 'comments']
const firstPostComments = get('no comment', pathToComments)
console.log(firstPostComments(props)) //> ["Good one!","Interesting..."]
console.log(firstPostComments({})) //> "no comment"

When designing or we should follow the same API design philosophy as designing get function. The typical use cases should determine the order of arguments, and if necessary, the built-in application. Notice how we place the default value on the left, because we are likely to know it early. I am not going to completely rewrite the above or, but that is definitely possible and would be equivalent to Ramda.either (Ramda.or is meant to cast values into Booleans).

Executing code based on returned value

So far, we have grabbed the nested property and printed it. We have even built-in a way to print default value if the nested property does not exist. What if we want to run multiple commands but only if the value exists? Hmm, we now have two problems:

  1. It is harder to tell when the property is invalid. Used to be easier: if the returned value is undefined or null then it meant the property is invalid. Now we need to compare to the default value, which is trickier.
  2. We have to use JavaScript if (...) { ... } construct, which is NOT composable.

Here is an example the explains the point # 2. Imagine we want to run foo() and bar() if the property is present. It is easy to do right after the property access.

1
2
3
4
5
6
7
8
const foo = () => console.log('in foo')
const bar = () => console.log('in bar')
if (firstPostComments(props) !== 'no comment') {
foo()
bar()
}
// in foo
// in bar

What if we want to run more functions if there is a property? We have to code them right there inside the if (firstPostComments(props) !== 'no comment') { ... } block. Alternative - return the fetched property from the code and have the check repeat outside.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const foo = () => console.log('in foo')
const bar = () => console.log('in bar')
function getComments() {
const comments = firstPostComments(props)
if (comments !== 'no comment') {
foo()
bar()
}
return comments
}
// run another function if there is a comment
const baz = () => console.log('in baz')
if (getComments() !== 'no comment') {
baz()
}
// in foo
// in bar
// in baz

This code is bad, and we need to solve both problems at once. We need a way to "flag" the situation when the original property was invalid to avoid comparing it to no comment value everywhere, and we need a way to "queue up" functions to execute if the returned property is valid.

Enter Maybe

Let us solve all problems by returning an object of Maybe type instead of a "plain" value. This object is like a box - it can keep anything inside; the box does not care. But what it can also do is "know" if the value inside is "valid" or "bad". We will flag a value when creating the box (values that are undefined or null are called "nullable" and that tells the Maybe box that they are "bad" on creation)

1
2
3
4
5
6
7
8
9
10
11
const Maybe = require('data.maybe')
// returns value or "null"
const _get = (path, object) =>
path.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, object)

const get = curry((path, object) => {
const value = _get(path, object)
// put the "value" into a Maybe box
return Maybe.fromNullable(value)
})

The get definition just gets the value and then returns it wrapped in "Maybe" box. We can shorten this a little bit using equivalent code (or not) depending on the mood.

One thing that we can notice right away is that the value we print are not "simple". Instead they are objects that know if the value inside is bad (property #type:Maybe.Nothing) or good (same property is Maybe.Just)

1
2
3
4
5
6
const pathToComments = ['user', 'posts', '0', 'comments']
const firstPostComments = get(pathToComments)
console.log(firstPostComments(props))
//> {"#type":"folktale:Maybe.Just","value":["Good one!","Interesting..."]}
console.log(firstPostComments({}))
//> {"#type":"folktale:Maybe.Nothing"}

Cool things we can do now: we ca use the response "box" and run new functions easily if and only if the value inside is good. Printing a good value (and doing nothing for the bad one) is simply:

1
2
3
4
firstPostComments(props).map(console.log)
// [ 'Good one!', 'Interesting...' ]
firstPostComments({}).map(console.log)
// nothing happens, "console.log" is NOT triggered

If we get the comments deep somewhere inside our code, we can simply return the Maybe box and let the other code do something if the value is good.

1
2
3
4
5
6
7
function getComments() {
return firstPostComments(props).map(foo).map(bar)
}
getComments().map(baz)
// in foo
// in bar
// in baz

This is why people say that wrapped values (like Maybe monad) are great for composability; because you can compose multiple functions easily using them.

Single vs multiple null checks

So far we have created a single Maybe after getting the deeply nested property. But if we look inside the get function we see the same pattern inside the reduce callback

1
2
3
4
5
6
7
const _get = (path, object) =>
path.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, object)
// callback (xs, x):
// if property and value is "good"
// implicitly run "callback" with next part of the path
// (implicitly because we are inside Array.reduce)

Hmm, this looks like our Maybe box! Maybe.fromNullable puts value in the Maybe box, and then lets Maybe decide: if the value is good, calls a function, which constructs another box.

1
2
3
4
const get = (path, object) =>
path.reduce((box, property) =>
box.map(xs => Maybe.fromNullable(xs[property])),
Maybe.fromNullable(object))

Yet there is a tiny difference, which creates a problem and does not give us a valid result

1
2
3
const pathToComments = ['user', 'posts', '0', 'comments']
get(pathToComments, props).map(console.log)
// Maybe {}

We have to pass a function to Maybe.map that returns not a simple value, but another Maybe box. If we just use map we quickly get into a situation where boxes are nested inside other boxes, just like a Russian Matryoshka doll.

1
2
3
4
console.log(Maybe.fromNullable('foo')
.map(() => Maybe.fromNullable('bar'))
.map(() => Maybe.fromNullable('baz')))
// Maybe { value: Maybe { value: 'baz' } }

In our case, the first iteration of get(..., props) fetches "user" value, which returns Maybe{ Maybe {...} } and then it tries to fetch "posts", which does not exist on the Maybe itself. Thus it catches it and returns just nothing.

We need to deal with this somehow. We cannot use map because we return an already wrapped value by checking inside Maybe.fromNullable. The wrapped types like Maybe thus implement a separate method to call functions that return wrapped results. In data.maybe case it is chain - a wrapped value returned from the chained callback replaces the value in the current box.

1
2
3
4
console.log(Maybe.fromNullable('foo')
.chain(() => Maybe.fromNullable('bar'))
.chain(() => Maybe.fromNullable('baz')))
// Maybe { value: 'baz' }

Let us replace map with chain inside our get function and all should work out

1
2
3
4
5
6
7
8
9
10
11
const get = (path, object) =>
path.reduce((box, property) =>
box.chain(xs => Maybe.fromNullable(xs[property])),
Maybe.fromNullable(object))
const pathToComments = ['user', 'posts', '0', 'comments']
get(pathToComments, props).map(console.log)
// [ 'Good one!', 'Interesting...' ]
get(pathToComments, {}).map(console.log)
// nothing happens, "console.log" is NOT called
get(pathToComments).map(console.log)
// nothing happens, "console.log" is NOT called

Composing chains

Notice how we have replaced a ternary operator (xs && xs[x]) ? xs[x] : null inside the reduce callback (and the outside Maybe.fromNullable) with a Maybe.chain() function? Nice! Having a function call instead of the built-in JavaScript ternary operator allows us to simplify it (just like we simplified code when we replaced built-in || operator).

I am going to simplify get in several steps for clarity. Let us first replace box.chain with Ramda.chain called on a box. It is a good idea to write a few unit tests before refactoring code like this.

1
2
3
4
5
6
7
8
9
10
// before
const get = (path, object) =>
path.reduce((box, property) =>
box.chain(xs => Maybe.fromNullable(xs[property])),
Maybe.fromNullable(object))
// after
const get = (path, object) =>
path.reduce((box, property) =>
R.chain(xs => Maybe.fromNullable(xs[property]), box),
Maybe.fromNullable(object))

Second, let us use a function call to get a property of an object, rather than using built-in object[name] syntax. In our case, we can use Ramda.prop function.

1
2
3
4
5
// use Ramda.prop instead of xs[property]
const get = (path, object) =>
path.reduce((box, property) =>
R.chain(xs => Maybe.fromNullable(R.prop(property, xs)), box),
Maybe.fromNullable(object))

Then we can abstract nested function calls Maybe.fromNullable(R.prop(property, xs) into its own function.

1
2
3
4
5
6
7
// extract safe property access
const safeProp = R.curry((property, xs) =>
Maybe.fromNullable(R.prop(property, xs)))
const get = (path, object) =>
path.reduce((box, property) =>
R.chain(safeProp(property), box),
Maybe.fromNullable(object))

Hmm, if we have safeProp, maybe we do not need to always use get. In a simple case, like grabbing a value a few levels deep, we can just chain it ourselves.

1
2
3
4
5
6
Maybe.fromNullable(props)
.chain(safeProp('user'))
.chain(safeProp('posts'))
.chain(safeProp('0'))
.chain(safeProp('comments'))
.map(console.log)

Looks awful 🤔

Luckily, there is utility methods for composing wrapped values that have chain interface. In Ramda it is composeK and pipeK functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
const getComments = R.pipeK(
safeProp('user'),
safeProp('posts'),
safeProp('0'),
safeProp('comments')
)
getComments(props).map(console.log)
// [ 'Good one!', 'Interesting...' ]
getComments({}).map(console.log)
// nothing is done, "console.log" is NOT triggered
getComments().map(console.log)
// crashes and burns, because we are not protecting
// against the very FIRST access

In order to protect against the missing or undefined first value, just put the protection function first. It is that easy with functional programming.

1
2
3
4
5
6
7
8
9
const getComments = R.pipeK(
Maybe.fromNullable,
safeProp('user'),
safeProp('posts'),
safeProp('0'),
safeProp('comments')
)
getComments().map(console.log)
// nothing is done, "console.log" is NOT triggered

Safe and sound.

Extra info

if there is another resource that might be helpful, leave a comment.