Functional Programming In JavaScript

Functional Programming In JavaScript

  • 2017-11-10 04:31 AM
  • 341

Functional Programming In JavaScript

Functional Programming(FP) can change the way you program for the better. But it’s hard to learn and many posts and tutorials don’t go into details like Monads, Applicative and so on and don’t seem to use practical examples to help us use powerful FP techniques on a daily basis. That’s why I thought of writing a post to make it easier to use FP techniques.

Please Note: The emphasis in this blog is on WHY xyz feature is required than just WHAT xyz feature is.

In Part 1, you’ll learn Functional Programming basics, Currying, Pure Functions, “Fantasy-land” specs, “Functors”, “Monads”, “Maybe Monads” and “Either Monads” via couple of examples.

Functional Programming

Functional Programming is a style of writing programs by simply composing a set of functions.

Essentially, FP asks us to wrap virtually everything in functions, write lots of small reusable functions and simply call them one after the other to get the result like: (func1.func2.func3) or in a compose fashion, like: func1(func2(func3())).

But in order to actually write programs in this style, functions need to follow some rules and overcome some challenges like the ones mentioned below:

The FP Challenges:

If everything can be done by composing a set of functions…

  1. How can we handle if-else condition? (Hint: “Either” Monad)
  2. How can we handle Null Exceptions (Hint: “Maybe” Monad)?
  3. How to ensure functions are truly “reusable” and can be reused anywhere, (Hint: Pure functions, referential transparency)?
  4. How to ensure the data we pass to it is unchanged so that we can reuse the data elsewhere(Hint: Pure functions, immutability)?
  5. If a function is taking multiple values but chaining can only pass a single value, how can we still make it part of a chain (Hint:currying” and “higher-order functions”)?
  6. and more….

The FP Solution:

In order to deal with all these challenges, fully Functional Programming languages like Haskell provides various tools and concepts from mathematics like “Monads”, “Functors” and so on out-of-the-box.

While JavaScript doesn’t provide many of the tools out-of-the-box, thankfully it has enough FP features that allows people to write libraries.

Fantasy-Land Specs And FP Libraries

Libraries that want to provide features like Functors, Monads and so on, need to implement functions/classes that follow some specs in order to provide functionalities like they are in languages like Haskell.

Fantasyland specs is one of the prominent specs that explains how each JS functions/classes should behave.

The above picture shows all the specs and their dependencies. Specs are essentially laws and similar to “interfaces” in Java. From JS perspective, you can think of specs as “classes” or constructor functions that implement some methods like (map, of, chain and so on) according to the specification.

For example:

A JS class is a “Functor” if it implements a “map” method. And that map method must work as per spec (ps:This is simplified version and there are more rules).

Similarly, a JS class is an “Apply Functor” if it implements “map” and “ap” functions as per spec.

Similarly, a JS class is a “Monad” (aka Monad Functor), if implements requirements of “Functor”, “Apply”, “Applicative”, “Chain” and “Monad” itself (because of the dependency chain).

Fantasy-Land Spec compliant Libraries

There are several libraries that implement FL spec. For example: monet.js,barely-functional, folktalejs, ramda-fantasy (based on Ramda), immutable-ext (based on ImmutableJS), Fluture and more.

What Libraries Should I Use?

Libraries like lodash-fp, ramdajs, only enable you to start writing in FP style. But they don’t provide functions to use key mathematical concepts like Monads, Functors, Foldables to actually solve real-world problems.

So, in addition to them you’ll have to use one of the libraries that follow fantasy-land spec. Some such libraries are:monet.js, barely-functional, folktalejs, ramda-fantasy (based on Ramda), immutable-ext (based on ImmutableJS), Fluture and more.

OK, now that we know the basics, Let’s see some practical examples and learn various FP features and techniques through those examples.

Example 1 — Dealing With Null Checks

Topics covered: Functors, Monads, Maybe Monad, Currying.

Use-case: We want to show different index webpage depending on the user’s “primary” language (inside user’s prefs, see below). And we need to write getUrlForUser that returns appropriateURLfrom the list of URLs(indexURLs) for the user’s (joeUser) primary language(“spanish”).

