avatarYannick S

Summary

The web content provides an overview of functional programming techniques in JavaScript, emphasizing their utility in improving code efficiency and readability.

Abstract

The article "Functional Programming Techniques in JavaScript" offers a retrospective on functional programming (FP) techniques that can enhance productivity in JavaScript application development. It covers concepts such as functional abstraction, composition and decomposition, currying, partial application, memoization, and functional data-driven programming. The author illustrates how these techniques can make code more generic, maintainable, and easier to test by using examples like functional abstraction to replace specific code parts, composition to pipeline function outputs, and decomposition to isolate function responsibilities. The article also discusses the benefits of currying and partial application for function reusability, memoization for optimizing expensive function calls, and using closures to create lightweight objects. The conclusion encourages the integration of these FP techniques to improve coding skills and efficiency, inviting feedback for further improvement.

Opinions

  • The author believes that naming functions properly is crucial for code readability and reusability, suggesting that names should be specific when functions have tight coupling and generic when they are more versatile.
  • There is an emphasis on the importance of referential transparency in pure functions, which allows for partial application and ensures that partially applied functions can be equivalently used in place of their more verbose counterparts.
  • The author promotes the use of functional data-driven programming to alter program logic and flow by modifying the sets of functions used, leveraging the first-class citizenship properties of functions.
  • The article suggests that using a functional approach with closures can simulate object-oriented programming features, such as data encapsulation, without relying on classical OOP mechanisms.
  • The author acknowledges the potential for performance enhancement through memoization but also recommends using well-tested libraries like Lodash for more reliable and optimized memoization algorithms.
  • The author values the flexibility of functional programming in JavaScript, highlighting that the techniques discussed can be applied with different libraries and APIs, demonstrating the adaptability of FP principles.

Functional Programming Techniques in JavaScript

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(square, 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 processes 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} !`
// 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 are 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 by modifying 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 :

  • We create the exported Api namespace :
var PhotoApi = {};
  • We iterate on each object contained inside the config.method array and at each step we do :
  • 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) => 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, it will be much appreciated 😊

Originally published on sparkyspace.com

JavaScript
Functional Programming
Programming
Recommended from ReadMedium