Blog

Some Higher Order Abstractions in JS

Posted on August 28, 2017 by Clive

This first article in the series concerns itself with discussing higher order functional programming (FP) concepts or rather abstractions and how they can apply in the context of JavaScript. Some of these concepts will be unfamiliar to those who approach JavaScript from an object-oriented perspective.

Topics that will be discussed in this post are Functors, Maybe, Either and Monads.

If thats interesting, then read on.

 

Intro into FP

If you are unaware of the programming paradigm and need a starter for 10, then you can't go far wrong by reading Functional Programming with JS first.

Some of the functions used here as part of the basis code examples are explained in my Functional Programming with JS article. In particular we use curry() and compose() as given there. If you need further elucidation then I might suggest that you take a look at the documentation around the Ramda.js library, Curry and Compose.

Code examples provided are only for illustration purposes. They are in no way designed be working code.

We're going to dive in at the middle and go right to the deep end...

 

Functors

Scary looking name, simple concept. So what are Functors anyway?

Simply put a Functor is Value Container the can be mapped over.

A value container is simply a container that holds one or more values, examples are arrays/lists, dictionaries and objects. Mapping is discussed in the intro article.

Lets take a look at a couple of examples

// Example 1: Array Prototype map function
const res = [1,2,3].map( (i) => i * 2 ); // res = [2,4,6]

The JS array type is probably the most typical Functor that you will have come across. We use const to emphasise that we are dealing with immutable values

// Example 2: General value container

// Value container
const Container_ = function (value) {
    this.value = value;
}
Container_.prototype.map = function (f) {
    return Container(f(this.value));
}

// Composable constructor function
const container = value => new Container_(value);

container(10); // Container_ { value: 10 }

// Map is composable
container([1,2,3]).map(reverse).map(first) // => container(3)

// Map function
const map = curry( (f, obj) => obj.map(f) );

// container needs to be available here
container(3).map(add(1)); // => container(4)

// Can be lazily evaluated here through composition
map(add(1), container(3)); // => container(4)

Here we're building our own generic value container. The container doesn't care what type it holds, but it does provide a mapping function. The value container uses JavaScript object semantics here, but that's just a convenience.

In order to gain access to the value(s) held in the value container so that the transformation required can by applied, functions need to be mapped over the Functor.

 

Maybe

The Functor Maybe will be a concept that most JavaScript developers are probably not familiar with and the first that we will discuss here. Functional languages like Haskell (i.e. those with a comprehensive and sometimes bewildering/baffling type system) express this as a union type, but the semantics of this are that a Maybe() will return either a Just(value) or Nothing().

For JS this has the advantage of being a way to avoid explicitly null and undefined checks that can otherwise break program flow and hinder compatibility.

Maybe() can be one or the other - Just() or Nothing().

Just() and Nothing() are also Functors.

There are libraries out there that describe Maybe, Maybe.js being one, but simply put:

// Set up the various value containers because this is JS
const Just = function (x) { this.val = x; };
const Nothing = function (x) { this.val = x; };

// Null check or it doesn't work
const notThere = x => (x === undefined || x === null);

// Create the Maybe value container
const Maybe = x => {
    return notThere(x) ?
        new Nothing() :
        new Just(x);
};

Maybe.Just = Just;
Maybe.Nothing = Nothing;

// Because it's a monad provide a mapping function
Nothing.prototype.map = fun => new Nothing();
Just.prototype.map = fun => new Just(fun(this.val));

module.exports = Maybe;

FYI, Maybe has far more functionality than that described here, but I'm not going into it.

So the question that you're asking right now is: So why do I need this?

The idea here is to provide a null check that is composable and that also doesn't interfere with the flow of an application. Let's see some code

// Composing WITHOUT Maybe

// compose a function where we want the first letter from a match
const myMatch = compose( first, match(/foo/g) );

// match with expected value
myMatch('foo'); // => 'f'

// match with unexpected value
myMatch('bar'); // => throws exception

The issue here is that myMatch is expecting to match, but 'bar' cannot be matched by /foo/, therefore an undefined is passed to first() which can't do anything with it and therefore throws an exception.

// Composing WITH Maybe

// compose a function where we want the first letter from a match
const myMatch = compose( map(first), Maybe, match(/foo/g) );

// match with expected value
myMatch('foo'); // => Just('f')

// match with unexpected value
myMatch('bar'); // => Nothing()

As you can tell from the code, there are a couple of differences, first off we've added the Maybe. This is applied to the result of the match() function being applied to the argument passed to myMatch(), meaning that if you want to apply first(), it needs to be mapped - if you remember Maybe is a Functor and therefore its value is not openly available. So in the first attempt, you get a Just('f').

In the second attempt, because the Maybe is a Nothing(), applying (mapping) first over it returns a Nothing(). Runtime explosion averted.

 

Either

The next Functor that we'll discuss is Either.

Either returns either the Left() or the Right(). Usually, Right() is returned on correct execution path and Left() is used to stop processing based on error conditions. This is another mechanism that can be used to prevent errors from propagating through your application.

It is in fact a disjunction operator and can be used a little like a Maybe, but wereas a Maybe will return Nothing(), Either will return a value.

This is best demonstrated with a bit of code:

// Set up the value containers

const Right = function (x) { this.val = x; };
const Left = function (x) { this.val = x; };

// Create Either

const determineAge = x => x ? Right(x) : Left("Error");

// Compose the functions together

compose( map( add(1), determineAge(32) ) );  // => Right(32);
compose( map( add(1), determineAge(null) ) );  // => Left("Error");

 

Monads

The scary title monad is a wrap-around term for nestable computations created by Functors that are joined together.

To be a monad a Functor that also provides an of() function (called a Pointed Functor) will also provide a mjoin() and/or chain() function.

mjoin() is a typed join function. Both Functors that are used in the join function need to be of the same type, eg: Maybe( Maybe (5) ) => Maybe(5) or Either( Either( <fileContents>) ) => Either(<fileContents>). It works a lot a List.flatten(), but for Functors.

A example using a monad:

// fetch a hypothetical id

const getOrderId = compose( Maybe, get('orderId') );  // => Maybe(Integer)

// find hypothetical order based on that id

const findOrder = compose( Maybe, API.findOrder );  // => Maybe(OrderObject)

// pull the tracking information

const getOrderTracking = compose( mjoin, map(getOrderId), findOrder );  // => Maybe(TrackingObject)

// render page - only one map required over getOrderTracking

renderPage = compose( map( renderTemplate ), getOrderTracking );  // => Maybe(HTML)

// finally render the page out
renderPage(1111)

Same example without the monad:

// fetch a hypothetical id

const getOrderId = compose( Maybe, get('orderId') );  // => Maybe(Integer)

// find hypothetical order based on that id

const findOrder = compose( Maybe, API.findOrder );  // => Maybe(OrderObject)

// pull the tracking information

const getOrderTracking = compose( map(getOrderId), findOrder );  // => Maybe(Maybe(TrackingObject))

// render page - only one map required over getOrderTracking

renderPage = compose( map( map( renderTemplate ) ), getOrderTracking );  // => Maybe(Maybe(HTML))

// finally render the page out
renderPage(1111)

Notice that the renderPage composition requires two maps, decreasing the readability.

There's nothing particularly scary about monads, but they theory behind them can make them seem more complicated than they really are.

See you next time.

DistortedThinking.Agency
Phone: +44 (0)7815 166434Email: clive@distortedthinking.agency
www.distortedthinking.agency