The problem is: the primary language could be null. The user itself could be null (not logged in). The primary language might not be available in our list of indexURLs. So we’ll have to take care of lots of “nulls” or “undefined”.

//TODO Write this in Imperative v/s Functional style
const getUrlForUser = (user) => {
//todo
}
//User object
let joeUser = {
    name: 'joe',
    email: '[email protected]',
    prefs: {
        languages: {
            primary: 'sp',
            secondary: 'en'
        }
    }
};
//Global indexURLs map for different languages
let indexURLs = {
    'en': 'http://mysite.com/en',  //English
     'sp': 'http://mysite.com/sp', //Spanish
    'jp': 'http://mysite.com/jp'   //Japanese
}
//apply url to window.location
const showIndexPage = (url) => { window.location = url };

Solution (Imperative Vs FP):

PS: Don’t worry if the FP version looks hard to understand, I’ll cover them step-by-step later in this post.

//Imperative:
//Too many if-else and null checks; relying on global indexURLs; decided that "en" urls are default for any country
const getUrlForUser = (user) => {
  if (user == null) { //not logged in
    return indexURLs['en']; //return default page
  }
  if (user.prefs.languages.primary && user.prefs.languages.primary != 'undefined') {
    if (indexURLs[user.prefs.languages.primary]) {//if translation exists,
      return indexURLs[user.prefs.languages.primary];
    } else {
      return indexURLs['en'];
    }
  }
}

//call
showIndexPage(getUrlForUser(joeUser));


//Functional Programming:
//(Little hard to understand at first but is more robust and bug free)
//FP techniques used: Functors, "Maybe Monad" and "Currying"
const R = require('ramda');
const prop = R.prop;
const path = R.path;
const curry = R.curry;
const Maybe = require('ramda-fantasy').Maybe;

const getURLForUser = (user) => {
    return Maybe(user)//wrap user in a Maybe object 
        .map(path(['prefs', 'languages', 'primary'])) //use Ramda's to grab primary language
        .chain(maybeGetUrl); //pass language to maybeGetUrl &  get url or null Monad
}

const maybeGetUrl = R.curry(function(allUrls, language) {//curry to convert this to a single arg func
    return Maybe(allUrls[language]);//return Monad(url | null)
})(indexURLs);//pass indexURLs instead of accessing globally


function boot(user, defaultURL) {
   showIndexPage(getURLForUser(user).getOrElse(defaultURL));
}

boot(joeUser, 'http://site.com/en'); //'http://site.com/sp'

OK, Let’s first understand several FP concepts and techniques used in this solution.

Functors

Any class(or construction function) or a datatype that stores a value and implements “map” method is called a “Functor”.

For example: An Array is a “Functor”. Because an Array can store values and has “map” method that allows us to map a function to the values it’s storing.

const add1 = (a) => a+1;
let myArray = new Array(1, 2, 3, 4); //store values
myArray.map(add1) // -> [2,3,4,5] //applies functions

Let’s write our own Functor “MyFunctor”. It’s simply a JS class (constructor function) that stores some value and implements a “map” method. This “map” method applies the function to the stored value and then creates a new Myfunctor from the result and returns that new MyFunctor.

const add1 = (a) => a + 1;
class MyFunctor { //Custom "Functor"
  constructor(value) {
    this.val = value;
  }
  map(fn) {   //Applies function to this.val + returns new Myfunctor
   return new Myfunctor(fn(this.val));
  }
}
//temp is a Functor instance that's storing value 1
let temp = new MyFunctor(1); 
temp.map(add1) //-> temp allows us to map "add1"

PS: Functors also need to implement other specs (see Fantasyland specs) in addition to “map” but I’m not going to cover them here.

Monads

Monads are also Functors, i.e. they have “map” method but implements more methods than just “map”. If you look at the spec dependency graph again, you’ll see that also need to implement various other features in different specs like: “Apply” (ap method), “Applicative” (ap and of method), and “Chain” (chain method).

Simplified Explanation: In JS, Monads are classes or constructor functions that store some data and implements “map”, “ap”, “of” and “chain” methods that do something with the stored data as per spec.

