Programmation fonctionnelle

La programmation fonctionnelle est un paradigme de programmation de type déclaratif qui considère le calcul en tant qu'évaluation de fonctions mathématiques.

Comme le changement d'état et la mutation des données ne peuvent pas être représentés par des évaluations de fonctions [1] la programmation fonctionnelle ne les admet pas, au contraire elle met en avant l'application des fonctions, contrairement au modèle de programmation impérative qui met en avant les changements d'état [2].

Un langage fonctionnel est donc un langage de programmation dont la syntaxe et les caractéristiques encouragent la programmation fonctionnelle. Alors que l'origine de la programmation fonctionnelle peut être trouvée dans le lambda-calcul, le langage fonctionnel le plus ancien est Lisp, créé en 1958 par McCarthy. Lisp a donné naissance à des variantes telles que Scheme ( 1975) et Common Lisp ( 1984) [3] qui, comme Lisp, ne sont pas ou peu typées. Des langages fonctionnels plus récents tels ML ( 1973), Haskell ( 1987), OCaml, Erlang, Clean et Oz, CDuce, Scala ( 2003) ou F# sont fortement typés.

Machine à états et effets secondaires

Programmation fonctionnelle

La programmation fonctionnelle s'affranchit de façon radicale des effets secondaires (ou effets de bord) en interdisant toute opération d'affectation.

Le paradigme fonctionnel n'utilise pas de machine à états pour décrire un programme, mais un emboîtement de fonctions qui agissent comme des « boîtes noires » que l'on peut imbriquer les unes dans les autres. Chaque boîte possédant plusieurs paramètres en entrée mais une seule sortie, elle ne peut sortir qu'une seule valeur possible pour chaque n-uplet de valeurs présentées en entrée. Ainsi, les fonctions n'introduisent pas d'effets de bord. Un programme est donc une application, au sens mathématique, qui ne donne qu'un seul résultat pour chaque ensemble de valeurs en entrée. Cette façon de penser, très différente de la démarche de la programmation impérative, est l'une des causes principales de la difficulté qu'ont les programmeurs formés aux langages impératifs pour aborder la programmation fonctionnelle. Cependant, elle ne pose généralement pas de difficultés particulières aux débutants qui n'ont jamais été exposés à des langages impératifs [4]. Un avantage important des fonctions sans effet de bord est la facilité que l'on a à les tester unitairement. Par ailleurs, l'usage généralisé d'une gestion de mémoire automatique par l'intermédiaire d'un ramasse-miettes simplifie la tâche du programmeur.

En pratique, pour des raisons d'efficacité, et du fait que certains algorithmes s'expriment aisément avec une machine à états, certains langages fonctionnels autorisent la programmation impérative en permettant de spécifier que certaines variables sont assignables (ou mutables selon la dénomination habituelle), et donc la possibilité d'introduire localement des effets de bord [réf. nécessaire]. Ces langages sont regroupés sous le nom de langages fonctionnels impurs.

Les langages dits purement fonctionnels n'autorisent pas la programmation impérative [1]. De fait, ils sont dénués d'effets de bord et protégés contre les problèmes que pose l' exécution concurrente. On peut voir par exemple ce qui a été fait dans le cadre du langage Erlang.

La mise en œuvre des langages fonctionnels fait un usage sophistiqué de la pile car, afin de s'affranchir de la nécessité de stocker des données temporaires dans des tableaux, ils font largement appel à la récursivité (fait d'inclure l'appel d'une fonction dans sa propre définition). La récursivité peut être rendue plus efficace à l'aide d'une technique dénommée récursion terminale (en anglais : tail-recursion), qui consiste à accumuler les résultats intermédiaires dans une case mémoire de la pile et à la passer en paramètre dans l'appel récursif. Ceci permet d'éviter d'empiler les appels récursifs dans la pile en les remplaçant par une simple succession de sauts. Le code généré par le compilateur est alors similaire à celui généré par une boucle en impératif. Certains langages comme Scheme, OCaml et Anubis optimisent automatiquement les appels récursifs de cette manière.

Programmation impérative et effets de bord

Article détaillé : programmation impérative.

La programmation impérative s'appuie sur le modèle des machines à états (voir aussi machine de Turing et Architecture de von Neumann), avec une mémoire centrale et des instructions qui modifient son état grâce à des affectations successives. Un tel programme est représenté par une machine à états (voir machine à compteurs) qui représente les états successifs de la mémoire. Cela nécessite pour le programmeur d'avoir à tout instant un modèle exact de l'état de la mémoire que le programme modifie.

Afin de réduire la complexité de cette tâche, de nombreuses techniques réduisent le nombre de variables à gérer à un même instant, la plupart relèvent de la programmation structurée et de l' encapsulation de données :

  • les variables dites automatiques ou locales, sont restreintes au seul domaine (procédure, méthode, etc.) dans lequel elles sont utiles. Leur portée est alors strictement limitée à ce domaine.
  • l'encapsulation de données, notamment celle de la programmation orientée objet, restreint la manipulation des variables au seul domaine de la classe à l'intérieur de laquelle elles sont définies (utilisation d' attributs privés)

Ces variables ont alors vocation à être désallouées par le compilateur ou interpréteur, immédiatement en sortie de procédure ou via un ramasse-miettes.

Cependant, il arrive que la conception choisie par le programmeur l'amène à modifier dans certaines procédures, volontairement ou non, des variables ou des zones de mémoire « partagées », ne relevant pas structurellement de la procédure. Il se trouve qu'en programmation impérative, ces modifications «périphériques», désignées sous le terme générique d' effets secondaires (ou effets de bord), sont de facto plus la règle que l'exception [réf. nécessaire] et compliquent grandement la compréhension des programmes, par conséquent, ils sont la source de nombreuses difficultés et de bugs : en effet, si on oublie de mettre à jour certaines données partagées ou qu'au contraire on les modifie sans en mesurer tous les effets de bord, si l'ordre chronologique des affectations par les différents composants du logiciel est inadéquat, ou si une zone de mémoire est désallouée au mauvais moment, le programme se retrouve dans un état imprévu, incohérent voire instable et le programmeur est souvent incapable de détecter l'origine de l'erreur et s'il y réussit c'est au prix d'une instrumentation lourde des programmes.

Other Languages
العربية: برمجة وظيفية
Bahasa Indonesia: Pemrograman Fungsional
日本語: 関数型言語
srpskohrvatski / српскохрватски: Funkcijsko programiranje
Tiếng Việt: Lập trình hàm