Question

J'ai lu les articles Wikipédia pour les deux programmation procédurale et programmation fonctionnelle, mais je suis encore un peu confus.Quelqu'un pourrait-il le résumer jusqu'au fond ?

Était-ce utile?

La solution

Un langage fonctionnel (idéalement) permet d'écrire une fonction mathématique, c'est-à-direune fonction qui prend n arguments et renvoie une valeur.Si le programme est exécuté, cette fonction est logiquement évaluée selon les besoins.1

Un langage procédural, quant à lui, effectue une série de séquentiel pas.(Il existe un moyen de transformer la logique séquentielle en logique fonctionnelle appelée style de passage de continuation.)

En conséquence, un programme purement fonctionnel donne toujours la même valeur pour une entrée, et l’ordre d’évaluation n’est pas bien défini ;ce qui signifie que les valeurs incertaines telles que les entrées utilisateur ou les valeurs aléatoires sont difficiles à modéliser dans des langages purement fonctionnels.


1 Comme tout le reste dans cette réponse, c'est une généralisation.La propriété d’évaluer un calcul lorsque son résultat est nécessaire plutôt que séquentiellement là où on l’appelle est connue sous le nom de « paresse », et tous les langages fonctionnels ne sont pas en réalité universellement paresseux, et la paresse n’est pas non plus limitée à la programmation fonctionnelle.La description donnée ici fournit plutôt un « cadre mental » pour réfléchir à différents styles de programmation qui ne sont pas des catégories distinctes et opposées mais plutôt des idées fluides.

Autres conseils

Fondamentalement, les deux styles sont comme le Yin et le Yang.L’un est organisé, tandis que l’autre est chaotique.Il existe des situations où la programmation fonctionnelle est le choix évident, et d'autres situations où la programmation procédurale est le meilleur choix.C'est pourquoi au moins deux langages ont récemment sorti une nouvelle version, qui englobe les deux styles de programmation. ( Perl 6 et J 2 )

De procédure:

  • La sortie d’une routine n’a pas toujours une corrélation directe avec l’entrée.
  • Tout est fait dans un ordre précis.
  • L'exécution d'une routine peut avoir des effets secondaires.
  • A tendance à mettre l’accent sur la mise en œuvre de solutions de manière linéaire.

Perl6

J2

int factorial( int n ){

  int result = 1;

  for( ; n > 0 ; n-- ){
    result *= n;
  }

  return result;
}

Fonctionnel:

  • Souvent récursif.
  • Renvoie toujours la même sortie pour une entrée donnée.
  • L’ordre d’évaluation n’est généralement pas défini.
  • Doit être apatride.c'est à dire.Aucune opération ne peut avoir d’effets secondaires.
  • Bon ajustement pour une exécution parallèle
  • A tendance à mettre l’accent sur une approche diviser pour mieux régner.
  • Peut avoir la fonctionnalité d’évaluation paresseuse.

Haskell

(copié de Wikipédia );

fac :: Integer -> Integer

fac 0 = 1
fac n | n > 0 = n * fac (n-1)

ou en une seule ligne :

fac n = if n > 0 then n * fac (n-1) else 1

Perl6

J2

pure int factorial( invariant int n ){
  if( n <= 1 ){
    return 1;
  }else{
    return n * factorial( n-1 );
  }
}

Remarque complémentaire :

Factorial est en fait un exemple courant pour montrer à quel point il est facile de créer de nouveaux opérateurs dans Perl 6 de la même manière que vous créeriez un sous-programme.Cette fonctionnalité est tellement ancrée dans Perl 6 que la plupart des opérateurs de l'implémentation de Rakudo sont définis de cette façon.Il vous permet également d'ajouter vos propres multi-candidats aux opérateurs existants.

Cet exemple montre également la création de plage (2..$n) et le méta-opérateur de réduction de liste ([ OPERATOR ] LIST) combiné avec l'opérateur de multiplication d'infixe numérique.(*)
Cela montre également que vous pouvez mettre --> UInt dans la signature au lieu de returns UInt après ça.

