Logo INGIN - société de développement informatique spécialisé dans la refonte de site internet
Bouton de scroll vers le haut
Partager cet article

Paradigme de programmation fonctionnelle : exemples en JavaScript

La programmation fonctionnelle en JavaScript

Programmation fonctionnelle : définition et explications

Le paradigme de programmation fonctionnelle est applicable au langage JavaScript. Cela permet d’écrire des programmes plus fiables et surtout plus faciles à maintenir.

Pour vous le prouver, voyons un peu de théorie pour entrevoir le gain que l’on peut obtenir en appliquant le concept de programmation fonctionnelle à des langages impératifs tel que le JavaScript. Puis nous terminerons par un peu de pratique en appliquant ce concept sur les manipulations de tableaux car la programmation fonctionnelle excelle dans ce domaine.

JavaScript : Les fonctions

Vous le savez tous, les fonctions sont la base de la programmation, elles permettent de regrouper les portions de code qui se répètent. D'autre part, avec un bon nommage, elles permettent une meilleure lisibilité/compréhension du code.

En d’autres termes, elles permettent de découper un besoin fonctionnel complexe en un ensemble de petites fonctions moins complexe, et donc, plus facile à implémenter.

Définition d'une fonction en programmation fonctionnelle

Pour faire simple, c'est un ensemble de ligne de code mis bouts à bouts dans le but unique de faire ce pour quoi on l'a créée ET RIEN D'AUTRE !

    
function add(a, b) {
    return a + b;
}

// Ou, l'équivalent avec la syntaxe ES6

const add = (a, b) => a + b;

// add(3, 2) => 5
    

Cette fonction a été crée pour additionner 2 nombres. Celle-ci retourne donc l’addition des deux paramètres “a” et “b” passés en arguments et rien d'autre.

On peut dire que si on appelle cette fonction plusieurs fois de suite avec les mêmes paramètres en entrée, alors le résultat en sortie sera toujours identique ! Et en affirmant cela, on se retrouve au cœur du paradigme de programmation fonctionnelle.

On appelle ce genre de fonction, une fonction pure car rien ne viendra altérer son comportement. Cette fonction n'utilise que des variables passés en paramètres et surtout pas de variable globale.

Maintenant, jetons un coup d’œil à la fonction suivante :

var a = 3; // ne pas utiliser “var” ! uniquement à des fins de démo

function add(b) {
    return a + b;
}

add(2); // renverra 5.
// Si pour une raison ou une autre, la valeur de "a" était amenée à 
// changer, notre fonction renverrait une valeur différente !

var a = 2;
// alors, cette fois
add(2); // renverra 4
    

Dans ce cas, vous voyez bien qu’il y a un problème car on ne peut en aucun cas garantir le résultat de la fonction “add” puisqu'un programme/fonction externe peut altérer/modifier la variable globale et ainsi modifier le comportement de cette fonction.

Autrement dit, une autre fonction à été en mesure d’accéder au même espace mémoire où est stockée la variable globale.

Cette fonction peut donc avoir un comportement inattendu, c’est ce que l’on appelle en programmation fonctionnelle, les effets de bord.

