118 votes

Comprendre réellement la différence entre procédural et fonctionnel

J'ai vraiment du mal à comprendre la différence entre procédure y fonctionnel paradigmes de programmation.

Voici les deux premiers paragraphes de l'entrée de Wikipedia sur programmation fonctionnelle :

En informatique, la programmation fonctionnelle programmation fonctionnelle est un paradigme de programmation qui traite le calcul comme une l'évaluation de fonctions mathématiques et évite l'état et les données mutables. Elle met l'accent sur l'application de fonctions, contrairement à la style de programmation impératif, qui qui met l'accent sur les changements d'état. La programmation fonctionnelle trouve ses racines dans le lambda calculus, un système formel développé dans les années 1930 pour étudier la définition des fonctions, leur application de fonctions et la récursion. De nombreux langages de programmation fonctionnels peuvent être considérés comme des élaborations du lambda calculus.

En pratique, la différence entre une fonction mathématique et la notion de d'une "fonction" utilisée en programmation est que les fonctions impératives fonctions peuvent avoir des effets secondaires, en changeant la valeur de l'état du programme. De ce fait, elles manquent de transparence référentielle référentielle, c'est-à-dire que la même expression langage peut donner lieu à des valeurs valeurs différentes à des moments différents, selon l'état du programme en cours d'exécution. A l'inverse, dans un code fonctionnel, la valeur de sortie d'une fonction dépend uniquement des arguments qui sont entrés à la fonction, de sorte que l'appel d'une fonction f deux fois avec la même valeur pour un argument x produira le même résultat f(x) les deux fois. Élimination de effets secondaires peut rendre beaucoup plus facile de comprendre et de prédire le comportement d'un programme, ce qui est l'une des principales motivations pour le développement de la programmation fonctionnelle.

Au paragraphe 2, où il est dit

À l'inverse, dans le code fonctionnel, la valeur de sortie d'une fonction ne dépend que des arguments qui sont entrés dans la fonction, de sorte que l'appel d'une fonction f deux fois avec la même valeur pour un argument x produira le même résultat f(x) les deux fois.

N'est-ce pas exactement le même cas pour la programmation procédurale ?

Qu'est-ce qu'il faut rechercher dans le procédural par rapport au fonctionnel pour se démarquer ?

1 votes

Le "Charmant Python : Functional Programming in Python" d'Abafei était cassé. Voici un bon ensemble de liens : ibm.com/developerworks/linux/library/l-prog/index.html ibm.com/developerworks/linux/library/l-prog2/index.html

0 votes

L'attribution de noms en est un autre aspect. Par exemple, en JavaScript et en Common Lisp, nous utilisons le terme "fonction" même si les effets secondaires sont autorisés, alors qu'en Scheme, la même chose est systématiquement appelée "procédure". Une fonction CL qui est pure peut être écrite comme une procédure Scheme fonctionnelle pure. Presque tous les livres sur Scheme utilisent le terme procédure car c'est le terme utilisé dans la norme et cela n'a rien à voir avec le fait qu'il s'agisse d'une procédure ou d'une fonction.

284voto

WReach Points 13161

Programmation fonctionnelle

La programmation fonctionnelle fait référence à la capacité de traiter les fonctions comme des valeurs.

Considérons une analogie avec les valeurs "ordinaires". Nous pouvons prendre deux valeurs entières et les combiner à l'aide de la fonction + pour obtenir un nouveau nombre entier. Ou bien nous pouvons multiplier un nombre entier par un nombre à virgule flottante pour obtenir un nombre à virgule flottante.

En programmation fonctionnelle, nous pouvons combiner deux valeurs de fonction pour produire une nouvelle valeur de fonction en utilisant des opérateurs tels que composer o ascenseur . Nous pouvons également combiner une valeur de fonction et une valeur de données pour produire une nouvelle valeur de données en utilisant des opérateurs tels que carte o pliage .

Notez que de nombreux langages ont des capacités de programmation fonctionnelle, même ceux qui ne sont pas habituellement considérés comme des langages fonctionnels. Même le grand-père FORTRAN supportait les valeurs de fonctions, bien qu'il n'offrait pas beaucoup d'opérateurs de combinaison de fonctions. Pour qu'un langage soit qualifié de "fonctionnel", il faut qu'il offre des capacités de programmation fonctionnelle de manière importante.