Below is a sample implementation so you get an idea of the internals of the Monad.

//Monad - a sample implementation
class Monad {
    constructor(val) {
        this.__value = val;
    }
    static of(val) {//Monad.of is simpler than "new Monad(val)
        return new Monad(val);
    };
    map(f) {//Applies the function but returns another Monad!
        return Monad.of(f(this.__value));
    };
    join() { // used to get the value out of the Monad
        return this.__value;
    };
    chain(f) {//Helper func that maps and then gets the value out
        return this.map(f).join();
    };
     ap(someOtherMonad) {//Used to deal w/ multiple Monads
        return someOtherMonad.map(this.__value);
    }
}

Now, the generic Monads are not typically used but more specific and more useful Monads like “Maybe Monad” or “Either Monad” are often used in FP programming. So, let’s take a look at “Maybe Monad”.

“Maybe” Monad

A “Maybe” Monad is a class that implements Monad spec. But the special thing about Monad is that it takes care of “null” or “undefined” values.

Specifically, if the data stored is a null or undefined, then it’s “map” function doesn’t run the given function at all and there by avoiding any null or undefined issues. It is used in situations where we are dealing with Null values.

Below code shows ramda-fantasy’s implementation of Maybe Monad. It creates an instance of one of the two different sub-classes, Just or Nothing, depending on the value (i.e. useful value V/s null/undefined respectively).

While both Just and Nothing has similar methods (map, orElse etc), Just’s actually does something but Nothing’s doesn’t do anything.

Give special attention to “map” and “orElse” methods below

//Showing relevant parts from the Maybe implementation from ramda-fantasy lib
//See https://github.com/ramda/ramda-fantasy/blob/master/src/Maybe.js for full source

function Maybe(x) { //<-- main constructor that returns Maybe of Just or Nothing
  return x == null ? _nothing : Maybe.Just(x);
}

function Just(x) {
  this.value = x;
}
util.extend(Just, Maybe);

Just.prototype.isJust = true;
Just.prototype.isNothing = false;

function Nothing() {}
util.extend(Nothing, Maybe);

Nothing.prototype.isNothing = true;
Nothing.prototype.isJust = false;

var _nothing = new Nothing();

Maybe.Nothing = function() {
  return _nothing;
};

Maybe.Just = function(x) {
  return new Just(x);
};

Maybe.of = Maybe.Just;

Maybe.prototype.of = Maybe.Just;


// functor
Just.prototype.map = function(f) { //Doing "map" on Just runs the func and returns Just out of the result
  return this.of(f(this.value));
};

Nothing.prototype.map = util.returnThis; // <-- Doing "Map" on Nothing doesnt do anything

Just.prototype.getOrElse = function() {
  return this.value;
};

Nothing.prototype.getOrElse = function(a) {
  return a;
};

module.exports = Maybe;

Let’s see how to use Maybe monad to deal with “null” checks.

Follow these steps:

  1. If there any object that might be null or have null properties, create a Monad object out of it.
  2. Use some libraries like ramdajs, that are “Maybe-aware” to access value from w/in the Monad and work on it.
  3. Provide a default value if the actual value happens to be null (i.e handle Null errors upfront).
//Step 1. Instead of..
if (user == null) { //not logged in
    return indexURLs['en']; //return default page
  }

//Use:
 Maybe(user) //Returns Maybe({userObj}) or Maybe(null). i.e. data wrapped INSIDE "Maybe"