En conclusion, une fonction avec des effets de bord (utilisant des variables en dehors de son champ d'action) est une fonction qui n’est pas pure. Cela nous causera des soucis inévitables dans la vie et l’évolution du programme à venir.

Les notions importantes de la programmation fonctionnelle

L’immutabilité

L’immutabilité est la notion qui consiste à dire qu’une variable ne peut être, en aucun cas modifiée par qui que ce soit.

L’utilisation de var et let est donc interdite en programmation fonctionnelle. De même que la mutation d’objet. En effet, même en utilisant “const” pour déclarer un objet, rien ne vous empêchera de modifier une propriété de cet objet.

Il est crucial de comprendre ce qu’est l’immutabilité mais grâce au paradigme de programmation fonctionnelle, cette notion deviendra peu à peu transparente et vous n’y ferez même plus attention.

Inutile de vous munir de librairie telle que “Immutable” ou d’utiliser des “Object.freeze” à tout va puisque la programmation fonctionnelle vous évitera de faire des bêtises. Ou plutôt, la programmation fonctionnelle changera votre manière de penser et donc de concevoir votre programme qui fait que vous n'aurez plus à vous souciez de l'immutabilité d'une variable.

La transparence référentielle

En programmation fonctionnelle, vous entendrez certainement parler de “transparence référentielle”. Cette notion un peu barbare et pas du tout évidente à comprendre peut être expliquée de la manière suivante :

Lorsqu'une fonction est pure, les variables utilisées à l'intérieure restent dans le champ d'action (ou scope) de cette fonction. Cela signifie qu’une autre fonction n’aura pas accès à ces variables puisqu'elles sont privées.

Autrement dit, l’accès en mémoire est donc réservé à cette fonction et aucune autre.

Dans un programme développé avec des fonctions pures, il est possible de remplacer l’appel de la fonction directement par sa valeur retournée sans risque de modifier le résultat final.

// reprenons notre fonction pure du début
function add(a, b) {
    return a + b;
}

console.log( add(2, 3) ); // retourne 5

// aura le même comportement que

console.log( add( add(1, 1) , 3 ) ); // retourne 5 également
    
console.log( add( add(1, 1) , add(2, 1) ) ); // retourne encore 5
    
console.log( 5 ); // encore et toujours 5 !

Remarquez-vous la manière dont il est possible de remplacer une fonction par une valeur et inversement ? Et bien dites-vous bien que cela est possible uniquement si vous respectez le paradigme de programmation fonctionnelle.

Le même exercice avec des fonctions non pures est impossible puisque le comportement, et donc, le résultat final peut changer en fonction de la valeur de la variable globale utilisée dans cette même fonction - et que cette variable globale peut être modifiée à tout moment par un autre programme.

C’est grâce à ce concept de transparence référentielle qu’il est pour nous, développeurs, plus facile de factoriser un programme sans risquer de tout casser. Encore une fois, ce sont, en majeur partie, les mauvaises habitudes de travailler sur des variables globales qui font que votre programme devient très difficile à maintenir/corriger voir impossible. Surtout si celui-ci a déjà fait l'objet de divers correctifs par le passé et que plusieurs développeurs sont passés par là... 

Les fonctions d’ordre supérieur (ou High Order Function => HOF)

Pour faire simple, une fonction d’ordre supérieur est une fonction qui prend au moins une fonction en paramètre et/ou qui retourne une fonction plutôt qu’une valeur de type "string", "object" ou "number".

// Mon objet "car" initial
const myCar = {
    brand: "Audi",
    model: "A7 sportback",
};

// Fonction d'ordre supérieur qui retourne une fonction
function enrichCarWithProp (obj1) {
    console.log("obj1", obj1); // { brand: "Audi", model: "A7 sportback" }
    return function(prop, value) {
        return {
            ...obj1,
           [prop]: value, // ici => color: "Bleu lunaire"
        };
    };
}

const enrichCar = enrichCarWithProp(myCar);
console.log( enrichCar("color", "Bleu lunaire") )
/* 
{ 
    brand: "Audi", 
    model: "A7 sportback", 
    color: "Bleu lunaire" 
}
*/

// En ES6, on peut simplifier de la sorte
// Mon object "car" initial
const myCar = {
    brand: "Audi",
    model: "A7 sportback",
};

// Fonction d'ordre supérieur qui retourne une fonction
const enrichCarWithProp = obj => (prop, value) => ({
    ...obj,
    [prop]: value,
});

const enrichCar = enrichCarWithProp(myCar);
console.log( enrichCar("color", "Bleu lunaire") )
/* 
{ 
    brand: "Audi", 
    model: "A7 sportback", 
    color: "Bleu lunaire" 
}
*/
    

Grâce aux fonctions d’ordre supérieur, il est possible de créer des fonctions génériques et surtout réutilisables. C’est, en partie, grâce à ce concept que l’on peut séparer notre programme en plusieurs petites fonctions ayant chacune leur responsabilité. Cela nous permet également de créer des modules réutilisable et qui pourraient être utiles pour d'autres programmes.

Essayons d’appliquer ce concept sur un exemple plus concret grâce à la manipulation de tableaux.

Les tableaux

Un tableau représente une liste ordonnée de valeurs. Peu importe le type de ces valeurs. En revanche, je vous conseille vivement de ne pas mélanger les types au sein de votre tableau car il devient très difficile, voire impossible d’industrialiser un traitement sur chacune des occurrences de votre tableau si leur type change. En effet, vous seriez obligé de tester le type de votre variable (typeof <myVar> ou Array.isArray(myVar) ) afin d'appliquer le traitement ou la transformation adéquate.

Array.prototype.forEach

var globalCars = [
    { brand: "Honda", model: "civic", power: "140cv" },
    { brand: "Audi", model: "A7 sportback", power: "252cv" },
];

/*
 * Extract number from a given string
 * @param {string}
 * @return {number}
 */
const getNumericValue = txt => txt.match(/\d/g).join("");

function formatCars(globalCars) {
    globalCars.forEach((car, idx) => {
        globalCars[idx].power = Number(getNumericValue(car.power));
    });
}

formatCars(globalCars);

console.log( "globalCars", globalCars );
    

Le code ci-dessus fonctionne très bien, alors pourquoi aller chercher plus loin ? 

Et bien tout simplement parce que la fonction “formatCars” (ainsi que celle à l’intérieur du “forEach”) n’est pas pure et que vous vous exposez à des bugs certains ! Il est donc impossible de connaître avec certitude le contenu de la variable “globalCars”.

En effet, la fonction utilise la variable “globalCars” qui se situe en dehors de son scope. Il y a donc de forte chance que d’autres fonctions fassent de même puisque rien n’interdit l’accès à l’espace mémoire où est stockée la variable “globalCars”.

D’autre part, il est important pour la compréhension du code, de bien séparer la logique métier de celle de votre programme (regex, boucle, etc). Cela vous permettra de tester très facilement vos fonctions de manière unitaire en vous concentrant sur le résultat final et non la manière dont vous avez implémenté la fonctionnalité.

Souvenez-vous, une fonction ne doit faire que ce pour quoi elle a été créée. Pour simplifier et améliorer la qualité de votre programme, séparez votre code en plein de petites fonctions simple ayant chacune UNE seule responsabilité.

L’itérateur “forEach” est à bannir de vos prochaines lignes de code !

Ne connaissez-vous une fonction qui permettrait d’itérer sur chacun des éléments du tableau et qui, en même temps, retourne un tableau ? La fonction “map”, et oui !

Une meilleure solution consisterait donc à utiliser l’itérateur “map” qui nous permettra de créer un nouveau tableau sans changer le tableau d’origine.

Array.prototype.map

Voyons comment faire :

const cars = [
    { brand: "Honda", model: "civic", power: "140cv" },
    { brand: "Audi", model: "A7 sportback", power: "252cv" },
];

/*
 * Extract number from a given string
 * @param {string}
 * @return {number}
 */
const getNumericValue = txt => txt.match(/\d/g).join("");

/*
 * @param {array} cars - liste des voitures
 * @return {array}   
 */
function formatCars(cars) {
    // déclaration d'un nouveau tableau pour respecter l'immutabilité.
    // c'est inutile de créer une variable pour la retourner juste après, retournez directement comme ceci => return cars.map(...)
    const formattedCars = cars.map(car => {
        // Remarque : pour un code à destination d'un environnement de production, il faudrait tester l'objet "car" pour s'assurer que la propriété "power" existe !
        const { power, ...rest } = car;
        const powerAsNumber = getNumericValue(power);
     
        return {
            ...rest,
            power: Number(powerAsNumber)
        }
    });

    return formattedCars;
}

const formattedCars = formatCars(cars);

console.log( "formattedCars", formattedCars )
/*
[
    { brand: "Honda", model: "civic", power: 140 },
    { brand: "Audi", model: "A7 sportback", power: 252 },
]
*/

Pour faciliter la lecture, on peut sortir la fonction qui se trouve à l'intérieur de l’itérateur “map” et simplifier en utilisant la syntaxe es6 :

const cars = [
    { brand: "Honda", model: "civic", power: "140cv" },
    { brand: "Audi", model: "A7 sportback", power: "252cv" },
];

/*
 * Extract number from a given string
 * @param {string}
 * @return {number}
 */
const getNumericValue = txt => txt.match(/\d/g).join("");

const formatCar = car => {
    // Objet "car" à valider avant d'extraire la propriété "power"
    const { power, ...rest } = car;
    const powerAsNumber = getNumericValue(power);
    return {
        ...rest,
        power: Number(powerAsNumber)
    }
};

const formatCars = cars => cars.map(formatCar);

/* l'équivalent es5 pour ceux qui ne seraient pas encore familier avec  la syntaxe es6
function formatCars(cars) {
    // déclaration d'un nouveau tableau pour respecter l'immutabilité.
    return cars.map(formatCar);
}
*/

const formattedCars = formatCars(cars);

console.log( "formattedCars", formattedCars )
/*
[
    { brand: "Honda", model: "civic", power: 140 },
    { brand: "Audi", model: "A7 sportback", power: 252 },
]
*/

Array.prototype.filter

Comme son nom l’indique, cet itérateur permet de filtrer et donc de réduire le tableau initial.

Imaginons le besoin métier suivant : 

Créer une fonction permettant de filtrer les voitures afin que seules celles qui se situent entre 130 et 200 chevaux soient sélectionnées pour participer à la course et ainsi garantir un équilibrage acceptable entre les voitures qui vont s’affronter.

// Liste des voitures candidates pour la course
const cars = [
    { brand: "Honda", model: "civic", power: "140cv" },
    { brand: "BMW", model: "320 i worldline", power: "163cv" },
    { brand: "Audi", model: "A7 sportback", power: "252cv" },
    { brand: "Audi", model: "A5 TFSI", power: "cv" }, // bad parameter type
    { brand: "Renault", model: "twingo" }, // missing power property !
];

// Regex permettant d'extraire un nombre à partir d'une string
const getNumericValue = txt => txt.match(/\d/g).join("");

// Fonction de formattage d'un objet "car"
// Destructuration immédiate de l'objet "car"
const formatCar = ({ power, ...rest }) => {
    try {
        const powerAsNumber = getNumericValue(power);

        return {
            ...rest,
            power: Number(powerAsNumber)
        }
    } catch(e) {
        console.log("ERROR", e.message);

        return {
            ...rest,
            power: 0,
            error: "Bad or missing property 'power'",
        }
    }
};

// Itération sur chaque voiture pour appliquer le formattage à chacune
// d'entres elles
const formatCars = cars => cars.map(formatCar);

// Fonction de comparaison pour la sélection des voitures
// Notez que cette fonction sert à implémenter une règle métier. Il serait
// pertinent de la sortir dans un module à part (ex: racingCarbusinessRules.js)
const filterCarLowerThan200CV = car => car.power > 130 && car.power < 200;

// Itération sur chaque voiture pour appliquer la fonction de filtre 
// sur chacune d'entres elles
const getElligibleCars = cars => cars.filter(filterCarLowerThan200CV);

// Appel de la fonction de formattage des voitures afin de pouvoir
// exploiter/comparer les puissances
const formattedCars = formatCars(cars);
console.log( "formattedCars", formattedCars )
/*
formattedCars [
    {
        "brand": "Honda",
        "model": "civic",
        "power": 140
    },
    {
        "brand": "BMW",
        "model": "320 i worldline",
        "power": 163
    },
    {
        "brand": "Audi",
        "model": "A7 sportback",
        "power": 252
    },
    {
        "brand": "Audi",
        "model": "A5 TFSI",
        "power": 0,
        "error": "Bad or missing property 'power'"
    },
    {
        "brand": "Renault",
        "model": "twingo",
        "power": 0,
        "error": "Bad or missing property 'power'"
    }
]
*/
    
// Récupération des voitures elligibles à la course
const elligibleCars = getElligibleCars(formattedCars)

console.log( "elligibleCars", elligibleCars );
/*
elligibleCars [
    {
        "brand": "Honda",
        "model": "civic",
        "power": 140
    },
    {
        "brand": "BMW",
        "model": "320 i worldline",
        "power": 163
    }
]
*/

Array.prototype.reduce

Pour comprendre la fonction “reduce”, il faut d’abord comprendre la notion d’accumulateur.

En quelques mots, un accumulateur est une variable (de type string, object ou array) qui va nous permettre de passer le résultat de la 1ère itération en paramètre de la seconde itération et ainsi de suite.

// Liste des personnages
const brawlers = [
    { pseudo: "Nita", nbTrophy: 148 },
    { pseudo: "Bartaba", nbTrophy: 140 },
    { pseudo: "Colt", nbTrophy: 103 },
    { pseudo: "Penny", nbTrophy: 56 },
    { pseudo: "El Costo", nbTrophy: 324 },
    { pseudo: "Jessie", nbTrophy: 215 },
    { pseudo: "Shelly", nbTrophy: 183 },
    { pseudo: "Bull", nbTrophy: 43 },
    { pseudo: "Rosa", nbTrophy: 0 },
];

// Fonction de calcul du nombre total de trophés
const getTotalTrophy = brawlers => 
    brawlers.reduce(
        (accumulator, brawler) => accumulator + brawler.nbTrophy, 
        0
    );

// Appel de la fonction pour récupérer la somme des trophés
console.log("sumTrophy", getTotalTrophy(brawlers)); // => sumTrophy 1212

Vous voyez qu’on utilise un accumulateur afin de récupérer le résultat de l’itération précédente.

je vous entends déjà me dire “et oui, mais la 1 ère itération alors ?”

Pas d’inquiétude, le "reduce" est prévu pour cela, c’est d’ailleurs la raison qui fait qu’il y a un second paramètre passé en argument, ici la valeur “0”.

<monArray>.reduce(myFunction, initialValue)

La composition de fonction

Cette expression est relativement barbare… Je vous propose donc un exemple simple plutôt qu’un long discours.

const add = (a, b) => a + b; 
const mult = (a, b) => a * b;
const sous = (a, b) => a - b;

// Exemple n°1
const addResult = add(2, 3); // 2 + 3 => 5
const multResult = mult(addResult, 2); // 5 * 2 => 10
const sousResult = sous(multResult, 2); // 10 - 2 => 8

console.log("sousResult", sousResult);
// => 8

// Exemple n°2
const result = sous(mult(add(2, 3), 2), 2);

console.log("result", result);
// => 8

Vous noterez au passage que toutes mes fonctions sont pures. Il n’y a pas de variable globale et les scopes de chaque fonction sont bien respectés. Il n’y a pas de fonction accédant au même espace mémoire qu’une autre fonction. Autrement dit, deux fonctions différentes n’accèdent jamais à une même variable.

Dans l’exemple 1, j’utilise le résultat de la 1ère fonction (add) pour le passer en paramètre de la seconde fonction (mult) et ainsi de suite.

Dans l’exemple 2, plutôt que de déclarer des variables pour les utiliser juste après et nulle part ailleurs, je passe directement la 1ère fonction en paramètre de la seconde fonction. 

En effet, puisque “addResult” est équivalent à “add(2, 3)

const addResult = add(2, 3);

Alors, grâce à la transparence référentielle, je peux remplacer le résultat de la 1ère fonction directement par la fonction elle-même.

Et dans ce cas, l’appel de fonction “mult” ci-dessous

const multResult = mult(addResult, 2);

Devient alors

const multResult = mult(add(2, 3), 2);

On économise alors la déclaration d’une constante qui a une durée de vie très courte et n’a donc aucune utilité en dehors du scope de la fonction.

La curryfication (ou le Currying)

Le principe de currying est relativement simple mais il est plus ardu de comprendre vraiment à quoi cette notion peut bien servir.

Définition du currying

Le currying consiste à transformer une fonction acceptant "n" paramètres en une série de fonction ne traitant qu'un seul paramètre à la fois.

Mais comme d'habitude, un exemple vaut mieux qu'un long discours. Le currying transforme la fonction suivante :

const add = (a, b, c) => a + b + c;

En ceci :

curry(add)(a)(b)(c);

Pour arriver à ce résultat, il faut que la fonction "curry(add)" renvoie une fonction de manière à pouvoir l’exécuter en lui passant le 1er paramètre "a" qui à sont tour, devra retourner une fonction de manière à pouvoir l’exécuter avec le 2nd paramètre et ainsi de suite.

Une version simplifiée de la fonction curry pourrait ressembler à ceci :

// Fonction "add" d'origine
const add = (a, b, c) => a + b + c;

// Fonction de curryfication simplifiée
// Cette version "simplifiée" ne fonctionne qu'avec 3 paramètres
const currySimple = fn => {
    return function(a) {
        return function(b) {
            return function(c) {
                // Une fois les 3 paramètres enregistrés dans les différents scopes,
                // on retourne la fonction d'origine avec ses 3 paramètres en arguments.
                return fn(a, b, c);
            }
        }
    }
};
/*
Et avec la syntaxe es6, cela donne :
const currySimple = fn => a => b => c => fn(a, b, c);
*/

// Fonction "add" curryfiée
const curriedAddVersion = currySimple(add);

// Execution de la fonction "add" curryfiée
console.log( "curriedAddVersion", curriedAddVersion(1)(2)(3) );
/*
curriedAddVersion 6
*/

Est-ce que ce pattern ne vous dit rien ? Aller... Une fonction qui accepte au moins une fonction en paramètre et/ou retourne une fonction... Et oui, il s'agit d'une HOF !

Exemple à tester dans votre console Chrome:

const curry = (fn, arity = fn.length) =>
    (function recursiveFunc(prevArgs) {
        return function(nextArgs) {
            const args = [
                ...prevArgs,
                nextArgs,
            ];

            if (args.length >= arity) {
                return fn( ...args );
            }
    
            return recursiveFunc(args);
        };
    })([]);

const add = (a, b, c) => a + b + c;

console.log( "call add function", add(1, 2, 3) )
// call add function 6
console.log( "call curried version", curry(add)(1)(2)(3) );
// call curried version 6

A quoi sert le currying en JavaScript - Exemple d'application

Si cette notion se révèle très intéressante, il est certain que les cas d'applications sont limités. Là où il est possible d'exploiter toute la puissance du currying se trouve être dans les traitements appliqués en boucle et plus précisément sur les occurrences d'un tableau.

Imaginons un tableau pour lequel nous souhaiterions appliquer une opération, par exemple, multiplier par 2, sur tous les éléments du tableau tout en s'assurant que la variable sur laquelle nous appliquons l'opération est bien un nombre (de type Number).

// Fonction curry qu'on peut trouver un peu partout sur le net.
// Notez qu'il existe également la fonction curryRight pour inverser l'ordre
// de prise en compte des paramètres
const curry = (fn, arity = fn.length) => 
    (function recursiveFunc(prevArgs) {
        return function(nextArgs) {
            const args = [
                ...prevArgs,
                nextArgs,
            ];

            if (args.length >= arity) {
                return fn( ...args );
            }
            
            return recursiveFunc(args);            
        };
    })([]);

// Mon tableau tout pourri :D
const myArray = [2, "4", 6, '8', {count: 3}, 10, "douze", "14s"];

// Ma fonction générique permettant de multiplier un nombre par un coef
const multiply = (coef, num) => num * coef;
/*
La version curryfié devient
const curriedMultiply = coef => num => num * coef;    
*/

// Ma fonction générique "multiply" rendu spécifique pour mon besoin du moment
// c'est à dire, multiplier par 2
const double = curry(multiply)(2);
/*
ou avec la version curryfié, cela devient plus lisible
const double = curriedMultiply(2);
*/
    
// Ma fonction de filtre des valeurs de type "number"
const isNumberFilter = num => !isNaN(num)

const doubleArray = myArray
    .map( double )
    .filter( isNumberFilter );

console.log( "doubleArray", doubleArray );
/*
doubleArray [4,8,12,16,20]    
*/

Conclusions

Voilà, j'espère que vous aurez trouvé la motivation suffisante pour lire et tester les différents exemples ci-dessus. Mais surtout, j'espère vous avoir aidé (au moins un peu) dans la compréhension des concepts de la programmation fonctionnelle.

Si vous souhaitez améliorer la qualité de votre code de manière significative, alors adoptez le paradigme de programmation fonctionnelle. Séparez les bouts de code qui peuvent l’être dans des fonctions très simples. Privilégiez toujours les fonctions pures, c’est-à-dire, n’utilisez que des paramètres passés en arguments et ne modifiez jamais les paramètres eux-mêmes car vous risquez de modifier une variable appartenant au "scope" de la fonction parente !

JavaScript : les fonctions à bannir définitivement de vos programmes

  • while
  • do...while
  • for
  • for...of
  • for...in

A bannir également :

  • var et let
  • object mutation (ex: car.power = 140)

Les fonctions de mutation des tableaux (array mutator) :

  • copyWithin
  • fill
  • pop
  • push
  • reverse
  • shift
  • sort
  • splice
  • unshift

Auteur : Gael Cadoret - Architecte IT chez 24s

Sources


Partager cet article
    Réagir à cet article
    Icon de profil de Gcadoret sur ingin.fr