Some Higher Order Abstractions in JS
Posted on August 28, 2017 by Clive in JavaScript, Functional Programming
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.
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… 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 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 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. 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 explicity null and undefined checks that can otherwise break program flow and hinder composibility. 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: 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 composeable and that also doesn’t interfere with the flow of an application. Let’s see some code 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. 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. 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: 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: A example using a monad: Same example without the monad: Notice that the renderPage composition requires two maps, decreasing the readability. There’s nothing particularly scarey about monads, but they theory behind them can make them seem more complicated than they really are. See you next time.Intro into FP
Functors
// Example 1: Array Prototype map function
const res = [1,2,3].map( (i) => i * 2 ); // res = [2,4,6]
// 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)
Maybe
// 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;
// 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
// 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()
Either
// 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
Maybe( Maybe (5) )
=> Maybe(5)
or Either( Either( <fileContents>) )
=> Either(<fileContents>)
. It works a lot a List.flatten(), but for Functors.// 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)
// 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)