(Vous pouvez commencer la gamme avec 2 car l'"opérateur" de multiplication reviendra 1 lorsqu'il est appelé sans aucun argument)

Je n'ai jamais vu cette définition donnée ailleurs, mais je pense que cela résume assez bien les différences données ici :

Fonctionnel la programmation se concentre sur expressions

De procédure la programmation se concentre sur déclarations

Les expressions ont des valeurs.Un programme fonctionnel est une expression dont la valeur est une séquence d'instructions que l'ordinateur doit exécuter.

Les instructions n'ont pas de valeurs et modifient à la place l'état d'une machine conceptuelle.

Dans un langage purement fonctionnel, il n'y aurait pas d'instructions, dans le sens où il n'y a aucun moyen de manipuler l'état (ils pourraient toujours avoir une construction syntaxique nommée "instruction", mais à moins qu'elle ne manipule l'état, je ne l'appellerais pas une instruction dans ce sens ).Dans un langage purement procédural il n’y aurait pas d’expressions, tout serait une instruction qui manipule l’état de la machine.

Haskell serait un exemple de langage purement fonctionnel car il n'existe aucun moyen de manipuler l'état.Le code machine serait un exemple de langage purement procédural car tout dans un programme est une instruction qui manipule l'état des registres et de la mémoire de la machine.

Ce qui est déroutant, c'est que la grande majorité des langages de programmation contiennent les deux expressions et énoncés, permettant de mélanger les paradigmes.Les langages peuvent être classés comme plus fonctionnels ou plus procéduraux en fonction de la mesure dans laquelle ils encouragent l'utilisation d'instructions plutôt que d'expressions.

Par exemple, C serait plus fonctionnel que COBOL car un appel de fonction est une expression, alors que l'appel d'un sous-programme en COBOL est une instruction (qui manipule l'état des variables partagées et ne renvoie pas de valeur).Python serait plus fonctionnel que C car il vous permet d'exprimer la logique conditionnelle sous forme d'expression en utilisant l'évaluation de court-circuit (test && path1 || path2 par opposition aux instructions if).Scheme serait plus fonctionnel que Python car tout dans Scheme est une expression.

Vous pouvez toujours écrire dans un style fonctionnel dans un langage qui encourage le paradigme procédural et vice versa.C'est juste plus difficile et/ou plus gênant d'écrire dans un paradigme qui n'est pas encouragé par la langue.

En informatique, la programmation fonctionnelle est un paradigme de programmation qui traite le calcul comme l'évaluation de fonctions mathématiques et évite les données d'état et mutables.Il met l'accent sur l'application des fonctions, contrairement au style de programmation procédurale qui met l'accent sur les changements d'état.

Je crois que la programmation procédurale/fonctionnelle/objective concerne la façon d'aborder un problème.

Le premier style planifierait tout par étapes et résoudrait le problème en mettant en œuvre une étape (une procédure) à la fois.D'un autre côté, la programmation fonctionnelle mettrait l'accent sur l'approche diviser pour régner, où le problème est divisé en sous-problèmes, puis chaque sous-problème est résolu (en créant une fonction pour résoudre ce sous-problème) et les résultats sont combinés pour créer la réponse à l’ensemble du problème.Enfin, la programmation objective imiterait le monde réel en créant un mini-monde à l'intérieur de l'ordinateur avec de nombreux objets, chacun ayant des caractéristiques (quelque peu) uniques et interagissant avec les autres.De ces interactions, le résultat émergerait.

Chaque style de programmation possède ses propres avantages et faiblesses.Par conséquent, faire quelque chose comme de la « programmation pure » (c.-à-d.purement procédural - personne ne fait ça d'ailleurs, ce qui est un peu bizarre - ou purement fonctionnel ou purement objectif) est très difficile, voire impossible, sauf quelques problèmes élémentaires spécialement conçus pour démontrer l'avantage d'un style de programmation (d'où, nous appelons ceux qui aiment la pureté "weenie" :D).

Ensuite, à partir de ces styles, nous avons des langages de programmation conçus pour être optimisés pour chaque style.Par exemple, Assembly est avant tout une question de procédure.D'accord, la plupart des premiers langages sont procéduraux, pas seulement Asm, comme C, Pascal (et Fortran, j'ai entendu).Ensuite, nous avons tous les célèbres Java à l'école objective (en fait, Java et C# sont également dans une classe appelée « orientée vers l'argent », mais cela fait l'objet d'une autre discussion).Smalltalk est également objectif.Dans une école fonctionnelle, nous aurions des familles Lisp et ML « presque fonctionnelles » (certains les considéraient comme impures) et de nombreux Haskell, Erlang, etc. « purement fonctionnels ».D'ailleurs, il existe de nombreux langages généraux comme Perl, Python, Ruby.

