functional-programmingDémarrer avec la programmation fonctionnelle


Remarques

La programmation fonctionnelle est un paradigme de programmation qui modélise les calculs (et donc les programmes) en tant qu’évaluation de fonctions mathématiques. Il a ses racines dans le lambda calcul, développé par Alonzo Church dans ses recherches sur la calculabilité.

La programmation fonctionnelle a des concepts intéressants:

  • Fonctions d'ordre supérieur
  • Pureté
  • Récursivité
  • Paresse
  • Transparence référentielle
  • Currying
  • Les foncteurs
  • Monades
  • Mémo & Optimisation des appels
  • Test d'unité fonctionnelle

Quelques exemples de langages de programmation fonctionnels sont Lisp , Haskell , Scala et Clojure , mais aussi d’autres langages, comme Python , R et Javascript, qui permettent d’écrire (des parties de) vos programmes dans un style fonctionnel. Même en Java , la programmation fonctionnelle a trouvé sa place avec les expressions Lambda et l’ API Stream qui ont été introduites dans Java 8.

Currying

Le curry est le processus de transformation d'une fonction qui prend de multiples arguments en une séquence de fonctions dont chacune n'a qu'un seul paramètre. Le curry est lié à, mais pas identique, à une application partielle.

Considérons la fonction suivante en JavaScript:

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

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

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

Cette nouvelle version prend un seul paramètre, x , et retourne une fonction qui prend un seul paramètre, y , ce qui retourne 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 placent des parenthèses 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 dans les chaînes qui ne sont pas des crochets, notre fonction renvoie toujours un résultat incorrect. Fixons cela:

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

Notez que les deux bracket et doubleBracket sont maintenant des fonctions en attente de leur paramètre final:

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

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 constituent les éléments constitutifs de la programmation fonctionnelle. La plupart des langages fonctionnels ont une forme de fonction de filtrage, par exemple. C'est une fonction d'ordre supérieur, prenant une liste et un prédicat (fonction qui retourne true ou false) comme arguments.

Les fonctions qui ne font ni l'une ni l'autre sont souvent appelées fonctions de first-order functions .

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

Ici, "prédicat" est une fonction qui teste une condition impliquant ses arguments et renvoie true ou false.

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

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

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

Vous voulez ajouter tous les entiers compris 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 entiers compris 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 à chaque argument et ensuite les ajouter.

Permet de créer 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 comme ceci:

def identity(a: Int): Int = a

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

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

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 dans 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 - par Martin Odersky comme référence.

Immutabilité

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

Les variables n'existent pas dans la programmation fonctionnelle. Les valeurs stockées sont encore appelées variables uniquement à cause 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 effectuant 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 telles que 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 du mondial au 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 du test - vous n'avez pas à vous soucier de la valeur de x après un certain nombre d'instructions, cela ne changera jamais).

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