Functional Programming Techniques

October 25, 20179 min read

For an introduction to functional programming concepts, see my previous post functional programming 101

cover

Hi there, I hope you all had a nice week so far, it's Thursday, maybe you're already exhausted as I am ...

... Come on! let's have a small break, take fresh air for our brain and learn new things! 😉

Here is a retrospective on some useful functional programming techniques that I enjoy using these days when programming in JavaScript.

They make me even more productive as when it comes to developing apps.

I hope they will help you as well !

Functional programming techniques are techniques induced by the purity properties or/and the first-class properties of functions.

Functional Abstraction

A Functional Abstraction is replacing a specific part of the code of some function by a functional parameter.

function applyTwiceExp(n) {
  return exp(exp(n))
}
/* Abstraction */
function applyTwice(n, f) {
  return f(f(n))
}

Therefore, this abstraction makes it more generic. This technique is the most important and the most used FP technique. It is usually used with lambda expressions.

// ES5
applyTwice(3, function(x) {
  return x + 10
})
// -> 23

// ES6
applyTwice(3, () => x + 10)
// -> 23

Composition & Decomposition

Composition

According to Haskell Wiki :

Function composition is the act of pipelining the result of one function, to the input of another, creating an entirely new function.

Composition makes relationships explicit. We compose entities to make the relationships between them explicit.

let compose = (f, g) => (x) => f(g(x))
const square = (x) => x*x
const sqrt = (x) => Math.sqrt(x)

let identity = compose(lowerCase, sqrt)

identity(3) // --> 3
const lowerCase = (s) => s.toLowerCase()
const trim = (s) => s.trim()

let lowerCase_and_Trim = compose(lowerCase, trim)

lowerCase_and_Trim("HeLLO !  ") // --> "hello !"

Decomposition

Decomposition make responsibilities of each function explicit.

It helps us to isolate processes and make testing easier. This is why we need to name our functions :

fetchProfiles()
  .then((response) => {
    return response.json()
  })
  .then((collection) => {
    return collection.map((obj) => new Profile(obj) )
  })
  .then((profiles) => {
    profiles
      .map((p) => '<li>' + p.name + '</li>')
      .forEach((li) => $('.profiles-list').append(li))
  })
  .catch((err) => {
    $('.error-tooltip').text(err.message)
  })

This code is valid but the chunks of code in each .then(...) statement is responsible for one distinct operation on the data. These processes are not tightly coupled and can be extracted for a better readability :

let toJSON = (response) => response.json()

let transformData = (collection) => {
  return collection.map((obj) => new Profile(obj))
}

let renderToDOM = (profiles) => {
  profiles
    .map((p) => '<li>' + p.name + '</li>')
    .forEach((li) => $('.profiles-list').append(li))
}

let handleErrors = (err) => {
  $('.error-tooltip').text(err.message)
}

// This data flow is sooo easy to understand now
fetchProfiles()
  .then(toJSON)
  .then(transformData)
  .then(renderToDOM)
  .catch(handleErrors)

Here we have extracted and decomposed the previous code in functions. Each function have a single responsibility, are named and can be reused in another context.

I like to say that naming and extracting functions let us write literature instead of code.

Now writing the essence of the data flow in our program is like writing sentences. The words in these sentences are the name of our functions.

That's why naming is so hard and so important !

Naming has to be specific when the extracted function has tight coupling and generic when it is.

let toJSON = (response) => response.json()

let transformJSONToProfiles = (collection) => { // Specific naming
  return collection.map((obj) => new Profile(obj))
}

let renderProfilesToDOM =  (profiles) => { // Specific naming
  profiles
    .map((p) => '<li>' + p.name + '</li>')
    .forEach((li) => $('.profiles-list').append(li))
}

let handleErrors = (err) => { // Generic naming
  $('.error-tooltip').text(err.message)
}