Pour développer le commentaire de Konrad :

En conséquence, un programme purement fonctionnel donne toujours la même valeur pour une entrée, et l'ordre d'évaluation n'est pas bien défini ;

De ce fait, le code fonctionnel est généralement plus facile à paralléliser.Puisqu'il n'y a (généralement) aucun effet secondaire des fonctions et qu'elles agissent (généralement) simplement sur leurs arguments, de nombreux problèmes de concurrence disparaissent.

La programmation fonctionnelle est également utilisée lorsque vous devez être capable de prouver votre code est correct.C'est beaucoup plus difficile à faire avec la programmation procédurale (pas facile avec la programmation fonctionnelle, mais quand même plus facile).

Clause de non-responsabilité:Je n'ai pas utilisé la programmation fonctionnelle depuis des années et je n'ai commencé à l'examiner que récemment, donc je n'ai peut-être pas tout à fait raison ici.:)

Une chose que je n'avais pas vraiment soulignée ici est que les langages fonctionnels modernes tels que Haskell se concentrent davantage sur les fonctions de première classe pour le contrôle de flux que sur la récursivité explicite.Vous n'avez pas besoin de définir factoriellement de manière récursive dans Haskell, comme cela a été fait ci-dessus.Je pense à quelque chose comme

fac n = foldr (*) 1 [1..n]

est une construction parfaitement idiomatique, et beaucoup plus proche dans son esprit de l'utilisation d'une boucle que de l'utilisation d'une récursivité explicite.

Une programmation fonctionnelle est identique à une programmation procédurale dans laquelle des variables globales sont pas utilisé.

Les langages procéduraux ont tendance à garder une trace de l'état (à l'aide de variables) et à s'exécuter sous la forme d'une séquence d'étapes.Les langages purement fonctionnels ne gardent pas de trace de l'état, utilisent des valeurs immuables et ont tendance à s'exécuter comme une série de dépendances.Dans de nombreux cas, l'état de la pile d'appels contiendra des informations équivalentes à celles qui seraient stockées dans les variables d'état du code procédural.

La récursivité est un exemple classique de programmation de style fonctionnel.

Konrad a dit :

Par conséquent, un programme purement fonctionnel donne toujours la même valeur pour un intrant, et l'ordre d'évaluation n'est pas bien défini;ce qui signifie que des valeurs incertaines comme l'entrée de l'utilisateur ou les valeurs aléatoires sont difficiles à modéliser dans des langages purement fonctionnels.

L'ordre d'évaluation dans un programme purement fonctionnel peut être difficile à raisonner (surtout avec paresse) ou même sans importance, mais je pense que dire qu'il n'est pas bien défini donne l'impression que vous ne pouvez pas savoir si votre programme fonctionne. travailler du tout !

Une meilleure explication serait peut-être que le flux de contrôle dans les programmes fonctionnels est basé sur le moment où la valeur des arguments d'une fonction est nécessaire.La bonne chose à ce sujet est que dans les programmes bien écrits, l'état devient explicite :chaque fonction répertorie ses entrées sous forme de paramètres plutôt que arbitrairement munging état mondial.Donc à un certain niveau, il est plus facile de raisonner sur l'ordre d'évaluation par rapport à une fonction à la fois.Chaque fonction peut ignorer le reste de l’univers et se concentrer sur ce qu’elle doit faire.Lorsqu’elles sont combinées, les fonctions sont garanties de fonctionner de la même manière[1] que si elles étaient isolées.

