Développement Web

Programmation fonctionnelle en JavaScript

Romuald THION

Semestre pair 2022 UNC

Introduction

JavaScript is a multi-paradigm, dynamic language with types and operators, standard built-in objects, and methods. […] JavaScript also supports functional programming — because they are objects, functions may be stored in variables and passed around like any other object.

MDN

Attention concepts nouveaux

Le E de JavaScript

La programmation fonctionnelle aujourd’hui

  • @faheem_khan_dev
    • What much JavaScript should you learn before moving to React?
    • #5, #6 et #7 (sur 9) : programmation fonctionnelle

Les concepts de la programmation fonctionnelle

Glossary of Modern JavaScript Concepts (2017)

  • Purity: (Im)Pure Functions, Side Effects
  • State: Stateful and Stateless
  • Immutability and Mutability
  • Imperative and Declarative Programming
  • Higher-order Functions
  • Functional Programming

La programmation fonctionnelle (functional) c’est un peu tout ça

Les fonctions citoyennes de premier ordre

Qu’est ce qui fait (en partie) le caractère “fonctionnel” ?

  • affecter des fonctions à des variables
  • passer des fonctions en paramètre d’autres fonctions
  • renvoyer des fonctions comme résultat
    • et donc les créer dynamiquement
    • éventuellement avec des \(\lambda\)
Pass a function to a function

Slogan

En JavaScript (et en Python) les fonctions sont des objets comme les autres.

Les fonctions sont essentiellement des objets avec une méthode particulière call (__call__ en Python)

Créer des fonctions en JS

Déclaration

function funct(arg1, arg2, ..., argN) {
  // instructions
  return expression;
}
const res = funct(p1, p2, ..., pN);

Ici, on crée (déclare) une fonction avec une instruction (statement en anglais, qui n’a pas de valeur de retour). MDN

Expression fonctionnelle

const funct = function (arg1, arg2, ..., argN) {
  // instructions
  return expression;
};
const res = funct(p1, p2, ..., pN);

Ici, on crée une fonction comme une expression que l’on affecte à une variable, on appelle cette expression une Named Function Expression (NFE). MDN

Expression fonctionnelle fléchée

Dite aussi : arrow function expression, fat arrows, lambda ou \(\lambda\). MDN

const funct = (arg1, arg2, ..., argN)  => expression;
const res = funct(p1, p2, ..., pN);

Quasi-équivalente à la NFE suivante :

let funct = function(arg1, arg2, ..., argN) {
  return expression;
};

La notation fléchée, suite

let sum = (a, b) => {
  // accolade pour avoir plusieurs lignes
  const result = a + b;
  // avec les accolades, l'instruction return est OBLIGATOIRE
  return result;
};

console.log(sum(1, 2));

Variante multi-lignes, attention au return final.

La notation fléchée, danger

Attention : une arrow n’a pas de this propre, voir Arrow functions, the basics et Arrow functions revisited.

Vous aurez le problème avec les arrows si vous utilisez this dans un handler.

(il n’y a pas non plus de variable arguments et on ne peut pas instancier avec new, mais de toute façon, les deux sont à éviter.)

Exemple, créer des fonctions dynamiquement

On reprend l’exercice 2 affichage d’images du TP JS2 mais avec une variante :

  • Modifier la page pour qu’à chaque clic sur un lien on ajoute (et pas remplace) l’image correspondante dans l’élément <div id="image-container">.
  • Chaque image doit avoir en plus un bouton pour la supprimer.

HTML, CSS et JS

Les fermetures (closure)

Créer des fonctions avec d’autres fonctions, valable pour les trois types de créations (instruction, expression, arrow)

const creerFonction = function () {
  const nom = "Mozilla";
  return function () {
    console.log(nom);
  };
};

const maFonction = creerFonction();
console.log(maFonction());

Que fait le code précédent ?

  1. Une erreur de référence sur la variable nom
  2. Une autre erreur
  3. Il affiche "Mozilla"
  4. Autre chose

Éléments remarquables de creerFonction()

  • creerFonction() renvoie une fonction
  • la fonction renvoyée est anonyme
  • la fonction renvoyée lie la variable nom

On dit que maFonction() est une fermeture (closure)

Exemple de fermeture avec passage de paramètre

function sayHello(person) {
  return function (str) {
    console.log("Hi " + person + ", " + str);
  };
}
let helloBuddy = sayHello("Buddy");
let helloGuys = sayHello("Guys");
helloBuddy("c'mon");
helloGuys("how do you do?");

La variable person a une valeur dans helloBuddy et une autre dans helloGuys : l’environnement lexical a été capturé.

Exemple commun en DOM

function fabriqueRedimensionneur(taille) {
  return function () {
    document.body.style.fontSize = taille + "px";
  };
}
let taille12 = fabriqueRedimensionneur(12);
let taille14 = fabriqueRedimensionneur(14);
document.getElementById("t-12").onclick = taille12;
document.getElementById("t-14").onclick = taille14;

La portée lexicale des variables

La portée des variables en JS

Pour les déclarations let et const on une portée bloc délimitée par des accolades {...} :

{
  let message = "Hello";
  console.log(message);
}

{
  // pas d'erreur, chaque variable dans son bloc
  let message = "Goodbye";
  console.log(message);
}

Pour var c’est faux, mais de toute façon var c’est terminé.

La portée des variables en JS, suite

for (let i = 0; i < 3; i++) {
  console.log(i); // 0, then 1, then 2
}

console.log(i); // Error, no such variable

La portée de i est limitée à la boucle for car déclarée avec let (est faux avec var !)

L’environnement lexical

function makeCounter() {
  let count = 0;

  return function () {
    console.log(count);
    count += 1;
  };
}

let counter_1 = makeCounter();
let counter_2 = makeCounter();

Question qu’est ce qui s’affiche ?

counter_1;
counter_1();
counter_1();
counter_2;
counter_2();
counter_2();

A retenir

A closure is a function that remembers its outer variables and can access them javascript.info.

  • un exemple anondin, car vous avez pu l’écrire vous-mêmes sans vous en rendre compte,
  • un exemple profond, car il illustre le mécanisme de fermeture :
    • ici, count est dans l’environnement extérieur de la fonction counter_1
    • chaque fonction créée counter_1 et counter_2 a son propre environnement

Accès à la fermeture en Python

Un exemple similaire en Python, en utilisant le module d’introspection inspect

from inspect import getclosurevars
def makeCounter():
    count = 0
    def out():
        nonlocal count # spécifique à Python
        print(count)
        count +=1
    return out
counter = makeCounter()
getclosurevars(makeCounter)
getclosurevars(counter)
counter()
getclosurevars(counter)

Cas d’usage, les IIFE

An IIFE (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it is defined MDN.

(function () {
  /* … */
})();
(() => {
  /* … */
})();

Pourquoi faire ceci ?

Exercice

Ensuite, faire les 6 premiers exercices (jusqu’à sum with closure) de javascript.info.

Exercice, suite

  • écrire une fonction counter qui renvoie une fonction, disons f, qui renvoie son paramètre n élevé au carré mais en comptant et affichant le nombre d’appels fait à f à chaque utilisation.
  • écrire ensuite une variante où le compteur est commun à toutes les fonctions générées par counter.

Remarque les globaux sont interdits (dans cet exercice, et en général).

Remarque faites avec/sans fermeture