Programmation procédurale

La programmation procédurale fait référence à la capacité d'encapsuler une séquence commune d'instructions dans une procédure afin que ces instructions puissent être invoquées à partir de nombreux endroits sans avoir recours au copier-coller. Comme les procédures ont été un développement très précoce de la programmation, cette capacité est presque invariablement liée au style de programmation exigé par la programmation en langage machine ou en langage d'assemblage : un style qui met l'accent sur la notion d'emplacements de stockage et d'instructions qui déplacent les données entre ces emplacements.

Contraste

Les deux styles ne sont pas vraiment opposés, ils sont simplement différents l'un de l'autre. Il existe des langages qui embrassent pleinement les deux styles (LISP, par exemple). Le scénario suivant peut donner une idée de certaines différences entre les deux styles. Écrivons du code pour une exigence de non-sens où nous voulons déterminer si tous les mots d'une liste ont un nombre impair de caractères. Tout d'abord, le style procédural :

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

Je considère comme acquis que cet exemple est compréhensible. Maintenant, le style fonctionnel :

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

Travaillant de l'intérieur vers l'extérieur, cette définition fait les choses suivantes :

  1. compose(odd, length) combine le odd y length pour produire une nouvelle fonction qui détermine si la longueur d'une chaîne est impaire.
  2. map(..., words) appelle cette nouvelle fonction pour chaque élément dans words et renvoie finalement une nouvelle liste de valeurs booléennes, chacune indiquant si le mot correspondant a un nombre impair de caractères.
  3. apply(and, ...) applique l'opérateur "et" à la liste résultante, y -en additionnant tous les booléens pour obtenir le résultat final.

Ces exemples montrent que la programmation procédurale s'attache à déplacer des valeurs dans des variables et à décrire explicitement les opérations nécessaires pour produire le résultat final. En revanche, le style fonctionnel met l'accent sur la combinaison des fonctions nécessaires pour transformer l'entrée initiale en sortie finale.

Cet exemple montre également la taille relative typique du code procédural par rapport au code fonctionnel. En outre, il démontre que les caractéristiques de performance du code procédural peuvent être plus faciles à voir que celles du code fonctionnel. Par exemple : les fonctions calculent-elles la longueur de tous les mots de la liste ou s'arrêtent-elles immédiatement après avoir trouvé le premier mot de longueur égale ? D'autre part, le code fonctionnel permet à une implémentation de haute qualité d'effectuer des optimisations assez sérieuses puisqu'il exprime principalement une intention plutôt qu'un algorithme explicite.

Autres lectures

Cette question revient souvent... voir, par exemple :

La conférence de John Backus, récompensée par le prix Turing, expose en détail les motivations de la programmation fonctionnelle :

La programmation peut-elle être libérée du style von Neumann ?

Je ne devrais vraiment pas mentionner cet article dans le contexte actuel, car il devient très technique, très rapidement. Je n'ai pas pu résister car je pense qu'il est vraiment fondamental.


Addendum - 2013