...les valeurs incertaines comme l'entrée de l'utilisateur ou les valeurs aléatoires sont difficiles à modéliser les langues fonctionnelles.

La solution au problème de saisie dans les programmes purement fonctionnels consiste à intégrer un langage impératif comme DSL en utilisant une abstraction suffisamment puissante.Dans les langages impératifs (ou non purement fonctionnels), cela n'est pas nécessaire car vous pouvez "tricher" et transmettre implicitement un état et l'ordre d'évaluation est explicite (que cela vous plaise ou non).En raison de cette "tricherie" et de l'évaluation forcée de tous les paramètres de chaque fonction, dans les langages impératifs 1) vous perdez la possibilité de créer vos propres mécanismes de flux de contrôle (sans macros), 2) le code n'est pas intrinsèquement thread-safe et/ou parallélisable par défaut, 3) et implémenter quelque chose comme annuler (voyage dans le temps) demande un travail minutieux (un programmeur impératif doit stocker une recette pour récupérer les anciennes valeurs !), alors que la programmation fonctionnelle pure vous achète toutes ces choses - et quelques autres, je peux avez oublié - "gratuitement".

J'espère que cela ne ressemble pas à du fanatisme, je voulais juste ajouter un peu de perspective.La programmation impérative et en particulier la programmation à paradigmes mixtes dans des langages puissants comme C# 3.0 restent des moyens totalement efficaces pour faire avancer les choses et il n'y a pas de solution miracle.

[1] ...sauf éventuellement en ce qui concerne l'utilisation de la mémoire (cf.Foldl et Foldl' en Haskell).

Pour développer le commentaire de Konrad :

et l'ordre d'évaluation n'est pas bien définies

Certains langages fonctionnels ont ce qu'on appelle une évaluation paresseuse.Ce qui signifie qu'une fonction n'est exécutée que lorsque la valeur est nécessaire.Jusque-là, c'est la fonction elle-même qui est transmise.

Les langages procéduraux sont l'étape 1, l'étape 2, l'étape 3...si à l'étape 2 vous dites ajouter 2 + 2, il le fait immédiatement.Dans une évaluation paresseuse, vous diriez ajouter 2 + 2, mais si le résultat n'est jamais utilisé, il ne fait jamais l'addition.

Si vous en avez l'occasion, je vous recommande d'obtenir une copie de Lisp/Scheme et d'y réaliser quelques projets.La plupart des idées qui sont récemment devenues des tendances ont été exprimées en Lisp il y a des décennies :programmation fonctionnelle, continuations (comme fermetures), garbage collection, même XML.

Ce serait donc un bon moyen de prendre une longueur d'avance sur toutes ces idées actuelles, et quelques autres encore, comme le calcul symbolique.

Vous devez savoir à quoi sert la programmation fonctionnelle et à quoi elle ne sert pas.Ce n'est pas bon pour tout.Certains problèmes s’expriment mieux en termes d’effets secondaires, où la même question donne des réponses différentes selon le moment où elle est posée.

@Creighton :

Dans Haskell, il existe une fonction de bibliothèque appelée produit:

prouduct list = foldr 1 (*) list

ou simplement:

product = foldr 1 (*)

donc la factorielle "idiomatique"

fac n = foldr 1 (*)  [1..n]

serait simplement

fac n = product [1..n]

Programmation fonctionnelle

num = 1 
def function_to_add_one(num):
    num += 1
    return num


function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)

#Final Output: 2

Programmation procédurale

num = 1 
def procedure_to_add_one():
    global num
    num += 1
    return num


procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()

#Final Output: 6

function_to_add_one est une fonction

procedure_to_add_one est une procédure

Même si vous exécutez le fonction cinq fois, à chaque fois il reviendra 2

Si vous exécutez le procédure cinq fois, à la fin de la cinquième manche, cela vous donnera 6.

La programmation procédurale divise les séquences d'instructions et de constructions conditionnelles en blocs distincts appelés procédures paramétrées sur des arguments qui sont des valeurs (non fonctionnelles).

La programmation fonctionnelle est la même, sauf que les fonctions sont des valeurs de première classe, elles peuvent donc être transmises comme arguments à d'autres fonctions et renvoyées comme résultats d'appels de fonction.

