web-dev-qa-db-fra.com

Comprendre vraiment la différence entre procédural et fonctionnel

J'ai vraiment du mal à comprendre la différence entre les paradigmes de programmation procédural et fonctionnel.

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

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

En pratique, la différence entre une fonction mathématique et la notion de "fonction" utilisée dans la programmation impérative est que les fonctions impératives peuvent avoir des effets secondaires, modifiant la valeur de l'état du programme. À cause de cela, ils manquent de transparence référentielle, c'est-à-dire qu'une même expression de langage peut donner des valeurs différentes à des moments différents selon l'état du programme en cours d'exécution. À l'inverse, dans le code fonctionnel, la valeur de sortie d'une fonction dépend uniquement des arguments entrés dans la fonction. L'appel d'une fonction f à deux reprises avec la même valeur pour un argument x produira le même résultat f(x) les deux fois. L'élimination des effets secondaires peut faciliter la compréhension et la prévision du comportement d'un programme, ce qui est l'une des principales motivations du développement d'une programmation fonctionnelle.

Au paragraphe 2 où il est dit

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

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

Que faut-il rechercher dans la procédure vs fonctionnelle qui ressortent?

109
Philoxopher

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 "régulières". Nous pouvons prendre deux valeurs entières et les combiner en utilisant l'opérateur + Pour obtenir un nouvel entier. Ou nous pouvons multiplier un 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 à l'aide d'opérateurs tels que compose ou lift . Nous pouvons également combiner une valeur de fonction et une valeur de données pour produire une nouvelle valeur à l'aide d'opérateurs tels que map ou fold .

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

Programmation procédurale

La programmation procédurale fait référence à la possibilité d'encapsuler une séquence d'instructions commune dans une procédure de sorte que ces instructions puissent être appelées de nombreux endroits sans recourir au copier-coller. Comme les procédures constituaient un développement très précoce de la programmation, cette capacité est presque toujours liée au style de programmation demandé par la programmation en langage machine ou en langage assembleur: un style qui met l'accent sur la notion d'emplacements de stockage et d'instructions déplaçant des données entre ces emplacements.

Contraste

Les deux styles ne sont pas vraiment opposés - ils sont juste différents l'un de l'autre. Il existe des langues qui intègrent 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 un besoin insensé dans lequel nous voulons déterminer si tous les mots d'une liste ont un nombre impair de caractères. Tout d'abord, 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 vais prendre pour acquis que cet exemple est compréhensible. Maintenant, style fonctionnel:

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

En travaillant de l'intérieur, cette définition fait les choses suivantes:

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

Vous pouvez voir à partir de ces exemples que la programmation procédurale est très soucieuse de déplacer des valeurs dans des variables et de 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.

L'exemple montre également les tailles relatives typiques du code procédural par rapport au code fonctionnel. De plus, cela démontre que les caractéristiques de performance du code de procédure pourraient être plus faciles à voir que celles du code fonctionnel. Considérez: les fonctions calculent-elles la longueur de tous les mots de la liste ou s'arrêtent-elles immédiatement après la recherche du premier mot de longueur égale? D'autre part, le code fonctionnel permet à une implémentation de haute qualité d'effectuer une optimisation assez sérieuse puisqu'il exprime principalement l'intention plutôt qu'un algorithme explicite.

Lectures supplémentaires

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

La conférence Turing Award de John Backus énonce en détail les motivations de la programmation fonctionnelle:

Peut-on libérer la programmation du style de von Neumann?

Je ne devrais vraiment pas mentionner ce papier dans le contexte actuel car il devient assez technique, assez rapidement. Je ne pouvais tout simplement pas résister parce que je pense que c'est vraiment fondamental.


Addendum - 2013

Les commentateurs soulignent que les langages contemporains populaires offrent d'autres styles de programmation que les procédures procédurales et fonctionnelles. De tels langages offrent souvent un ou plusieurs des styles de programmation suivants:

  • requête (par exemple, compréhension de liste, requête intégrée à la langue)
  • flux de données (par exemple, itération implicite, opérations en bloc)
  • orienté objet (par exemple, données et procédés encapsulés)
  • orienté vers le langage (syntaxe, macros, par exemple)

Voir les commentaires ci-dessous pour obtenir des exemples montrant comment les exemples de pseudo-codes de cette réponse peuvent tirer parti de certaines des installations disponibles à partir de ces autres styles. En particulier, l'exemple de procédure bénéficiera de l'application de pratiquement n'importe quelle construction de niveau supérieur.

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

269
WReach

