Initiation à la programmation fonctionnelle

Fonctions pures

Les fonctions pures sont autonomes et n’ont aucun effet secondaire. Étant donné le même ensemble d’entrées, une fonction pure renverra toujours la même valeur de sortie.

La fonction suivante est pure :

function pure(data) {
    return data.total + 3;
}

Cependant, cette fonction n’est pas pure car elle modifie une variable externe :

function impure(data) {
    data.total += 3;
    return data.total;
}

Exemple:

data = {
    total: 6
};

pure(data);   // outputs: 9
impure(data); // outputs: 9 (but now data.total has changed)
impure(data); // outputs: 12

Fonctions d’ordre supérieur

Les fonctions d’ordre supérieur prennent d’autres fonctions comme arguments et/ou les renvoient comme résultats. Ils forment les blocs de construction de la programmation fonctionnelle. La plupart des langages fonctionnels ont une forme de fonction de filtrage, par exemple. Il s’agit d’une fonction d’ordre supérieur, prenant une liste et un prédicat (fonction qui renvoie vrai ou faux) comme arguments.

Les fonctions qui ne font ni l’un ni l’autre sont souvent appelées “fonctions du premier ordre”.

function validate(number,predicate) {
    if (predicate) {    // Is Predicate defined
        return predicate(number);
    }
    return false;
}

Ici, “prédicat” est une fonction qui testera une condition impliquant ses arguments et renverra vrai ou faux.

Un exemple d’appel pour ce qui précède est :

validate(someNumber, function(arg) {
    return arg % 10 == 0;
    }
);

Une exigence courante consiste à ajouter des nombres dans une plage. En utilisant des fonctions d’ordre supérieur, nous pouvons étendre cette capacité de base, en appliquant une fonction de transformation sur chaque nombre avant de l’inclure dans la somme.

  • Vous voulez ajouter tous les entiers dans une plage donnée (en utilisant Scala) *

    def sumOfInts(a: Int, b: Int): Int = { if(a > b) 0 else a + sumOfInts(a+1, b) } Vous voulez ajouter des carrés de tous les nombres entiers dans une plage donnée

    def square(a: Int): Int = a * a

    def sumOfSquares(a: Int, b: Int): Int = { if(a > b) 0 else square(a) + sumOfSquares(a + 1, b) }

Notez que ces choses ont 1 chose en commun, que vous voulez appliquer une fonction sur chaque argument, puis les ajouter.

Créons une fonction d’ordre supérieur pour faire les deux :

def sumHOF(f: Int => Int, a: Int, b: Int): Int = {
  if(a > b) 0
  else f(a) + sumHOF(f, a + 1, b)
}

Vous pouvez l’appeler ainsi :

def identity(a: Int): Int = a

def square(a: Int): Int = a * a

Notez que sumOfInts et sumOfSquare peuvent être définis comme :

def sumOfInts(a: Int, b: Int): Int = sumHOF(identity, a, b)

def sumOfSquares(a: Int, b: Int): Int = sumHOF(square, a, b)

Comme vous pouvez le voir sur cet exemple simple, les fonctions d’ordre supérieur fournissent des solutions plus généralisées et réduisent la duplication de code.

J’ai utilisé Scala By Example - de Martin Odersky comme référence.

Curry

Currying est le processus de transformation d’une fonction qui prend plusieurs arguments en une séquence de fonctions qui n’ont chacune qu’un seul paramètre. Le curry est lié à, mais pas la même chose, à une application partielle.

Considérons la fonction suivante en JavaScript :

var add = (x, y) => x + y

Nous pouvons utiliser la définition de currying pour réécrire la fonction add :

var add = x => y => x + y

Cette nouvelle version prend un seul paramètre, x, et renvoie une fonction qui prend un seul paramètre, y, qui renverra finalement le résultat de l’ajout de x et y.

var add5 = add(5)
var fifteen = add5(10) // fifteen = 15

Un autre exemple est lorsque nous avons les fonctions suivantes qui mettent des crochets autour des chaînes :

var generalBracket = (prefix, str, suffix) => prefix + str + suffix

Maintenant, chaque fois que nous utilisons generalBracket nous devons passer entre parenthèses :

var bracketedJim = generalBracket("{", "Jim", "}") // "{Jim}"
var doubleBracketedJim = generalBracket("{{", "Jim", "}}") // "{{Jim}}"

De plus, si nous passons des chaînes qui ne sont pas entre parenthèses, notre fonction renvoie toujours un mauvais résultat. Corrigeons ça :

var generalBracket = (prefix, suffix) => str => prefix + str + suffix
var bracket = generalBracket("{", "}")
var doubleBracket = generalBracket("{{", "}}")

Notez que bracket et doubleBracket sont maintenant des fonctions attendant leur dernier paramètre :

var bracketedJim = bracket("Jim") // "{Jim}"
var doubleBracketedJim = doubleBracket("Jim") // "{{Jim}}"

Immuabilité

Dans les langages traditionnels orientés objet, x = x + 1 est une expression simple et légale. Mais en programmation fonctionnelle, c’est illégal.

Les variables n’existent pas dans la programmation fonctionnelle. Les valeurs stockées sont toujours appelées variables uniquement en raison de l’historique. En fait, ce sont des constantes. Une fois que x prend une valeur, c’est cette valeur pour la vie.

Donc, si une variable est une constante, alors comment pouvons-nous changer sa valeur ?

La programmation fonctionnelle traite des modifications apportées aux valeurs d’un enregistrement en faisant une copie de l’enregistrement avec les valeurs modifiées.

Par exemple, au lieu de faire :

var numbers = [1, 2, 3];
numbers[0] += 1; // numbers = [2, 2, 3];

Tu fais:

var numbers = [1, 2, 3];
var newNumbers = numbers.map(function(number) {
    if (numbers.indexOf(number) == 0)
        return number + 1
    return number
});
console.log(newNumbers) // prints [2, 2, 3]

Et il n’y a pas de boucles dans la programmation fonctionnelle. Nous utilisons des fonctions de récursivité ou d’ordre supérieur comme map, filter et reduce pour éviter les boucles.

Créons une boucle simple en JavaScript :

var acc = 0;
for (var i = 1; i <= 10; ++i)
    acc += i;
console.log(acc); // prints 55

Nous pouvons encore faire mieux en changeant la durée de vie de acc de global à local :

function sumRange(start, end, acc) {
    if (start > end)
        return acc;
    return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); // 55

Aucune variable ou boucle ne signifie un code plus simple, plus sûr et plus lisible (en particulier lors du débogage ou des tests - vous n’avez pas à vous soucier de la valeur de x après un certain nombre d’instructions, elle ne changera jamais).