Commencement

J'utiliserai la bibliothèque Ramda tout au long de ce livre, bien que la plupart des concepts exposés soient applicables à d'autres bibliothèques, comme Underscore et Lodash, ainsi qu'à d'autres langages de programmation.

Je m'en tiendrai aux sujets les plus légers, les moins académiques de la programmation fonctionnelle. Ceci principalement parce que je veux garder ce livre le plus accessible au plus grand nombre, mais aussi (en partie) parce que je ne suis pas encore très avancé sur la route de la programmation fonctionnelle.

Ramda

Randy Coulman avait déjà parlé du sujet sur son blog avant de commencer la série Thinking in Ramda :

Je trouve que Ramda est une bibliothèque joliment conçue qui fournit beaucoup d'outils pour faire de la programmation fonctionnelle d'une manière claire et élégante.

Si vous voulez expérimenter Ramda en lisant ce livre, il y a un moyen très pratique de le faire depuis le navigateur, en utilisant la page Try Ramda sur le site même de Ramda.

Fonctions

Comme son nom le suggère, la programmation fonctionnelle est une affaire de fonctions. Pour notre propos, nous définirons une fonction comme un morceau de programme appelée avec ou sans arguments et renvoyant un résultat.

Voici une fonction JavaScript simple:

function double(x) {
  return x * 2
}

Avec les fonctions fléchées (arrow functions en anglais), on peut écrire la même fonction avec beaucoup plus de concision. Je le mentionne dès à présent car nous allons beaucoup les utiliser.

const double = x => x * 2

La plupart des langages de programmations permettent l'utilisation de fonctions.

Certains langages vont plus loin, et considèrent les fonctions comme la brique de base (first-class constructs en anglais). Cela signifie qu'elles sont considérées comme d'autres valeurs. On peut:

  • les référencer comme des variables ou des constantes,
  • les passer en paramètres à d'autres fonctions,
  • les renvoyer comme résultats d'autres fonctions.

JavaScript est un de ces langages, et nous allons tirer parti de cet aspect.

Fonctions pures

En écrivant des programmes fonctionnels, il devient important de ne travailler qu'avec des fonctions soi-disant pures.

Les fonctions pures sont des fonctions qui n'ont pas d'effet de bord. Elle n'affectent aucune variable externe, elles ne lisent pas l'entrée standard, n'écrivent pas dans la sortie standard, elles ne lisent ni n'écrivent dans une base de données, elles ne modifient pas les paramètres qu'elles reçoivent, etc.

L'idée de base est que, si vous appelez une fonction avec les mêmes données encore et encore, vous obtiendrez toujours le même résultat.

