Thursday 23 August 2018

Function Currying

The other day somehow I came across some discussion about Currying. Currying in computer programming is a confusing technique, first because quite a few times the term is wrongly used for referring to Partial Application, and second, because I've always found it pretty difficult to find it any use (contrary to Partial Application, that for sure can be pretty useful).

After some thinking I've managed to envision one use case for currying, and indeed it has led me to do a sort of variation over the normal currying. I'll show it here. If you want an explanation of Currying vs Partial Application, you can for example check it here.

Lodash provides a curry function, but anyway I've written my own basic implementation:

function curry(fn){
 let args = [];
 let originalFunc = fn;
 let argsNum = fn
 return function curriedFunc(arg){
  args.push(arg);
  if (args.length >= originalFunc.length){
   return originalFunc(...args);
  }
  else{
   return curriedFunc;
  }

 }
}

And now the difficult part, coming up with one case where currying could be useful!
Well, let's say that I have a function "retrieveAndProcess", that takes care of retrieving items one by one (through a "getNextItem" function), and then passes all those items to a second function "processItems". The thing is that our main function does not know how many items "processItems" needs. So our main function contains a loop that calls to getItem and processItems until this second one returns the processing result, rather than a function. This "curried" processItems is a closure that can keep the different items that it receives until that it gets all the ones that it needs and can process them all at once. Let's see an example:

function* userDataGenerator(){
 yield "Francois";
 yield "Toulouse";
 yield 42;
}


function format(name, city, age){
 return "My name is: " + name + ", I live in: " + city + " and I'm " + age;
}


function getUserDataAndFormat(formatFunc){
 let generatorObj = userDataGenerator();
 let formattedData = curriedFormatFunc(generatorObj.next().value);
 while(typeof formattedData == "function"){
  formattedData = formattedData(generatorObj.next().value);
 } 
 return formattedData; 
}

let curriedFormatFunc = curry(format);
console.log("Formatted user data: " + getUserDataAndFormat(curriedFormatFunc));

After writing that sample I realised of a variation of function currying that could be useful. Let's say that we have a function that can work with a variable number of parameters (imagine we had an Array.prototype.sum). It could be interesting to create from it a curried function that works for a specific number of parameters. For example I have a function that calculates the average of any number of items, and I want to create a curried version that will work for just 3 elements. Let's see what I mean:

//expects a function that can work with a variable number of parameters 
//and returns a curried version that will work for a concrete number of parameters (argsNum)
function curryWithMaxParameters(fn, argsNum){
 let args = [];
 let originalFunc = fn;
 if (argsNum === undefined){
  argsNum = fn.length;
 }
 return function curriedFunc(arg){
  args.push(arg);
  if (args.length >= argsNum){
   return originalFunc(...args);
  }
  else{
   return curriedFunc;
  }

 }
}


function* ageGenerator(){
 yield 10;
 yield 12;
 yield 14;
}

function average(...args){
 return args.reduce((it1, it2) => it1 + it2) / args.length;
}


function requestAndCalculateAverage(averageFunc){
 let generatorObj = ageGenerator();
 let averageAge = averageFunc(generatorObj.next().value);
 while(typeof averageAge == "function"){
  averageAge = averageAge(generatorObj.next().value);
 } 
 return averageAge; 
}

console.log("testing currying for a given number of parameters");

let curriedAverage3ItemsFunc = curryWithMaxParameters(average, 3);
console.log("average age: " + requestAndCalculateAverage(curriedAverage3ItemsFunc)); //12

No comments:

Post a Comment