Notez que la programmation fonctionnelle est une généralisation de la programmation procédurale dans cette interprétation.Cependant, une minorité interprète « programmation fonctionnelle » comme signifiant sans effets secondaires, ce qui est assez différent mais non pertinent pour tous les principaux langages fonctionnels à l'exception de Haskell.

Pour comprendre la différence, il faut comprendre que le paradigme « du parrain » de la programmation procédurale et fonctionnelle est le programmation impérative.

Fondamentalement, la programmation procédurale n'est qu'une façon de structurer des programmes impératifs dans lesquels la principale méthode d'abstraction est la « procédure ». (ou "fonction" dans certains langages de programmation).Même la programmation orientée objet n'est qu'une autre façon de structurer un programme impératif, où l'état est encapsulé dans des objets, devenant un objet avec un « état actuel », et cet objet possède un ensemble de fonctions, de méthodes et d'autres éléments qui vous permettent de le programmeur manipule ou met à jour l’état.

Maintenant, en ce qui concerne la programmation fonctionnelle, le essentiel Son approche consiste à identifier les valeurs à adopter et la manière dont ces valeurs doivent être transférées.(il n'y a donc pas d'état ni de données mutables car il prend les fonctions comme valeurs de première classe et les transmet comme paramètres à d'autres fonctions).

PS :comprendre chaque paradigme de programmation est utilisé devrait clarifier les différences entre eux tous.

PSS :En fin de compte, les paradigmes de programmation ne sont que des approches différentes pour résoudre des problèmes.

PSS : ce La réponse de Quora a une excellente explication.

Aucune des réponses ici ne montre une programmation fonctionnelle idiomatique.La réponse factorielle récursive est idéale pour représenter la récursion dans FP, mais la majorité du code n'est pas récursive donc je ne pense pas que cette réponse soit entièrement représentative.

Supposons que vous ayez un tableau de chaînes et que chaque chaîne représente un entier tel que « 5 » ou « -200 ».Vous souhaitez vérifier ce tableau d'entrée de chaînes par rapport à votre scénario de test interne (à l'aide d'une comparaison d'entiers).Les deux solutions sont présentées ci-dessous

De procédure

arr_equal(a : [Int], b : [Str]) -> Bool {
    if(a.len != b.len) {
        return false;
    }

    bool ret = true;
    for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
        int a_int = a[i];
        int b_int = parseInt(b[i]);
        ret &= a_int == b_int;  
    }
    return ret;
}

Fonctionnel

eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization

arr_equal(a : [Int], b : [Str]) -> Bool =
    zip(a, b.map(toInt)) # Combines into [Int, Int]
   .map(eq)
   .reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value

Alors que les langages fonctionnels purs sont généralement des langages de recherche (comme le monde réel aime les effets secondaires gratuits), les langages procéduraux du monde réel utiliseront la syntaxe fonctionnelle beaucoup plus simple, le cas échéant.

Ceci est généralement implémenté avec une bibliothèque externe comme Lodash, ou disponible intégré avec des langages plus récents comme Rouiller.Le gros du travail de la programmation fonctionnelle se fait avec des fonctions/concepts comme map, filter, reduce, currying, partial, dont vous pouvez consulter les trois derniers pour mieux comprendre.

Addenda

Afin d'être utilisé dans la nature, le compilateur devra normalement trouver comment convertir la version fonctionnelle en version procédurale en interne, car la surcharge des appels de fonction est trop élevée.Les cas récursifs tels que la factorielle illustrée utiliseront des astuces telles que appel de queue pour supprimer l’utilisation de la mémoire O(n).Le fait qu'il n'y ait pas d'effets secondaires permet aux compilateurs fonctionnels d'implémenter le && ret optimisation même lorsque le .reduce se fait en dernier.L'utilisation de Lodash dans JS ne permet évidemment aucune optimisation, c'est donc un impact sur les performances (ce qui n'est généralement pas un problème avec le développement Web).Des langages comme Rust s'optimiseront en interne (et auront des fonctions telles que try_fold aider && ret optimisation).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top