Tu maîtrises les fonctions, mais qu’en est-il des "functors" ?

La programmation fonctionnelle est à la mode en ce moment. Tout le monde en parle. Si vous aussi vous avez commencé à fouiner un peu vous êtes surement déjà tombé plusieurs fois sur ce mot : "functor".

On est bien d’accord que le titre de cet article n’existe que pour la ressemblance des mots fonction et functor. On ne va pas du tout parler des fonctions ici. Pas directement en tout cas.

Qu'est-ce qu'un functor

C’est si simple que vous allez être déçu. Un functor est :

  • un objet
  • qui implémente la méthode map

Voilà. Oui, c’est tout.

Les secrets de la méthode map

Bon ceci dit, vous vous doutez bien que la méthode map doit répondre à quelques règles précises et qu'il ne suffit pas d'appeler une méthode "map" pour que l'objet qui la possède devienne comme par magie un functor. Sinon quel intérêt ? D'ailleurs elle n'est pas obligée de s'appeler "map" cette méthode.

Map doit être telle que F(f) : F(a) => F(b)

La méthode map sert à faire un mapping, à transformer un functor. Elle transforme, dans l'exemple ci-dessus, a en b. La règle précise juste que si F est un type de functor, sa méthode map retournera peut-être des données modifiées, mais encapsulées dans un functor de même type F.

Map ne modifie pas l'instance principale

La méthode map d’une instance de F retournera toujours une instance de F. Sauf que si elle ne doit pas modifier l'instance principale tout en ayant une action sur les valeurs de l'instance retournée, on comprend vite que map retourne toujours une nouvelle instance.

Ce qui fait de map une méthode "pure". Pour les mêmes valeurs d'entrées j'obtiendrais toujours les mêmes valeurs de sortie. Elle ne cache pas de side effects. Elle ne modifie pas les valeurs d'entrées.

F(a) et F(b) possède le même nombre de connexions

L'instance retournée par map doit posséder le même nombre de connexions que l'instance principale. Les connexions sont les valeurs modifiées par map. C’est un peu flou dit comme ça mais ce sera sûrement plus simple avec un exemple.

Les functors en JavaScript

Un exemple de functor bien connu en JavaScript est Array. Cet objet possède une méthode map qui permet de modifier les valeurs contenues dans le tableau.

[1, 2].map(x => x * 2) // => [2, 4]
[1, 2].map(x => String(x)) // => ["1", "2"]

La méthode Array.map retourne toujours une instance de Array même si les valeurs contenues ne sont plus de même type.

let a = [1, 2]
let b = a.map(x => x * 2)

console.log(a) // [1, 2]
console.log(b) // [2, 4]

Les valeurs de a n’ont pas changées après l’utilisation de Array.map alors que l'on a bien les bonnes (nouvelles) valeurs dans b.

let a = [1, 2]
let b = a.map(x => x * 2)

a.length === b.length // true

Ce dernier exemple paraît bête, mais il illustre bien le propos. Array.map retournera toujours un Array avec le même nombre de connexions (ou d’éléments ici).

Bien d’autres objets sont des functors en JavaScript. Observez par exemple le fonctionnement de Promise et essayer de trouver comment il respecte les règles sur les functors.


Pour aller plus loin :