// The sentence you've just written :
fetchProfiles()
  .then(toJSON)
  .then(transformJSONToProfiles)
  .then(renderProfilesToDOM)
  .catch(handleErrors)

// It fetches profiles data,
// and then transforms it to JSON,
// and then transforms JSON Data to Profiles objects,
// and then renders Profiles to the DOM.

Currying

The curried form of a function f accepting n arguments consists in representing f as an imbrication of n functions accepting one argument.

Thanks to the first-class citizenship of functions, we can write :

// in ES5
function f (x,y) {
  return x + y
}

/* Currying magic ! */
function f_curry (x) {
  return function(y) {
    return x + y
  }
}


// in ES6
const f = (x, y) => x + y

/* TADA ! */
const f_curry = (x) => (y) => x + y
f(3, 4)       // -> 7
f_curry(3)(4) // -> 7

We're now able to partially apply our functions. Let's go to the next section !

Partial Application

Partial application is delaying instantiation of some of the parameters of a function.

It may be useful when we need to parameter some process for a later use.

// ES5
function prepareDrink(verb) {
  // Outer function
  return function(input) {
    // Inner function
    return "I'm " + verb + " " + input + " !"
  }
}

// ES6
const prepareDrink = (verb) => (input) => `I'm ${verb} ${input} !`

// Let's partially apply 'prepareDrink'
var makeHotBeverage = prepareDrink('brewing')
var makeJuice = prepareDrink('pressing')

makeHotBeverage('coffee')
// -> "I'm brewing coffee !"
makeJuice('oranges')
// -> "I'm pressing oranges !"

Let's decompose this example :

  • We're partially applying prepareDrink function by setting a value to the first parameter which is the parameter of the outer function.

  • prepareDrink('brewing') returns the inner function and we're now able to store it inside a variable for a later use.

var makeHotBeverage = prepareDrink('brewing')
console.log(makeHotBeverage)
// --> function (input) { return "I'm " + verb + " " + input + " !" }
// This is the code of the inner function.
  • Because prepareDrink is a pure function, thanks to the principle of Referential transparency, we can now write :
makeHotBeverage('coffee') === prepareDrink('brewing')('coffee')

These two expressions are totally equivalent.

However, partial application of a function depends on the order of the parameters of f.

prepareDrink('coffee')('brewing') !== prepareDrink('brewing')('coffee')

Memoization

Memoization is an optimization technique used to speed up computer programs by caching the results of expensive function calls and returning the cached result when the function is called with the already known inputs.

function memoize(f) {
  f.cache = f.cache || {};
  return (...args) => {
    let key = 'key_' + args.join('')
    if(f.cache[key] !== undefined) {
      console.log('Cache hit ! -> ', f.cache[key]);
      return f.cache[key]
    } else {
      console.log('Not cached ...');
      f.cache[key] = f(...args);
      return f.cache[key]
    }
  }
}
function fibonacci(n) {
  if(n === 0) {
    return 0
  } else if (n === 1) {
    return 1
  } else {
    return fibonacci(n-1) + fibonacci(n-2)
  }
}

var memoized_fibo = memoize(fibonacci)

memoized_fibo(6) // 'Not cached ...'
memoized_fibo(5) // 'Not cached ...'
memoized_fibo(5) // 'Cache hit ! -> 5'
memoized_fibo(6) // 'Cache hit ! -> 8'

I don't claim to have the best memoization algorithm around ! If you strive for performance and reliability, better use more optimized and well-tested libraries like Lodash 😉

Functional Data-Driven Programming

Data-driven programming permits to change a program logic and flow, not by modifying the code, but the data it uses.

The organization of a data-driven program is based on two ingredients:

  1. Data structures containing the crucial data as e.g. tables, dictionaries.
  2. Kernel functions whose behavior depends on and is tuned by the data contained in these data structures.

Functional data-driven programming permits to change a program logic and flow, not by modifying the code, but the sets of functions it uses, exploiting their first-class citizenship properties.