Les commentateurs soulignent que les langages contemporains les plus populaires proposent d'autres styles de programmation que les langages procéduraux et fonctionnels. Ces langages proposent souvent un ou plusieurs des styles de programmation suivants :

  • interrogation (par exemple, compréhension de listes, interrogation intégrée à la langue)
  • flux de données (par exemple, itération implicite, opérations en masse)
  • orienté objet (par exemple, données et méthodes encapsulées)
  • orientée vers le langage (par exemple, syntaxe spécifique à l'application, macros)

Voir les commentaires ci-dessous pour des exemples de la façon dont les exemples de pseudo-code dans cette réponse peuvent bénéficier de certaines des facilités disponibles dans ces autres styles. En particulier, l'exemple procédural bénéficiera de l'application de pratiquement toute construction de niveau supérieur.

Les exemples exposés évitent délibérément de mêler ces autres styles de programmation afin de souligner la distinction entre les deux styles en question.

1 votes

En effet, belle réponse, mais pourriez-vous simplifier un peu le code, par exemple : "function allOdd(words) { foreach (auto word in words) { odd(length(word) ? return false : ; } return true ; }"

0 votes

Le style fonctionnel est assez difficile à lire comparé au "style fonctionnel" de python : def odd_words(words) : return [x for x in words if odd(len(x))].

0 votes

@boxed : Votre odd_words(words) La définition fait quelque chose de différent de celle de la réponse allOdd . Pour le filtrage et le mappage, les compréhensions de listes sont souvent préférées, mais ici la fonction allOdd est censé réduire une liste de mots à une seule valeur booléenne.

48voto

Paul Betts Points 41354

La véritable différence entre la programmation fonctionnelle et la programmation impérative réside dans l'état d'esprit - les programmeurs impératifs pensent à des variables et à des blocs de mémoire, tandis que les programmeurs fonctionnels se demandent "Comment puis-je ". transformer mes données d'entrée dans mes données de sortie" - votre "programme" est le pipeline et l'ensemble des transformations sur le réseau de l'entreprise. données pour le faire passer de l'entrée à la sortie. C'est ce qui est intéressant, à mon avis, et non pas le passage "Tu n'utiliseras pas de variables".

En conséquence de cet état d'esprit, les programmes de PF décrivent généralement ce que se produira, au lieu du mécanisme spécifique de comment c'est ce qui se produira - c'est puissant parce que si nous pouvons clairement énoncer ce que "Select" et "Where" et "Aggregate" signifient, nous sommes libres de changer leurs implémentations, tout comme nous le faisons avec AsParallel() et soudainement notre application monofilaire passe à n cœurs.

0 votes

Pourriez-vous comparer les deux en utilisant des exemples de code ?

1 votes

@KerxPhilo : Voici une tâche très simple (additionner des nombres de 1 à n). Impératif : Modifier le nombre actuel, modifier la somme jusqu'à présent. Code : int i, sum ; sum = 0 ; for (i = 1 ; i <= n ; i++) { sum += i ; }. Fonctionnel (Haskell) : Prendre une liste paresseuse de nombres, les plier ensemble tout en ajoutant à zéro. Code : foldl (+) 0 [1..n]. Désolé, pas de mise en forme dans les commentaires.

0 votes

+1 à la réponse. En d'autres termes, la programmation fonctionnelle consiste à écrire des fonctions sans effets secondaires dans la mesure du possible, c'est-à-dire que la fonction renvoie toujours la même chose lorsqu'elle reçoit les mêmes paramètres - c'est la base. Si vous suivez cette approche à l'extrême, vos effets secondaires (vous en avez toujours besoin) seront isolés et le reste des fonctions se contentera de transformer les données d'entrée en données de sortie.

12voto

Andy Thomas Points 30979
     Isn't that the same exact case for procedural programming?

Non, parce que le code procédural peut avoir des effets secondaires. Par exemple, il peut stocker l'état entre les appels.

Cela dit, il est possible d'écrire du code qui satisfait à cette contrainte dans des langages considérés comme procéduraux. Et il est également possible d'écrire du code qui brise cette contrainte dans certains langages considérés comme fonctionnels.

1 votes

Pouvez-vous montrer un exemple et une comparaison ? J'apprécierais vraiment que vous le fassiez.

8 votes

La fonction rand() en C fournit un résultat différent à chaque appel. Elle stocke l'état entre les appels. Elle n'est pas transparente sur le plan référentiel. En comparaison, la fonction std::max(a,b) en C++ renvoie toujours le même résultat avec les mêmes arguments, et n'a pas d'effets secondaires (à ma connaissance...).

11voto

Veedrac Points 11712

Je ne suis pas d'accord avec la réponse de WReach. Déconstruisons un peu sa réponse pour voir d'où vient le désaccord.

D'abord, son code :

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

y

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

La première chose à noter est qu'il fait un amalgame :

  • Fonctionnel
  • Orienté vers l'expression et
  • Centré sur l'itérateur

et il manque la possibilité pour la programmation de style itératif d'avoir un flux de contrôle plus explicite qu'un style fonctionnel typique.

Parlons-en rapidement.

Le style centré sur l'expression est un style où les choses, autant que possible, sont mises en valeur, évaluer aux choses. Bien que les langages fonctionnels soient réputés pour leur amour des expressions, il est en fait possible d'avoir un langage fonctionnel sans expressions composables. Je vais en inventer un, dans lequel il y a pas de des expressions, simplement des déclarations.

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and

C'est à peu près la même chose que précédemment, sauf que les fonctions sont enchaînées purement par des chaînes d'instructions et de liaisons.

Un style de programmation centré sur les itérateurs pourrait être celui adopté par Python. Utilisons un purement style itératif, centré sur l'itérateur :

def all_odd(words):
    lengths = (len(word) for word in words)
    each_odd = (odd(length) for length in lengths)
    return all(each_odd)

Ce n'est pas fonctionnel, car chaque clause est un processus itératif, et elles sont liées entre elles par une pause et une reprise explicites des trames de la pile. La syntaxe peut être inspirée partiellement d'un langage fonctionnel, mais elle est appliquée à une incarnation complètement itérative de celui-ci.

Bien sûr, vous pouvez le compresser :

def all_odd(words):
    return all(odd(len(word)) for word in words)

L'impératif n'a pas l'air si mauvais maintenant, hein ? :)

Le dernier point concernait un flux de contrôle plus explicite. Réécrivons le code original pour l'utiliser :

function allOdd(words) {
    for (var i = 0; i < length(words); ++i) {
        if (!odd(length(words[i]))) {
            return false;
        }
    }
    return true;
}

En utilisant les itérateurs, vous pouvez avoir :

function allOdd(words) {
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
}

Alors quoi ? es l'intérêt d'un langage fonctionnel si la différence est entre :

return all(odd(len(word)) for word in words)

return apply(and, map(compose(odd, length), words))

for (word : words) { if (!odd(length(word))) { return false; } }
return true;

La principale caractéristique définitive d'un langage de programmation fonctionnel est qu'il supprime la mutation comme élément du modèle de programmation typique. Pour les gens, cela signifie souvent qu'un langage de programmation fonctionnel n'a pas d'instructions ou n'utilise pas d'expressions, mais ce sont des simplifications. Un langage fonctionnel remplace le calcul explicite par une déclaration de comportement, sur laquelle le langage effectue ensuite une réduction.

Se limiter à ce sous-ensemble de fonctionnalités vous permet d'avoir plus de garanties sur les comportements de vos programmes, ce qui vous permet de les composer plus librement.

Lorsque vous disposez d'un langage fonctionnel, la création de nouvelles fonctions est généralement aussi simple que la composition de fonctions étroitement liées.

all = partial(apply, and)

Ce n'est pas simple, ou peut-être même pas possible, si vous n'avez pas contrôlé explicitement les dépendances globales d'une fonction. La meilleure caractéristique de la programmation fonctionnelle est que vous pouvez constamment créer des abstractions plus génériques et avoir confiance dans le fait qu'elles peuvent être combinées en un tout plus grand.

0 votes

Tu sais, je suis presque sûr qu'un apply n'est pas tout à fait la même opération qu'un fold o reduce Je suis cependant d'accord avec la possibilité d'avoir des algorithmes très génériques.

0 votes

Je n'ai jamais entendu parler de apply pour signifier fold o reduce mais il me semble qu'il faut que ce soit dans ce contexte pour qu'il renvoie un booléen.

0 votes

Ah, ok, j'ai été confondu par le nom. Merci de l'avoir clarifié.

6voto

Artyom Shalkhakov Points 659

Dans le paradigme procédural (devrais-je plutôt parler de "programmation structurée" ?), vous avez une mémoire mutable partagée et des instructions qui la lisent/écrivent dans une certaine séquence (l'une après l'autre).

Dans le paradigme fonctionnel, vous avez des variables et des fonctions (au sens mathématique : les variables ne varient pas dans le temps, les fonctions peuvent seulement calculer quelque chose sur la base de leurs entrées).

(Ceci est trop simplifié, par exemple, les FPLs ont typiquement des facilités pour travailler avec de la mémoire mutable alors que les langages procéduraux peuvent souvent supporter des procédures d'ordre supérieur, donc les choses ne sont pas aussi claires ; mais ceci devrait vous donner une idée).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X