Qu'est-ce qu'une fonction pure en JavaScript?

Les fonctions pures sont les blocs de construction atomiques de la programmation fonctionnelle. Ils sont adorés pour leur simplicité et leur testabilité.

Cet article couvre une liste de contrôle rapide pour savoir si une fonction est pure ou non.

La liste de contrôle

Une fonction doit passer deux tests pour être considérée comme «pure»:

  1. Les mêmes entrées renvoient toujours les mêmes sorties
  2. Pas d'effets secondaires

Zoomons sur chacun d'eux.

1. Même entrée => Même sortie

Comparez ceci:

const add = (x, y) => x + y; add(2, 4); // 6 

Pour ça:

let x = 2; const add = (y) => { x += y; }; add(4); // x === 6 (the first time) 

Fonctions pures = résultats cohérents

Le premier exemple renvoie une valeur basée sur les paramètres donnés, quel que soit l'endroit / le moment où vous l'appelez.

Si vous réussissez 2et 4, vous aurez toujours 6.

Rien d'autre n'affecte la sortie.

Fonctions impures = résultats incohérents

Le deuxième exemple ne renvoie rien. Il s'appuie sur l' état partagé pour faire son travail en incrémentant une variable en dehors de sa propre portée.

Ce modèle est le carburant du cauchemar d'un développeur.

L'état partagé introduit une dépendance temporelle. Vous obtenez des résultats différents selon le moment où vous avez appelé la fonction. La première fois se produit 6, la prochaine fois 10et ainsi de suite.

Quelle version est la plus facile à raisonner?

Lequel est le moins susceptible de produire des insectes qui ne se produisent que sous certaines conditions?

Lequel est le plus susceptible de réussir dans un environnement multi-thread où les dépendances temporelles peuvent briser le système?

Certainement le premier.

2. Aucun effet secondaire

Ce test lui-même est une liste de contrôle. Quelques exemples d'effets secondaires sont

  1. Mutation de votre entrée
  2. console.log
  3. Appels HTTP (AJAX / fetch)
  4. Changer le système de fichiers (fs)
  5. Interroger le DOM

Fondamentalement, tout travail effectué par une fonction qui n'est pas lié au calcul de la sortie finale.

Voici une fonction impure avec un effet secondaire.

Pas si mal

const impureDouble = (x) => { console.log('doubling', x); return x * 2; }; const result = impureDouble(4); console.log({ result }); 

console.logest l'effet secondaire ici mais, dans toute la pratique, cela ne nous fera pas de mal. Nous obtiendrons toujours les mêmes sorties, avec les mêmes entrées.

Cela peut cependant poser un problème.

Changement "impur" d'un objet

const impureAssoc = (key, value, object) => { object[key] = value; }; const person = { name: 'Bobo' }; const result = impureAssoc('shoeSize', 400, person); console.log({ person, result }); 

La variable,, persona été modifiée à jamais car notre fonction a introduit une instruction d'affectation.

L 'état partagé signifie que l impureAssoc' impact de ce n'est plus tout à fait évident. Comprendre son effet sur un système implique désormais de retrouver chaque variable touchée et de connaître leur historique.

État partagé = dépendances de synchronisation.

Nous pouvons purifier impureAssocen retournant simplement un nouvel objet avec les propriétés souhaitées.

Le purifier

const pureAssoc = (key, value, object) => ({ ...object, [key]: value }); const person = { name: 'Bobo' }; const result = pureAssoc('shoeSize', 400, person); console.log({ person, result }); 

pureAssocRetourne maintenant un résultat testable et nous ne nous inquiétons jamais s'il a muté discrètement quelque chose ailleurs.

Vous pouvez même faire ce qui suit et rester pur:

Une autre voie pure

const pureAssoc = (key, value, object) => { const newObject = { ...object }; newObject[key] = value; return newObject; }; const person = { name: 'Bobo' }; const result = pureAssoc('shoeSize', 400, person); console.log({ person, result }); 

La mutation de votre entrée peut être dangereuse, mais la mutation d'une copie ne pose aucun problème. Notre résultat final est toujours une fonction testable et prévisible qui fonctionne peu importe où / quand vous l'appelez.

La mutation est limitée à cette petite portée et vous renvoyez toujours une valeur.

Objets de clonage profond

La tête haute! L'utilisation de l'opérateur d'étalement ...crée une copie superficielle d'un objet. Les copies superficielles ne sont pas à l'abri des mutations imbriquées.

Merci Rodrigo Fernández Díaz d'avoir porté cela à mon attention!

Mutation imbriquée non sécurisée

const person = { name: 'Bobo', address: { street: 'Main Street', number: 123 } }; const shallowPersonClone = { ...person }; shallowPersonClone.address.number = 456; console.log({ person, shallowPersonClone }); 

Les deux personet shallowPersonCloneont été mutés parce que leurs enfants partagent la même référence!

Mutation imbriquée sécurisée

To safely mutate nested properties, we need a deep clone.

const person = { name: 'Bobo', address: { street: 'Main Street', number: 123 } }; const deepPersonClone = JSON.parse(JSON.stringify(person)); deepPersonClone.address.number = 456; console.log({ person, deepPersonClone }); 

Now you’re guaranteed safety because they’re truly two separate entities!

Summary

  • A function’s pure if it’s free from side-effects and returns the same output, given the same input.
  • Side-effects include: mutating input, HTTP calls, writing to disk, printing to the screen.
  • You can safely clone, thenmutate, your input. Just leave the original one untouched.
  • Spread syntax ( syntax) is the easiest way to shallowly clone objects.
  • JSON.parse(JSON.stringify(object)) is the easiest way to deeply clone objects. Thanks again Rodrigo Fernández Díaz!

My Free Course

This tutorial was from my completely free course on Educative.io, Functional Programming Patterns With RamdaJS!

Please consider taking/sharing it if you enjoyed this content.

It’s full of lessons, graphics, exercises, and runnable code samples to teach you a basic functional programming style using RamdaJS.

Thanks for reading! Until next time.