//Step 2. Instead of..
 if (user.prefs.languages.primary && user.prefs.languages.primary != 'undefined') {
    if (indexURLs[user.prefs.languages.primary]) {//if translation exists,
      return indexURLs[user.prefs.languages.primary];
      
//Use:
//a library that knows how to deal w/ data inside Maybe like Ramda's map.path:
 <userMaybe>.map(path(['prefs', 'languages', 'primary']))
      
   
      
//Step 3. Instead of..
 return indexURLs['en']; //hardcoded default values
  
//Use:
//all Maybe libs provide 'orElse' or 'getOrElse' method that'll return either actual data or return "default value"
<userMayBe>.getOrElse('http://site.com/en')      

Currying — (Helps Dealing With Global Data And Multi-Param Functions)

Topics covered: Pure functions and Composition

If we want to chain a series of functions together like: func1.func2.func3 or (func1(func2(func3())), all these functions can only receive just one input parameter each. For example, if func2 takes two parameters func2(param1, param2), then we can’t chain it!

But the practically speaking, many functions take multiple parameters. So how to use them in composition? Solution: “Currying”.

Currying converts a function that takes multiple parameter into a function that takes a single parameter at a time. It wont run the function until all parameters are passed.

In addition, Currying can also be used in situations when we are accessing global values. i.e. make it “pure”.

Let’s look at our solution again:

//The below gist shows how to take care of global variables and also make funcs chainable

//Global indexURLs map for different languages
let indexURLs = {
    'en': 'http://mysite.com/en',  //English
    'sp': 'http://mysite.com/sp', //Spanish
    'jp': 'http://mysite.com/jp'   //Japanese
}

//Imperative
const getUrl = (language) => allUrls[language]; //Simple, but error prone and impure (accesses global variable)


//Functional Programming

//Before currying:
const getUrl = (allUrls, language) => {
    return Maybe(allUrls[language]);
}

//After currying:
const getUrl = R.curry(function(allUrls, language) {//curry to convert this to a single arg func
    return Maybe(allUrls[language]);
});

const maybeGetUrl = getUrl(indexURLs) //Store global value in the 'curried' function.

//From this point, maybeGetUrl needs only one argument(language). So we can now chain this like:
maybe(user).chain(maybeGetUrl).bla.bla

Example 2— Handling Error Throwing Functions And Exiting Immediately After An Error

Topics Covered: “Either Monad”

Maybe Monad is great if we have “default” values to replace Null errors. But what about functions that actually need to throw errors? And how to know which function threw the error when we chain multiple error-throwing functions (i.e. we want fast-failure)?

For example: If we have func1.func2.func3… and if func2 threw an error, we should skip func3 and other future functions and properly show error from func2 so we can handle it.

Either Monad

Either Monads are great for dealing with multiple functions when they all can potentially throw error and want to quit immediately after an error so that we can pin-point where the error occurred.

Use case: For example in the below imperative snippet, we are calculating “tax” and “discount” for items and ultimately displaying showTotalPrice.

Note that the “tax” function will throw error if the price is non-numeric. Similarly, “discount” function will throw error if price is non-numeric and it will also throw error if the item’s price is less than 10.

So showTotalPrice has multiple error checks.

//imperative
//Returns error or price including tax 
const tax = (tax, price) => {
  if (!_.isNumber(price)) return new Error("Price must be numeric");

  return price + (tax * price);
};

//Returns error or price indluding discount
const discount = (dis, price) => {
  if (!_.isNumber(price)) return (new Error("Price must be numeric"));

  if (price < 10) return new Error("discount cant be applied for items priced below 10");

  return price - (price * dis);
};

const isError = (e) => e && e.name == 'Error';

const getItemPrice = (item) => item.price;

//shows total price after tax and discount. Need to handle multiple errors.
const showTotalPrice = (item, taxPerc, disount) => {
  let price = getItemPrice(item);
  let result = tax(taxPerc, price);
  if (isError(result)) {
    return console.log('Error: ' + result.message);
  }
  result = discount(discount, result);
  if (isError(result)) {
    return console.log('Error: ' + result.message);
  }
  //display result
  console.log('Total Price: ' + result);
}

let tShirt = { name: 't-shirt', price: 11 };
let pant = { name: 't-shirt', price: '10 dollars' };
let chips = { name: 't-shirt', price: 5 }; //less than 10 dollars error

showTotalPrice(tShirt) // Total Is: 9.075
showTotalPrice(pant)   // Error: Price must be numeric
showTotalPrice(chips)  //Error: discount cant be applied for items priced below 10

Let’s see how showTotalPrice can be improved by using Either Monad and rewrite everything in FP style.

Either Monad provides two constructors: “Either.Left” and “Either.Right”. Think of them as subclasses of Either. Both “Left” and “Right” are Monads! The idea is to store errors/exceptions in Left and useful values in Right.

i.e. create an instance of Either.Left or Either.Right depending on the value. Once we do that we can run map, chain and so on on those values to compose them.

While both Left and Right provide “map”, “chain” and so on, Left constructor doesn’t do anything as it stored Errors. Where as the Right constructor implements all the functions as it contains actual result.

OK, Let’s see how to change our imperative example to FP

Step 1: Wrap return values with Left and Right

Note: “Wrap” means create an instance of some Class. These functions internally call “new” so we don’t have to.

var Either = require('ramda-fantasy').Either;
var Left = Either.Left;
var Right = Either.Right;

const tax = R.curry((tax, price) => {
  if (!_.isNumber(price)) return Left(new Error("Price must be numeric")); //<--Wrap Error in Either.Left

  return  Right(price + (tax * price)); //<--Wrap result in Either.Right
});

const discount = R.curry((dis, price) => {
  if (!_.isNumber(price)) return Left(new Error("Price must be numeric")); //<--Wrap Error in Either.Left

  if (price < 10) return Left(new Error("discount cant be applied for items priced below 10")); //<--Wrap Error in Either.Left

  return Right(price - (price * dis)); //<--Wrap result in Either.Right
});

Step 2: Wrap the initial value in Right because it’s a valid value and so we can compose it.

const getItemPrice = (item) => Right(item.price);

Step 3: Create two functions, one to handle eventual error and another to handle result. And warp them in Either.either (this is from ramda-fantasy.js api).

Either.either takes 3 params. success handler, an error handler and an “Either” Monad. Either is curried. So we can just pass the handlers for now and pass the Either (3rd param) later.

Once Either.either receives all 3 params, it passes the 3rd param “Either” to the success handler or error handler depending of if the Either is “Right” or “Left” respectively.

const displayTotal = (total) => { console.log(‘Total Price: ‘ + total) };
const logError = (error) => { console.log(‘Error: ‘ + error.message); };
const eitherLogOrShow = Either.either(logError, displayTotal);

Step 4: Use “chain” method to compose multiple error throwing functions. Pass their result to Either.either (eitherLogOrShow) which will take care of passing the result to success handler or failure handler.

const showTotalPrice = (item) => eitherLogOrShow(getItemPrice(item).chain(apply25PercDisc).chain(addCaliTax));

Putting it all together:

const tax = R.curry((tax, price) => {
  if (!_.isNumber(price)) return Left(new Error("Price must be numeric"));

  return  Right(price + (tax * price));
});

const discount = R.curry((dis, price) => {
  if (!_.isNumber(price)) return Left(new Error("Price must be numeric"));

  if (price < 10) return Left(new Error("discount cant be applied for items priced below 10"));

  return Right(price - (price * dis));
});

const addCaliTax = (tax(0.1));//10%

const apply25PercDisc = (discount(0.25));// 25% discount

const getItemPrice = (item) => Right(item.price);


const displayTotal = (total) => { console.log('Total Price: ' + total) };

const logError = (error) => { console.log('Error: ' + error.message); };

const eitherLogOrShow = Either.either(logError, displayTotal);

//api
const showTotalPrice = (item) => eitherLogOrShow(getItemPrice(item).chain(apply25PercDisc).chain(addCaliTax));


let tShirt = { name: 't-shirt', price: 11 };
let pant = { name: 't-shirt', price: '10 dollars' }; //error
let chips = { name: 't-shirt', price: 5 }; //less than 10 dollars error


showTotalPrice(tShirt) // Total Is: 9.075
showTotalPrice(pant)   // Error: Price must be numeric
showTotalPrice(chips)  //Error: discount cant be applied for items priced below 10

**Recommended Courses: **

Advanced Javascript
https://goo.gl/ecMXz4

Quick JavaScript Core learning Course JavaScript Essentials
https://goo.gl/F77r2R

JavaScript 101 Gain insights how to code with JavaScript
https://goo.gl/buuydx

JavaScript JSON and AJAX Explained JavaScript Objects
https://goo.gl/DEtnwR

Learning Data Structures in JavaScript from scratch
https://goo.gl/vbke5U