La différence entre la programmation fonctionnelle et la programmation impérative réside dans l’état d’esprit - les programmeurs impératifs pensent aux variables et aux blocs de mémoire, tandis que les programmeurs fonctionnels se demandent "Comment puis-je transformer mes données d’entrée en mes données de sortie" - votre "programme" est le pipeline et l'ensemble des transformations sur les données pour les emmener de l'entrée à la sortie. C'est la partie intéressante OMI, pas le bit "Tu ne vas pas utiliser de variables".

En conséquence de cet état d'esprit, les programmes FP décrivent généralement ce qui va se passer , au lieu du mécanisme spécifique de comment cela se produira - cela est puissant, car si nous pouvons indiquer clairement ce que "Select" et "Où" et "Agrégat" signifient, nous sommes libres de les échanger. leurs implémentations, comme nous le faisons avec AsParallel () et tout à coup, notre application mono-thread évolue vers n cœurs.

45
Paul Betts
     Isn't that the same exact case for procedural programming?

Non, car le code de procédure 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 réponde à cette contrainte dans des langages considérés comme procéduraux. Et il est également possible d'écrire du code qui casse cette contrainte dans certains langages considérés comme fonctionnels.

12
Andy Thomas

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;
}

et

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

La première chose à noter est qu'il confond:

  • Fonctionnel
  • Orienté expression et
  • Itérateur centré

programmation et 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 rapidement de ceux-ci.

Le style axé sur l’expression est celui où les choses, autant que possible, évaluent 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 une, où il y a non 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 chaînées uniquement par des chaînes d'instructions et de liaisons.

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

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 ils sont liés entre eux par une pause explicite et une reprise des trames de pile. La syntaxe peut être partiellement inspirée d’un langage fonctionnel, mais elle s’applique à un mode de réalisation complètement itératif de celui-ci.

Bien sûr, vous pouvez compresser ceci:

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 utiliser ceci:

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

En utilisant des itérateurs, vous pouvez avoir:

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

Alors, quel est le point 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 dans le cadre du modèle de programmation typique. Les gens pensent souvent que cela veut dire 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.

En vous limitant à ce sous-ensemble de fonctionnalités, vous disposez de davantage de garanties sur le comportement de vos programmes, ce qui vous permet de les composer plus librement.

Lorsque vous avez un langage fonctionnel, créer de nouvelles fonctions est généralement aussi simple que de composer des fonctions étroitement liées.

all = partial(apply, and)

Ce n'est pas simple, ni même 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 systématiquement créer des abstractions plus génériques et que vous pouvez être sûr qu'elles peuvent être combinées pour former un tout.

11
Veedrac

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

Dans le paradigme fonctionnel, vous avez des variables et des fonctions (au sens mathématique du terme: les variables ne varient pas dans le temps, les fonctions ne peuvent calculer que quelque chose en fonction de leurs entrées).

(Ceci est simplifié à l'extrême, par exemple, les FPL ont généralement la possibilité de travailler avec de la mémoire mutable alors que les langages procéduraux supportent souvent des procédures d'ordre supérieur, de sorte que les choses ne sont pas aussi claires; mais cela devrait vous donner une idée.)

6

Le Charming Python: Programmation fonctionnelle en Python de IBM Developerworks m'a vraiment aidé à comprendre la différence.

Surtout pour quelqu'un qui sait Python un peu, les exemples de code dans cet article dans lesquels il est opposé de faire différentes choses fonctionnellement et procéduralement peuvent clarifier la différence entre programmation procédurale et fonctionnelle.

2
Abbafei

En programmation fonctionnelle, afin de pouvoir raisonner sur la signification d'un symbole (nom de variable ou de fonction), il vous suffit de connaître 2 choses: la portée actuelle et le nom du symbole. Si vous avez un langage purement fonctionnel avec immuabilité, il s’agit de concepts "statiques" (désolé pour les noms très surchargés), ce qui signifie que vous pouvez voir les deux - la portée actuelle et le nom - en regardant simplement le code source.

En programmation procédurale, si vous voulez répondre à la question: quelle est la valeur derrière x, vous devez également savoir comment vous y êtes arrivé, la portée et le nom ne suffisent pas. Et c’est ce que je verrais comme le plus gros défi car ce chemin d’exécution est une propriété "runtime" et peut dépendre de tant de choses différentes que la plupart des gens apprennent à le déboguer et non à essayer de récupérer le chemin d’exécution.

2
vasily

J'ai récemment pensé à la différence en termes de problème d'expression . Description de Phil Wadler est souvent cité, mais la réponse acceptée à cette question est probablement plus facile à suivre. Fondamentalement, il semble que les langages impératifs tendent à choisir une approche du problème, tandis que les langages fonctionnels ont tendance à choisir l’autre.

1
John L