web-dev-qa-db-fra.com

Pièges / inconvénients de la programmation fonctionnelle

Quand ne voudriez-vous PAS utiliser la programmation fonctionnelle? À quoi n'est-il pas si bon?

Je recherche plutôt les inconvénients du paradigme dans son ensemble, pas des choses comme "pas largement utilisé", ou "pas de bon débogueur disponible". Ces réponses sont peut-être correctes pour le moment, mais elles concernent FP étant un nouveau concept (un problème inévitable) et non des qualités inhérentes.

En relation:

67
Gordon Gustafson

Il m'est difficile de penser à de nombreux inconvénients de la programmation fonctionnelle. Là encore, je suis un ancien président de la Conférence internationale sur la programmation fonctionnelle, vous pouvez donc sans risque supposer que je suis partiale.

Je pense que les principaux inconvénients sont liés à l'isolement et aux barrières à l'entrée. Apprendre à écrire bien les programmes fonctionnels signifient apprendre à penser différemment, et pour bien le faire, il faut investir beaucoup de temps et d'efforts . Il est difficile d'apprendre sans professeur. Ces propriétés entraînent certains inconvénients:

  • Il est probable qu'un programme fonctionnel écrit par un nouveau venu soit inutilement lent - plus probable qu'un programme C écrit par un nouveau venu en C. Par contre, il est à peu près tout aussi probable qu'un programme C++ écrit par un nouveau venu sera inutilement lent. (Toutes ces fonctionnalités brillantes ...)

    Généralement, les experts n'ont aucune difficulté à écrire des programmes fonctionnels rapides; et en fait, certains des programmes parallèles les plus performants sur les processeurs 8 et 16 cœurs sont désormais écrits en Haskell .

  • Il est plus probable que quelqu'un qui commence la programmation fonctionnelle abandonne avant de réaliser les gains de productivité promis que quelqu'un qui commence, par exemple, Python ou Visual Basic. Il n'y a tout simplement pas autant de support sous la forme de livres et outils de développement.

  • Il y a moins de gens à qui parler. Stackoverflow est un bon exemple; relativement peu de programmeurs Haskell visitent le site régulièrement (même si cela tient en partie au fait que les programmeurs Haskell ont leurs propres forums animés qui sont beaucoup plus anciens et mieux établis que Stackoverflow).

    Il est également vrai que vous ne pouvez pas parler très facilement à votre voisin, car les concepts de programmation fonctionnelle sont plus difficiles à enseigner et à apprendre que les concepts orientés objet derrière des langages tels que Smalltalk, Ruby et C++. Et aussi, la communauté orientée objet a passé des années à développer de bonnes explications pour ce qu'elle fait, tandis que la communauté de programmation fonctionnelle semble penser que leur contenu est évidemment génial et ne nécessite pas de métaphores ou de vocabulaire spécial pour l'explication. (Ils ont tort. J'attends toujours le premier grand livre Patterns de conception fonctionnelle.)

  • Un inconvénient bien connu de paresseux programmation fonctionnelle (s'applique à Haskell ou Clean mais pas à ML ou Scheme ou Clojure) est que il est très difficile de prédire les coûts de temps et d'espace pour évaluer un programme fonctionnel paresseux - même les experts ne peuvent pas le faire. Ce problème est fondamental pour le paradigme et ne disparaîtra pas. Il existe d'excellents outils pour découvrir le comportement du temps et de l'espace post facto, mais pour les utiliser efficacement, vous devez déjà être expert.

46
Norman Ramsey

Je pense que les conneries entourant les langages fonctionnels sont le plus gros problème avec la programmation fonctionnelle. Quand j'ai commencé à utiliser la programmation fonctionnelle dans la colère, un gros obstacle pour moi était de comprendre pourquoi de nombreux arguments très avancés avancés par la communauté LISP (par exemple sur les macros et la syntaxe homoiconique) étaient erronés. Aujourd'hui, je vois beaucoup de gens être trompés par la communauté Haskell en ce qui concerne la programmation parallèle.

En fait, vous n'avez pas besoin de regarder plus loin que ce fil pour en voir:

"En général, les experts n’ont aucune difficulté à écrire des programmes fonctionnels rapides. En fait, certains des programmes parallèles les plus performants sur les processeurs 8 et 16 cœurs sont désormais écrits en Haskell."

Des déclarations comme celle-ci pourraient vous donner l'impression que les experts choisissent Haskell parce qu'il peut être si bon pour le parallélisme, mais la vérité est que les performances de Haskell sont nulles et le mythe selon lequel Haskell est bon pour le parallélisme multicœur est perpétué par les chercheurs de Haskell qui ont peu ou pas de connaissances sur le parallélisme. qui évitent le véritable examen par les pairs en ne publiant que dans la zone de confort des revues et conférences sous le contrôle de leur propre clique. Haskell est invisible dans le monde réel parallèle/multicœur/HPC précisément parce qu'il aspire à la programmation parallèle.

Plus précisément, le véritable défi de la programmation multicœur consiste à tirer parti des caches CPU pour s'assurer que les cœurs ne manquent pas de données, un problème qui n'a jamais été résolu dans le contexte de Haskell. Le groupe de Charles Leiserson à MIT a fait un excellent travail pour expliquer et résoudre ce problème en utilisant leur propre langage Cilk qui est devenu l'épine dorsale de la programmation parallèle dans le monde réel pour les multicœurs dans Intel TBB et Microsoft's TPL dans .NET 4. Il y a une superbe description de la façon dont cette technique peut être utilisée pour écrire un code élégant de haut niveau - impératif qui se compile en un code haute performance évolutif dans l'article de 2008 La complexité du cache des algorithmes inconscients du cache multithread . Je l'ai expliqué dans ma critique de certaines des recherches de pointe sur Parallel Haskell.

Cela laisse un gros point d'interrogation sur le paradigme de programmation purement fonctionnel. C'est le prix à payer pour l'abstraction du temps et de l'espace, qui a toujours été la principale motivation derrière ce paradigme déclaratif.

EDIT: Texas Multicore Technologies a également récemment trouvé Haskell décevant dans le contexte du parallélisme multicœur .

30
Jon Harrop

Un gros inconvénient de la programmation fonctionnelle est qu'au niveau théorique, elle ne correspond pas au matériel ainsi qu'à la plupart des langages impératifs. (C'est le revers de l'une de ses forces évidentes, être capable d'exprimer quoi vous voulez faire plutôt que comment vous voulez que l'ordinateur le fasse.)

Par exemple, la programmation fonctionnelle fait un usage intensif de la récursivité. C'est très bien en calcul lambda pur car la "pile" des mathématiques est illimitée. Bien sûr, sur du vrai matériel, la pile est très finie. Récursivement naïvement sur un grand ensemble de données peut faire exploser votre programme. La plupart des langages fonctionnels optimisent la récursivité de la queue pour que cela ne se produise pas, mais rendre un algorithme récursif de la queue peut vous forcer à faire de la gymnastique de code plutôt belle (par exemple, une fonction de carte récursive de la queue crée une liste en arrière ou doit créer une différence , il doit donc faire un travail supplémentaire pour revenir à une liste mappée normale dans le bon ordre par rapport à la version non-récursive).

(Merci à Jared Updike pour la suggestion de liste de différences.)

29
Chuck

Si votre langage ne fournit pas de bons mécanismes pour déterminer le comportement des états/exceptions via votre programme (par exemple, les sucres de syntaxe pour les liaisons monadiques), alors toute tâche impliquant des états/exceptions devient une corvée. (Même avec ces sucres, certaines personnes pourraient trouver plus difficile de gérer les états/exceptions dans la PF.)

Les idiomes fonctionnels font souvent beaucoup d'inversion de contrôle ou de paresse, ce qui a souvent un impact négatif sur le débogage (à l'aide d'un débogueur). (Ceci est quelque peu compensé par FP étant beaucoup moins sujet aux erreurs en raison de l'immuabilité/transparence référentielle, ce qui signifie que vous devrez déboguer moins souvent.)

23
Brian

Philip Wadler a écrit un article à ce sujet (intitulé Pourquoi personne n'utilise de langages de programmation fonctionnels) et a évoqué les pièges pratiques empêchant les gens d'utiliser les langues FP:

Mise à jour: ancien lien inaccessible pour ceux qui ont un accès ACM:

13
Jared Updike

Mis à part les problèmes de vitesse ou d'adoption et la résolution d'un problème plus fondamental, j'ai entendu dire qu'avec la programmation fonctionnelle, il est très facile d'ajouter de nouvelles fonctions pour les types de données existants, mais il est "difficile" d'ajouter de nouveaux types de données. Considérer:

(Écrit en SMLnj. Veuillez également excuser l'exemple quelque peu artificiel.)

datatype Animal = Dog | Cat;

fun happyNoise(Dog) = "pant pant"
  | happyNoise(Cat) = "purrrr";

fun excitedNoise(Dog) = "bark!"
  | excitedNoise(Cat) = "meow!";

Je peux ajouter très rapidement ce qui suit:

fun angryNoise(Dog) = "grrrrrr"
  | angryNoise(Cat) = "hisssss";

Cependant, si j'ajoute un nouveau type à Animal, je dois passer par chaque fonction pour en ajouter le support:

datatype Animal = Dog | Cat | Chicken;

fun happyNoise(Dog) = "pant pant"
  | happyNoise(Cat) = "purrrr"
  | happyNoise(Chicken) = "cluck cluck";

fun excitedNoise(Dog) = "bark!"
  | excitedNoise(Cat) = "meow!"
  | excitedNoise(Chicken) = "cock-a-doodle-doo!";

fun angryNoise(Dog) = "grrrrrr"
  | angryNoise(Cat) = "hisssss"
  | angryNoise(Chicken) = "squaaaawk!";

Notez, cependant, que l'exact opposé est vrai pour les langages orientés objet. Il est très facile d'ajouter une nouvelle sous-classe à une classe abstraite, mais cela peut être fastidieux si vous souhaitez ajouter une nouvelle méthode abstraite à la classe/interface abstraite pour toutes les sous-classes à implémenter.

8
Ben Torell

Je voulais juste raconter une anecdote parce que j'apprends Haskell en ce moment même où nous parlons. J'apprends Haskell parce que l'idée de séparer les fonctions des actions me plaît et il y a des théories vraiment sexy derrière la parallélisation implicite en raison de l'isolement des fonctions pures des fonctions non pures.

Cela fait trois jours que j'apprends la classe de fonctions Fold. Fold semble avoir une application très simple: prendre une liste et la réduire à une seule valeur. Haskell implémente un foldl et foldr pour cela. Les deux fonctions ont des implémentations massivement différentes. Il existe une autre implémentation de foldl, appelée foldl'. En plus de cela, il existe une version avec une syntaxe légèrement différente appelée foldr1 Et foldl1 Avec des valeurs initiales différentes. Dont il existe une implémentation correspondante de foldl1' Pour foldl1. Comme si tout cela n'était pas époustouflant, les fonctions que fold[lr].* Nécessitent comme arguments et utilisent en interne dans la réduction ont deux signatures distinctes, une seule variante fonctionne sur des listes infinies (r), et une seule d'entre elles est exécuté en mémoire constante (si je comprends bien (L) car il nécessite seulement un redex). Comprendre pourquoi foldr peut fonctionner sur des listes infinies nécessite au moins une compréhension décente des langages paresseux et le détail mineur que toutes les fonctions ne forceront pas l'évaluation du deuxième argument. Les graphiques en ligne pour ces fonctions sont déroutants pour quelqu'un qui ne les a jamais vus à l'université. Il n'y a pas d'équivalent perldoc. Je ne trouve pas une seule description de ce que font les fonctions du prélude de Haskell. Le prélude est une sorte de distribution préchargée fournie avec le noyau. Ma meilleure ressource est vraiment un gars que je n'ai jamais rencontré (Cale) qui m'aide à un coût énorme pour son propre temps.

Oh, et fold n'a pas à réduire la liste à un scalaire de type non-liste, la fonction d'identité pour les listes peut être écrite foldr (:) [] [1,2,3,4] (met en évidence que vous pouvez accumuler dans une liste).

/ moi revient à la lecture.

3
Evan Carroll

Voici quelques problèmes que j'ai rencontrés:

  1. La plupart des gens trouvent la programmation fonctionnelle difficile à comprendre. Cela signifie qu'il sera probablement plus difficile pour vous d'écrire du code fonctionnel, et il sera presque certainement plus difficile pour quelqu'un d'autre de le récupérer.
  2. Les langages de programmation fonctionnels sont généralement plus lents qu'un langage comme c le serait. Cela devient de moins en moins un problème avec le temps (car les ordinateurs deviennent plus rapides et les compilateurs deviennent plus intelligents)
  3. N'étant pas aussi répandu que leurs homologues impératifs, il peut être difficile de trouver des bibliothèques et des exemples de problèmes de programmation courants. (Par exemple, il est presque toujours plus facile de trouver quelque chose pour Python, alors c'est pour Haskell)
  4. Il y a un manque d'outils, en particulier pour le débogage. Ce n'est certainement pas aussi simple que d'ouvrir Visual Studio pour C # ou Eclipse pour Java.
2
Caleb

En détournant les détails des implémentations spécifiques de la programmation fonctionnelle, je vois deux problèmes clés:

  1. Il semble relativement rare qu'il soit pratique de choisir un modèle fonctionnel d'un problème réel plutôt qu'un impératif. Lorsque le domaine problématique est impératif, l'utilisation d'un langage avec cette caractéristique est un choix naturel et raisonnable (car il est généralement conseillé de minimiser la distance entre la spécification et l'implémentation dans le cadre de la réduction du nombre de bogues subtils). Oui, cela peut être surmonté par un codeur suffisamment intelligent, mais si vous avez besoin de Rock Star Coders pour la tâche, c'est parce que c'est trop dur.

  2. Pour une raison que je n'ai jamais vraiment comprise, les langages de programmation fonctionnels (ou peut-être leurs implémentations ou communautés?) Sont beaucoup plus susceptibles de vouloir tout avoir dans leur langage. Les bibliothèques écrites dans d'autres langues sont beaucoup moins utilisées. Si quelqu'un d'autre a une implémentation particulièrement bonne d'une opération complexe, il est beaucoup plus logique de l'utiliser au lieu de créer la vôtre. Je soupçonne que cela est en partie une conséquence de l'utilisation d'exécutions complexes qui rendent la gestion du code étranger (et surtout le faire efficacement) plutôt difficile. Je serais ravi d'avoir tort sur ce point.

Je suppose que ces deux éléments renvoient à un manque général de pragmatisme causé par une programmation fonctionnelle beaucoup plus fortement utilisée par les chercheurs en programmation que par les codeurs courants. Un bon outil peut permettre à un expert d'accomplir de grandes choses, mais un excellent outil est celui qui permet à l'homme ordinaire d'approcher ce qu'un expert peut faire normalement, car c'est de loin la tâche la plus difficile.

0
Donal Fellows