Vous pouvez évidemment faire des choses avec des fonctions impures (et vous devez le faire, si votre programme doit faire quelque chose d'intéressant), mais pour l'essentiel, vous voudrez garder vos fonctions pures.

Immutabilité

Un autre concept important dans la programmation fonctionnelle est celui d'immutabilité. Qu'est-ce que ça signifie? Immutable veut dire inchangeable.

Quand je travaille de manière immutable, une fois que l'initialise une valeur ou un objet, je ne le/la modifie plus jamais. Cela signifie que je ne change pas les éléments d'un tableau ou les propriétés d'un objet.

Si j'ai besoin de changer quelque chose dans un tableau ou un objet, j'en renvoie une nouvelle copie avec la valeur modifiée. Nous en reparlerons en détails.

L'immutabilité travaille main dans la main avec les fonctions pures. Comme il est interdit aux fonctions pures d'entraîner des effets de bord, il leur est interdit de modifier des structures de données qui leur sont externes. Elles sont forcées de travailler les données de manière immutable.

Par où commencer ?

La façon la plus simple de commencer à penser fonctionnellement est de remplacer les boucles par des fonctions collection-iteration (NDT: itération-collection?)

Si vous venez d'un autre langage qui possède ces fonctions (Ruby et Smalltalk en sont quelques exemples), elles vous sont peut-être déjà familières.

Martin Fowler a une paire de très bons articles sur des pipelines de collections qui montrent comment utiliser ces fonctions, et comment refactorer du code existant en pipelines de collections.

Remarquez que ces fonctions sont toutes (à l'exception de reject) disponibles sur Array.prototype, donc vous n'avez pas strictement besoin de Ramda pour les utiliser. Néanmoins, j'utiliserai les versions Ramda pour une question de cohérence avec la suite du livre.

forEach

Essayons d'utiliser la fonction forEach plutôt que d'écrire une boucle explicite.

// Remplacer ceci:
for (const value of myArray) {
  console.log(value)
}

// par:
forEach(value => console.log(value), myArray)

forEach prend une fonction et un tableau, puis appelle la fonction sur chaque élément du tableau.

Bien que forEach soit la plus facile d'approche de ces fonctions, c'est la moins utilisée d'entre elles quand on fait de la programmation fonctionnelle. Elle ne renvoie aucune valeur, alors elle n'est vraiment utilisée que quand on appelle des fonctions ayant des effets de bord.

map

La fonction la plus importante à apprendre ensuite est map. CommeforEach, mapapplique une fonction à chaque élément d'un tableau. Toutefois, contrairement à forEach, map rassemble les résultats de l'application de la fonction dans un nouveau tableau et le renvoie.

Voici un exemple:

map(x => x * 2, [1, 2, 3])  // --> [2, 4, 6]

Nous avons utilisé une fonction anonyme, mais nous pourrions aussi bien utiliser une fonction nommée:

const double = x => x * 2

map(double, [1, 2, 3])

filter/reject

Ensuite, voyons filter et reject. Comme son nom l'indique, filter sélectionne les éléments d'un tableau sur la base d'une fonction. Par exemple:

const isEven = x => x % 2 === 0

filter(isEven, [1, 2, 3, 4])  // --> [2, 4]

filter applique sa fonction (isEven dans ce cas) à chaque élément du tableau. Dès que la fonction renvoie une valeur vrai (NDT: truthy), l'élément correspondant est inclus dans le résultat. Dès que la valeur renvoyée est fausse (NDT: falsy), l'élément correspondant est exclus du tableau.

reject fait exactement l'inverse. Elle garde les éléments pour lesquels la fonction renvoie une valeur fausse (NDT: falsy en anglais, qui signifie équivalent au booléen faux du point de vue JavaScript), et exclut les éléments pour lesquels elle retourne une valeur vraie (NDT: truthy en anglais, là encore, cela signfie vrai du point de vue JavaScript).

reject(isEven, [1, 2, 3, 4]) // --> [1, 3]

find

find applique une fonction à chaque élément d'un tableau, et renvoie le premier élément pour lequel la fonctions retourne une valeur vraie (NDT: truthy).

find(isEven, [1, 2, 3, 4]) // --> 2

reduce

reduce est un peu plus compliquée que les autres fonctions que nous avons vues jusqu'à présent. C'est la peine de la connaître, mais si vous avez du mal à comprendre au premier abord, ne vous laissez pas arrêter par cette impression. Vous pouvez aller loin sans la comprendre. reduce prend en paramètres une fonction à deux arguments, une valeur initiale, et un tableau à traiter.

Le premier argument que nous passons à la fonction est appelé l'accumulateur, et le second est la valeur venant du tableau. La fonction doit retourner la nouvelle valeur de l'accumulateur.

Regardons un exemple, et développons ce qui s'y passe.

const add = (accum, value) => accum + value

reduce(add, 5, [1, 2, 3, 4]) // --> 15
  1. reduce appelle d'abord la fonction (add) avec la valeur initiale (5) et le premier élément du tableau (1). add renvoie alors la nouvelle valeur de l'accumulateur (5 + 1 = 6),
  2. reduce appelle à nouveau add, cette fois avec la nouvelle valeur de l'accumulateur (6) et la valeur suivante du tableau (2). add renvoie 8,
  3. reduce rappelle add avec 8 et la valeur suivante (3), avec pour résultat 11,
  4. reduce appelle add une dernière fois, avec 11 et la dernière valeur du tableau (4), renvoyant 15,
  5. reduce renvoie la valeur accumulée comme son résultat (15).

Conclusion

En commençant par ces fonctions itération-collection, vous pouvez vous faire à l'idée de passer des fonctions à d'autres fonctions. Vous pouvez les avoir utilisées dans d'autres langages sans même vous rendre compte que vous étiez en train de faire de la programmation fonctionnelle.

Chapitre suivant

Le prochain chapitre, Combinaison de fonctions, montre comment nous pouvons aller un peu plus loin et commencer à combiner des fonctions d'une manière nouvelle et intéressante.

results matching ""

    No results matching ""