Sans rentrer dans le détail de la programmation fonctionnelle, le pattern matching ou filtrage par motif, permet de créer des instructions switch avancées. Malheureusement, cette fonctionnalité n’est pas proposée en standard dans Javascript pour le moment. Voyons donc comment l’implémenter simplement en Javascript.

Il existe des bibliothèques implémentant le pattern matching en Javascript comme z. Mais pourquoi ajouter une dépendance pour une fonction si simple ?

Red metal pipe

Exemples

Une instruction switch traditionnelle ressemble à ça:

const person = { name: "Alice" };

switch (person.name) {
  case "Chuck":
    console.log("Chuck you are not welcome!");
    break;
  default:
    console.log(`Hey ${person.name}, you are welcome!`);
}

// output:  Hey Alice, you are welcome!

Avec du pattern matching, nous pourrions l’implémenter ainsi :

const person = { name: "Alice" };

match(person)
  .on(
    x => x.name === "Chuck",
    () => console.log("Chuck you are not welcome!")
  )
  .otherwise(x => console.log(`Hey ${x.name}, you are welcome!`));

// output: Hey Alice, you are welcome!

On peut voir ici un premier point intéressant. La condition permettant de déclencher un branchement dans l’arbre de décision se base sur l’objet transmis et permet d’écrire un test plus complexe sur plusieurs champs par exemple.

Ici la première condition est x => x.name === "John" mais nous pouvons imaginer un test plus complexe tel que x => x.state === BLOCKED || x.expiration < TODAY.

Le second point qui ne saute pas aux yeux immédiatement mais qui peut s’avérer très intéressant, est qu’il est possible de retourner le résultat de cette instruction switch. Voyons donc avec cet exemple :

const result = match(1)
  .on(
    x => x === 42,
    () => "number 42 is the best!!!"
  )
  .on(
    x => typeof x === "number",
    x => `number ${x} is not that good`
  )
  .on(
    x => x instanceof Date,
    () => "blaa.. dates are awful!"
  )
  .otherwise(() => "outch! dummy object");

console.log(result); // output: number 1 is not that good

Ainsi, plutôt que d’affecter le résultat à une variable d’un contexte supérieur dans chaque cas (et donc avoir une affectation dans chaque case du switch traditionnel), il est possible de retourner le résultat directement !

Je vous vois venir. Oui, il est bien sûr possible de créer une fonction que l’on pourra appliquer à un tableau par exemple. Comme ci-dessous :

const switchOnSteroids = (obj) =>
  match(obj)
    .on(
      x => x === 42,
      () => "number 42 is the best!!!"
    )
    .on(
      x => typeof x === "number",
      x => `number ${x} is not that good`
    )
    .on(
      x => x instanceof Date,
      () => "blaa.. dates are awful!"
    )
    .otherwise(() => "outch! dummy object");

console.log([1, 42, new Date(), {}].map(switchOnSteroids)); // output: [ 'number 1 is not that good', 'number 42 is the best!!!', 'blaa.. dates are awful!', 'outch! dummy object' ]

Implémentation

Je pense que je vous ai assez tenu en halène. Vous voulez savoir comment l’implémenter simplement, non ?

"use strict";

const matched = x => ({
  on: () => matched(x),
  otherwise: () => x,
});

const match = x => ({
  on: (pred, fn) => (pred(x) ? matched(fn(x)) : match(x)),
  otherwise: (fn) => fn(x),
});

module.exports = { match };

Et oui. Ce sont bien ces 8 lignes qui permettent d’ajouter le pattern matching. Je vous invite à lire l’article de l’auteur si vous souhaitez en savoir plus, ainsi qu’un article expliquant d’autres façons de revisiter les instructions switch.

Et vous ? Utilisez-vous le pattern matching ? Si oui, sous quelle forme ?