Coderstand

PF: L'application partielle

tags: programmation fonctionnelle

17/09/2017 (c'était un dimanche)

Rappel du sommaire

Quelle cuisine!

L’application partielle, est le fait d’appeler une fonction avec une partie de ses paramètres à un instant t, puis le reste des paramètres à un instant t+1. Cela va permettre une mutualisation de code encore plus poussée, et donc moins de répétition.

Un simple sucre syntaxique?

Pour illustrer, prenons tout de suite un exemple

// Sans application partielle
const ajoute3chiffres = (nb1, nb2, nb3) => {
    return nb1 + nb2 + nb3;
}

let a = 3;
let b = 5;
// A ce moment là, je ne peux rien faire,
// car j'ai besoin de passer les 3 chiffres en paramètres.

// Beaucoup plus loin dans le code
let c = 7;
let d = 9;

let result1 = ajoute3chiffres(a,b,c);
let result2 = ajoute3chiffres(a,b,d);

Dans cet exemple, on voit qu’il est impossible de faire notre calcul avant d’avoir nos 3 chiffres. De plus, on additionne 2 fois a et b, sans pouvoir réutiliser le résultat, ce qui est dommage.

On aurait pu faire

const ajoute2chiffres = (nb1, nb2) => {
    return nb1 + nb2;
}

let result = ajoute2chiffres(ajoute2chiffres(a,b),c);
// si on reprends l'exemple de c, d,
// et qu'on souhaite ne pas refaire a + b
let temp = ajoute2chiffres(a,b);
let result1 = ajoute2chiffres(temp,c);
let result2 = ajoute2chiffres(temp,d);

Mais cela rend la syntaxe lourde lorsqu’on veut additionner 3 ou 4 chiffres, ou cela nous oblige à déclarer une variable temporaire, ce qui n’est jamais bien.

Curry to the rescue

Faire une application partielle pour l’ajout de 3 chiffres revient à

  • quand on me passe le premier chiffre, je le garde en mémoire
  • quand on me passe le second chiffre, je l’ajoute au premier, et garde la somme en mémoire
  • quand on me passe le troisième, je l’additionne à la somme précédente, et je renvoie le tout.

En Javascript, cette fonctionnalité n’existe pas nativement. Il y a 2 solutions:

  • utiliser un helper un peu barbare pour transformer une fonction ‘classique’ en fonction déconstruite. On appelle cela le currying.

    const curry = (fx) => {
    var arity = fx.length;
    
    return () => {
    	let args = Array.prototype.slice.call(arguments, 0);
    	if (args.length >= arity) {
    		return fx.apply(null, args);
    	}
    	else {
    		return () => {
    			let args2 = Array.prototype.slice.call(arguments, 0);
    			return f1.apply(null, args.concat(args2));
    		}
    	}
    };
    }

    (Pris sur cet excellent article en anglais)

  • écrire nous-même une fonction déconstruite, pour bien comprendre le fonctionnement.

    // Sans application partielle
    const ajoute3chiffres = (nb1, nb2, nb3) => {
    return nb1 + nb2 + nb3;
    };
    // Avec application partielle
    const ajoute3chiffres = (nb1) => {
    return (nb2) => { // Fonction anonyme, on l'appellera fonction2
    	let temp = nb1 + nb2;
    	return (nb3) => { // Fonction anonyme, on l'appellera fonction3
    		return temp + nb3;
    	}
    }
    }
    const aPlusB = ajoute3chiffres(a)(b); // aPlusB est une fonction
    let result1 = aPlusB(c);
    let result2 = aPlusB(d);

OH MON DIEU, PLEIN DE FONCTIONS

En effet, ça va être l’exemple un peu plus compliqué de cet article.

  • ajoute3chiffres est maintenant une fonction qui ne prend qu’un seul paramètre (nb1), et qui renvoie une fonction.
  • cette fonction ne prend qu’un seul paramètre également (nb2), fait la somme de nb1 et nb2, et renvoie une nouvelle fonction.
  • cette dernière fonction prend encore un paramètre (nb3), et renvoie la somme finale.

Donc aPlusB est bien une fonction, qui attend encore un paramètre avant de finaliser le calcul. Mais elle a déjà calculée a + b, qui est stocké dans une variable. Cette variable temp

  • n’est pas manipulée par le code qui a appelé ajoute3chiffres donc cela est transparent dans notre code,
  • n’est pas modifiable par un autre bout de code, car elle n’existe que dans fonction2, et lorsqu’on reçoit fonction3, on ne peut plus modifier cette variable

Conclusion

L’application partielle consiste donc, en Javascript, à ne faire que des fonctions à un seul paramètre, et à renvoyer des fonctions en retour.

Les principaux gains sont:

  • plus besoin de créer de variables temporaires dans du code plus haut, ces variables sont encapsulées dans les fonctions renvoyées, et ne sont pas modifiables, donc il n’y a plus de risque de remplacer par erreur le résultat de a+b,
  • avec la syntaxe ES 2017 utilisée au maximum, on peut arriver à du code très compact. J’aurai pu écrire ajoute3chiffres sous la forme

    // Avec application partielle
    const ajoute3chiffres = nb1 => nb2 => {let temp = nb1 + nb2; return nb3 => temp + nb3;};
    // Sans l'optimisation de l'addition
    const ajoute3chiffres = nb1 => nb2 => nb3 => nb1 + nb2 + nb3;
  • dans le cas où certaines opérations nécessitent des requêtes réseaux, on obtient du code qui travaille aussi vite qu’il le peut, sans attendre d’avoir tous les paramètres.

Brice Coquereau

Bonjour! Je suis Brice Coquereau, parisien, développeur, flexitarien, et amateur de chats. Je vais à des meetups assez souvent. Venez me parler sur Mastodon ou Twitter (mais Mastodon c'est mieux)

TwitterFacebookLinkedin