Dans cette vidéo nous allons découvrir le principe de la curryfication. Ce principe de programmation fonctionnelle permet de transformer une fonction qui attend plusieurs arguments en fonction à un argument qui retournera une fonction pour le reste des arguments.

Par exemple cette fonction :

function multiply (a, b, c) {
    return a * b * c
}

multiply(1, 2, 3)

Pourra être transformée de cette manière :

curry(multiply)(1)(2)(3)

Cela peut aussi être fait de manière inverse en faisant en sorte que le premier appel corresponde au dernier argument.

curryRight(multiply)(3)(2)(1)

Quelle utilisation ?

La curryfication n'est pas forcément un motif que l'on va retrouver systématiquement mais il permet de simplifier certaines opérations.

function multiply (a, b) {
    return a * b
}

const double = curry(multiply)(2)

[1, 2, 3].map(double) // Permet de doubler chaque valeur du tableau
['1', '23', '4'].map(curryRight(parseInt, 2)(10)) // Permet de s'assurer que le parseInt utilisera une base 10

On peut aussi simplifier une fonction qui attend de trop nombreux arguments.

function getPropertyOrDefault (obj, property, defaultValue) {
    return obj[property] || defaultValue
}

const getPropertyOrNull = curryRight(getPropertyOrDefault)(null)
const getTotal = curryRight(getPropertyOrDefault)(0)('total')

getTotal({a: '3'}) // 0
getTotal({total: 100}) // 100

Exemple d'implémentation

Maintenant que nous avons vu le principe on peut se demander comment va fonctionner cette transformation. En JavaScript il est assez simple d'implémenter une méthode de curryfication en se basant sur apply() et bind(). On permettra à nos fonctions d'accepter un second argument pour spécifier manuellement le nombre d'arguments attendus par la fonction (par exemple parseInt.length renverra 1 donc on ne peut pas se baser seulement sur cette propriété pour déterminer le nombre de paramètres).

function curry (func, arity = null) {
  return function curried (...args) {
    if (args.length >= (arity || func.length)) {
      return func.apply(this, args)
    }
    return curried.bind(this, ...args)
  }
}

function curryRight (func, arity = null) {
  return function curried (...args) {
    if (args.length >= (arity || func.length)) {
      return func.apply(this, args)
    }
    return function (...args2) {
      return curried.apply(this, [...args2, ...args])
    }
  }
}