À la page 839 de la deuxième édition, Steve McConnell discute de toutes les façons dont les programmeurs peuvent "conquérir la complexité" dans les grands programmes. Ses conseils culminent avec cette déclaration:
"La programmation orientée objet fournit un niveau d'abstraction qui s'applique aux algorithmes et aux données en même temps , une sorte d'abstraction que la décomposition fonctionnelle à elle seule ne fournissait pas."
Couplé à sa conclusion que "réduire la complexité est sans doute la clé la plus importante pour être un programmeur efficace" (même page), cela semble à peu près un défi pour la programmation fonctionnelle.
Le débat entre FP et OO est souvent encadré par les partisans de FP autour des questions de complexité qui découlent spécifiquement des défis de la concurrence ou de la parallélisation. Mais la concurrence n'est certainement pas le seul type de complexité que les programmeurs de logiciels doivent vaincre. Peut-être que le fait de se concentrer sur la réduction d'une sorte de complexité l'augmente considérablement dans d'autres dimensions, de sorte que dans de nombreux cas, le gain ne vaut pas le coût.
Si nous déplacions les termes de la comparaison entre FP et OO de questions particulières comme la concurrence ou la réutilisabilité à la gestion de la complexité globale, à quoi ressemblerait ce débat?
MODIFIER
Le contraste que je voulais souligner est que OO semble encapsuler et abstraire de la complexité des données et des algorithmes, tandis que la programmation fonctionnelle semble encourager à laisser les détails d'implémentation des structures de données plus "exposés" tout au long du programme.
Voir, par exemple, Stuart Halloway (un partisan de Clojure FP) disant ici que «la sur-spécification des types de données» est «une conséquence négative du style OO idiomatique» et privilégiant la conceptualisation d'un carnet d'adresses comme un simple vecteur ou carte au lieu d'un objet OO plus riche avec des propriétés et des méthodes supplémentaires (non vectorielles et non maplike). (De plus, les partisans de l'OO et de la conception pilotée par domaine peuvent dire que l'exposition d'un carnet d'adresses en tant que vecteur ou carte surexpose les données encapsulées à des méthodes qui ne sont pas pertinentes ou même dangereuses du point de vue du domaine).
Réponses:
Gardez à l'esprit que le livre a été écrit il y a plus de 20 ans. Pour les programmeurs professionnels de l'époque, la PF n'existait pas - c'était entièrement du domaine des universitaires et des chercheurs.
Nous devons cadrer la «décomposition fonctionnelle» dans le contexte approprié du travail. L'auteur ne fait pas référence à la programmation fonctionnelle. Nous devons relier cela à la "programmation structurée" et au
GOTO
gâchis rempli qui l'a précédé. Si votre point de référence est un ancien FORTRAN / COBOL / BASIC qui n'avait pas de fonctions (peut-être, si vous étiez chanceux, vous obtiendriez un seul niveau de GOSUB) et que toutes vos variables sont globales, pouvant décomposer votre programme en couches de fonctions est une aubaine majeure.La POO est un raffinement supplémentaire de ce type de «décomposition fonctionnelle». Non seulement vous pouvez regrouper des instructions dans des fonctions, mais vous pouvez également regrouper des fonctions connexes avec les données sur lesquelles elles travaillent. Le résultat est un morceau de code clairement défini que vous pouvez regarder et comprendre (idéalement) sans avoir à courir tout autour de votre base de code pour trouver quoi d'autre pourrait fonctionner sur vos données.
la source
J'imagine que les partisans de la programmation fonctionnelle diraient que la plupart des langages FP fournissent plus de moyens d'abstraction que la «décomposition fonctionnelle seule» et permettent en fait des moyens d'abstractions comparables en puissance à ceux des langages orientés objet. Par exemple, on pourrait citer les classes de types de Haskell ou les modules d'ordre supérieur de ML comme tels moyens d'abstractions. Ainsi, l'énoncé (qui, j'en suis sûr, concernait l'orientation des objets par rapport à la programmation procédurale, pas la programmation fonctionnelle) ne s'applique pas à eux.
Il convient également de souligner que FP et OOP sont des concepts orthogonaux et ne s'excluent pas mutuellement. Il n'est donc pas logique de les comparer les uns aux autres. Vous pourriez très bien comparer "POO impérative" (par exemple Java) et "POO fonctionnelle" (par exemple Scala), mais l'énoncé que vous avez cité ne s'appliquerait pas à cette comparaison.
la source
Je trouve la programmation fonctionnelle extrêmement utile pour gérer la complexité. Cependant, vous avez tendance à penser la complexité d'une manière différente, en la définissant comme des fonctions qui agissent sur des données immuables à différents niveaux plutôt que de l'encapsulation au sens de la POO.
Par exemple, j'ai récemment écrit un jeu dans Clojure, et l'état entier du jeu a été défini dans une seule structure de données immuable:
Et la boucle de jeu principale pourrait être définie comme l'application de certaines fonctions pures à l'état du jeu dans une boucle:
La fonction clé appelée est
update-game
, qui exécute une étape de simulation en fonction d'un état de jeu précédent et d'une certaine entrée utilisateur, et renvoie le nouvel état de jeu.Alors, où est la complexité? À mon avis, il a été assez bien géré:
La POO peut également gérer la complexité grâce à l'encapsulation, mais si vous la comparez à la POO, la fonctionnalité présente des avantages très importants:
Enfin, pour les personnes qui souhaitent plus d'informations sur la gestion de la complexité dans les langages fonctionnels vs OOP, je recommande fortement la vidéo du discours d'ouverture de Rich Hickey Simple Made Easy (filmé lors de la conférence sur la technologie Strange Loop )
la source
La décomposition fonctionnelle à elle seule ne suffit pas pour créer une sorte d'algorithme ou de programme: vous devez également représenter les données. Je pense que la déclaration ci-dessus suppose implicitement (ou du moins elle peut être comprise comme) que les "données" dans le cas fonctionnel sont du type le plus rudimentaire: juste des listes de symboles et rien d'autre. La programmation dans un tel langage n'est évidemment pas très pratique. Cependant, beaucoup, en particulier les langages nouveaux et modernes, fonctionnels (ou multiparadigm), tels que Clojure, offrent des structures de données riches: non seulement des listes, mais aussi des chaînes, des vecteurs, des cartes et des ensembles, des enregistrements, des structures - et des objets! - avec métadonnées et polymorphisme.
L'énorme succès pratique des abstractions OO est difficilement contestable. Mais est-ce le dernier mot? Comme vous l'avez écrit, les problèmes de concurrence sont déjà la principale difficulté, et l'OO classique ne contient aucune idée de la concurrence. En conséquence, les solutions OO de facto pour faire face à la concurrence ne sont que du ruban adhésif superposé: fonctionne, mais il est facile à visser, prend une quantité considérable de ressources cérébrales loin de la tâche essentielle à accomplir, et il n'évolue pas bien. Il est peut-être possible de tirer le meilleur parti de nombreux mondes. C'est ce que poursuivent les langues multiparadigm modernes.
la source
L'état mutable est à l'origine de la plupart des complexités et des problèmes liés à la programmation et à la conception de logiciels / systèmes.
OO embrasse l'état mutable. FP abhorre l'état mutable.
Les deux OO et FP ont leurs utilisations et leurs points faibles. Choisis sagement. Et rappelez-vous l'adage: "Les fermetures sont des objets du pauvre. Les objets sont la fermeture du pauvre."
la source
La programmation fonctionnelle peut avoir des objets, mais ces objets ont tendance à être immuables. Les fonctions pures (fonctions sans effets secondaires) opèrent alors sur ces structures de données. Il est possible de créer des objets immuables dans des langages de programmation orientés objet, mais ils n'ont pas été conçus pour le faire et ce n'est pas ainsi qu'ils ont tendance à être utilisés. Cela rend difficile de raisonner sur les programmes orientés objet.
Prenons un exemple très simple. Supposons qu'Oracle ait décidé que Java Strings devrait avoir une méthode inverse et que vous avez écrit le code suivant.
à quoi évalue la dernière ligne? Vous avez besoin d'une connaissance spéciale de la classe String pour savoir que cela serait faux.
Et si je créais ma propre classe WuHoString
Il est impossible de savoir à quoi correspond la dernière ligne.
Dans un style de programmation fonctionnelle, il s'écrirait plus comme suit:
et ça devrait être vrai.
Si 1 fonction dans l'une des classes les plus élémentaires est si difficile à raisonner, alors on se demande si l'introduction de cette idée d'objets mutables a augmenté ou diminué la complexité.
De toute évidence, il existe toutes sortes de définitions de ce qui constitue orienté objet et de ce que signifie être fonctionnel et ce que signifie avoir les deux. Pour moi, vous pouvez avoir un "style de programmation fonctionnel" en langage qui n'a pas des choses comme des fonctions de première classe mais d'autres langages sont faits pour cela.
la source
Je pense que dans la plupart des cas, l'abstraction OOP classique ne couvre pas la complexité de la concurrence. Par conséquent, la POO (par sa signification d'origine) n'exclut pas FP, et c'est pourquoi nous voyons des choses comme scala.
la source
La réponse dépend de la langue. Les lisps, par exemple, ont vraiment bien compris que le code est des données - les algorithmes que vous écrivez ne sont en fait que des listes lisp! Vous stockez les données de la même manière que vous écrivez le programme. Cette abstraction est à la fois plus simple et plus approfondie que la POO et vous permet de faire des choses vraiment intéressantes (consultez les macros).
Haskell (et un langage similaire, j'imagine) ont une réponse complètement différente: les types de données algébriques. Un type de données algébrique est comme une
C
structure, mais avec plus d'options. Ces types de données fournissent l'abstraction nécessaire pour modéliser les données; Les fonctions fournissent l'abstraction nécessaire pour modéliser les algorithmes. Les classes de types et d'autres fonctionnalités avancées offrent un niveau d'abstraction encore plus élevé sur les deux.Par exemple, je travaille sur un langage de programmation appelé TPL pour le plaisir. Types de données rendent Algebraic vraiment facile de représenter des valeurs:
Ce que cela dit - d'une manière très visuelle - est qu'un TPLValue (n'importe quelle valeur dans ma langue) peut être un
Null
ou unNumber
avec uneInteger
valeur ou même unFunction
avec une liste de valeurs (les paramètres) et une valeur finale (le corps ).Ensuite, je peux utiliser des classes de types pour coder certains comportements courants. Par exemple, je pourrais faire
TPLValue
et instance deShow
ce qui signifie qu'il peut être converti en chaîne.De plus, je peux utiliser mes propres classes de types lorsque j'ai besoin de spécifier le comportement de certains types (y compris ceux que je n'ai pas implémentés moi-même). Par exemple, j'ai une
Extractable
classe de type qui me permet d'écrire une fonction qui prend unTPLValue
et retourne une valeur normale appropriée. Ainsiextract
peut convertir unNumber
en unInteger
ou unString
enString
tant queInteger
etString
sont des instances deExtractable
.Enfin, la logique principale de mon programme est dans plusieurs fonctions comme
eval
etapply
. Ce sont vraiment le noyau - ils prennent desTPLValue
s et les transforment en plus deTPLValue
s, ainsi que la gestion de l'état et des erreurs.Dans l'ensemble, les abstractions que j'utilise dans mon code Haskell sont en fait plus puissantes que ce que j'aurais utilisé dans un langage OOP.
la source
eval
. "Hé, regardez-moi! Je n'ai pas besoin d'écrire mes propres failles de sécurité; j'ai une vulnérabilité d'exécution de code arbitraire intégrée directement dans le langage de programmation!" Les conflits de données avec le code sont à l'origine de l'une des deux classes de vulnérabilités de sécurité les plus populaires de tous les temps. Chaque fois que vous voyez quelqu'un se faire pirater à cause d'une attaque par injection SQL (entre autres), c'est parce que certains programmeurs ne savent pas comment séparer correctement les données du code.eval
ne dépend pas beaucoup de la structure de Lisp - vous pouvez en avoireval
dans des langages comme JavaScript et Python. Le vrai pouvoir vient de l'écriture de macros, qui sont essentiellement des programmes qui agissent sur des programmes comme les données et produisent d'autres programmes. Cela rend le langage très flexible et crée facilement des abstractions puissantes.and
. court-circuitor
.let
.let-rec
.cond
.defn
. Aucun de ceux-ci ne peut être implémenté avec des fonctions dans des langages d'ordre applicatif.for
(liste des compréhensions).dotimes
.doto
.and
!" J'entends, "hé regardez-moi, ma langue est tellement paralysée qu'elle ne vient même pas avec un court-circuitand
et je dois réinventer la roue pour tout !"La phrase citée n'a plus de validité, pour autant que je sache.
Les langages OO contemporains ne peuvent pas abstraire sur des types dont le type n'est pas *, c'est-à-dire que les types de type supérieur sont inconnus. Leur système de types ne permet pas d'exprimer l'idée de "certains conteneurs avec des éléments Int, qui permettent de mapper une fonction sur les éléments".
Par conséquent, cette fonction de base comme Haskells
ne peut pas être écrit facilement en Java *), par exemple, du moins pas de manière sûre. Par conséquent, pour obtenir des fonctionnalités de base, vous devez écrire beaucoup de passe-partout, car vous avez besoin
Et pourtant, ces cinq méthodes sont fondamentalement le même code, donnez ou prenez. En revanche, à Haskell, j'aurais besoin de:
Notez que cela ne changera pas avec Java 8 (juste que l'on peut appliquer des fonctions plus facilement, mais alors, exactement, le problème ci-dessus se matérialisera. Tant que vous n'avez même pas de fonctions d'ordre supérieur, vous n'êtes probablement même pas capable de comprendre à quoi servent les types supérieurs.)
Même les nouveaux langages OO comme Ceylan n'ont pas de types plus élevés. (J'ai demandé à Gavin King récemment, et il m'a dit que ce n'était pas important pour le moment.) Je ne sais pas pour Kotlin, cependant.
*) Pour être honnête, vous pouvez avoir une interface Functor qui a une méthode fmap. La mauvaise chose est que vous ne pouvez pas dire: Hey, je sais comment implémenter fmap pour la classe de bibliothèque SuperConcurrentBlockedDoublyLinkedDequeHasMap, cher compilateur, veuillez accepter qu'à partir de maintenant, tous les SuperConcurrentBlockedDoublyLinkedDequeHasMaps sont des foncteurs.
la source
Quiconque ayant déjà programmé en dBase saurait à quel point les macros unifilaires étaient utiles pour créer du code réutilisable. Bien que je n'ai pas programmé en Lisp, j'ai lu de nombreux autres qui ne jurent que par les macros de compilation. L'idée d'injecter du code dans votre code au moment de la compilation est utilisée sous une forme simple dans chaque programme C avec la directive "include". Parce que Lisp peut le faire avec un programme Lisp et parce que Lisp est hautement réfléchissant, vous obtenez des inclusions beaucoup plus flexibles.
Tout programmeur qui prendrait simplement une chaîne de texte arbitraire du Web et la transmettrait à sa base de données n'est pas un programmeur. De même, toute personne qui permettrait aux données "utilisateur" de devenir automatiquement du code exécutable est évidemment stupide. Cela ne signifie pas que permettre aux programmes de manipuler des données au moment de l'exécution, puis de les exécuter en tant que code est une mauvaise idée. Je crois que cette technique sera indispensable à l'avenir, avec un code "intelligent" qui écrit la plupart des programmes. Tout le "problème données / code" est ou non une question de sécurité dans la langue.
L'un des problèmes avec la plupart des langues est qu'elles ont été conçues pour qu'une seule personne hors ligne puisse exécuter certaines fonctions pour elle-même. Les programmes du monde réel nécessitent que de nombreuses personnes aient accès à tout moment et en même temps à partir de plusieurs cœurs et de plusieurs clusters d'ordinateurs. La sécurité devrait faire partie du langage plutôt que du système d'exploitation et dans un avenir pas trop lointain, elle le sera.
la source