Let's define some structure for our Photos API object :

const config = {
  methods : [
    {
      name: 'getPhotos',
      url: 'photos',
      type: 'GET',
      beforeRequest: (params) => params,
      afterRequest: (result) => {
        return result.map(json => new Photo(json))
      }
    },
    {
      name: 'getPhoto',
      url: 'photos/:id',
      type: 'GET',
      beforeRequest: (params) => params,
      afterRequest: (result) => {
        return new Photo(result)
      }
    }
  ]
}

This is our config file. It looks like a JSON object.

Our API will have two methods : getPhotos(params) and getPhoto(params)

Now, here comes the function createApi. It is responsible for creating a usable Api object based on the config.

It leverages the power of Array.reduce method to create each method defined in the config file on the PhotoApi object.

function createApi(config) {
  var PhotoApi = {};
  config.methods.reduce((Api, method) => {
    PhotoApi[method.name] = (params) => {
      return Promise
              .resolve(method.beforeRequest(params))
              .then((newParams) => {
                return fetch(url, newParams)
              }).then(method.afterRequest)
    }
    return Api
  }, PhotoApi)

  return PhotoApi
}

createApi is where lives the implementation details of the logic make the network call.

Let's describe what it does step by step :

  1. We create the exported Api namespace
var PhotoApi = {};
  1. We iterate on each object contained inside the config.method array and at each step we do :
  2. we create a new field on the namespace with the current method name
PhotoApi[method.name] = /*...*/
  • we assign a new function taking the queryParams as parameters
PhotoApi[method.name] = (params) => { /*...*/ }
  • We return a new Promise chain starting with the result of method.beforeRequest(params) then pipelining the new params to the actual network call using the fetch API then((newParams) => fetch(url, newParams)) and then conveying the request result through method.afterRequest(result) so the result can be transformed to instances of objects used in the app.

    Promise
      .resolve(method.beforeRequest(params))
      .then((newParams) => {
        return fetch(url, newParams)
      }).then(method.afterRequest)
    
  • Let's use it !

var PhotoApi = createApi(config.methods)
// creates a new namespace called 'PhotoApi'

PhotoApi.getPhoto({id: 3})
  .then(photo => console.log(photo))
// --> Photo { id: 3, url: '...' }

Now our PhotoApi namespace is completely parametrized by the config file.

For instance note that, here we are using Promises and the fetch API, but if we needed to use something else (Observables ? jQuery Ajax ? Restangular ?), it's still possible to create a new createApi function using another library instead of the duo Promises/fetch.

Closures as objects

Mixing closures with imperative features allows one to design lightweight objects.

A closure can then contains mutable states accessed through code within the closure (this code is run using arguments of the closure, which can then be considered as the classic “message semantics” of OOP).

It is possible to express some simple OO-like solutions without using the OOP mechanisms.

This technique works best in dynamically-typed languages (for example JavaScript, Python)

In JavaScript, we often use this technique to create modules and simulate data privacy :

function createCounter() {
   var count = 0;

   function updateCounter(value) {
     count += (value) ? value : 0
     console.log('count :', count)
   }

   return {
     updateCounter: updateCounter
   }
}

var counter = createCounter()
// We created a module ! 'count' variable is enclosed in 'counter'

console.log(counter.count) // --> undefined

counter.updateCounter(7)  // count : 7
counter.updateCounter(-3) // count : 4
counter.updateCounter(-2) // count : 2

The createCounter function is a factory function.

Conclusion

And that's it !

I hope these functional programming techniques will help you improve your coding skills and make you more efficient.

If you have any suggestions or you think I missed something in that post, feel free to say it the comments, it's much appreciated 😊



author undefined

By Yannick Spark : a Front-end engineer who works remotely at Teacup Analytics.
He likes Functional programming, Nutrition & Fasting, and Remote work.
You should definitely on Twitter 👋