Comment les promoteurs de la programmation fonctionnelle répondraient-ils à cette affirmation dans Code Complete?

30

À 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).

dan
la source
3
+1 malgré que la question ait été formulée de manière plutôt antagoniste, c'est une bonne question.
mattnz
16
Comme beaucoup l'ont indiqué dans les réponses, la décomposition fonctionnelle et la programmation fonctionnelle sont deux bêtes différentes. Donc, la conclusion que "cela semble être un défi pour la programmation fonctionnelle" est tout à fait erronée, cela n'a rien à voir avec cela.
Fabio Fracassi
5
De toute évidence, les connaissances de McConnel dans les systèmes de types de données fonctionnels modernes et les modules de première classe de premier ordre sont quelque peu inégales. Sa déclaration est totalement absurde, car nous avons les premiers modules de classe et les foncteurs (voir SML), les classes de type (voir Haskell). C'est juste un autre exemple de la façon dont la façon de penser OO est plus une religion qu'une méthodologie de conception respectueuse. Et, au fait, où avez-vous trouvé ce truc sur la concurrence? La plupart des programmeurs fonctionnels ne se soucient pas du tout du parallélisme.
SK-logic
6
@ SK-logic Tout ce que McConnell a dit, c'est que la «décomposition fonctionnelle seule» ne fournit pas les mêmes moyens d'abstraction que la POO, ce qui me semble une déclaration assez sûre. Nulle part il ne dit que les langages FP n'ont pas de moyens d'abstractions aussi puissants que la POO. En fait, il ne mentionne pas du tout les langues FP. C'est juste l'interprétation du PO.
sepp2k
2
@ sepp2k, ok, je vois. Mais encore, un système très complexe et bien structuré de structures de données et de traitement des abstractions peut être construit sur rien d'autre que la décomposition fonctionnelle pour le calcul lambda presque pur - via la simulation du comportement des modules. Pas besoin du tout des abstractions OO.
SK-logic

Réponses:

13

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 GOTOgâ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.

Sean McSomething
la source
27

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.

sepp2k
la source
10
+1 "Décomposition fonctionnelle"! = "Programmation fonctionnelle". Le premier repose sur un codage séquentiel classique utilisant des structures de données vanille sans héritage, encapsulation et polymorphisme (ou uniquement roulé à la main). Le second exprime des solutions utilisant le calcul lambda. Deux choses complètement différentes.
Binary Worrier
4
Toutes mes excuses, mais l'expression "programmation procédurale" a obstinément refusé de me revenir plus tôt. La "décomposition fonctionnelle" est pour moi beaucoup plus révélatrice d'une programmation procédurale que d'une programmation fonctionnelle.
Binary Worrier
Oui tu as raison. J'ai supposé que la programmation fonctionnelle favorise les fonctions réutilisables fonctionnant sur les mêmes structures de données simples (listes, arbres, cartes) encore et encore et prétend en fait que c'est un argument de vente par rapport à OO. Voir 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 vecteur ou carte au lieu d'un objet OO plus riche avec d'autres (non -vecteurs et non-maplike) propriétés et méthodes.
dan
Le lien pour la citation de Stuart Halloway: thinkrelevance.com/blog/2009/08/12/…
dan
2
@dan C'est peut-être ainsi que cela se fait dans le langage typé dynamiquement Clojure (je ne sais pas, je n'utilise pas Clojure), mais je pense qu'il est dangereux de conclure à partir de cela, que c'est ainsi que cela s'est fait dans FP en général. Les gens Haskell, par exemple, semblent être très gros sur les types abstraits et les informations cachées (peut-être pas autant que les gens Java).
sepp2k
7

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:

(def starting-game-state {:map ....
                          :player ....
                          :weather ....
                          :other-stuff ....}

Et la boucle de jeu principale pourrait être définie comme l'application de certaines fonctions pures à l'état du jeu dans une boucle:

 (loop [initial-state starting-game-state]
   (let [user-input (get-user-input)
         game-state (update-game initial-state user-input)]
     (draw-screen game-state)
     (if-not (game-ended? game-state) (recur game-state))))

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é:

  • Certes, la fonction de mise à jour du jeu fait beaucoup de travail, mais elle est elle-même construite en composant d'autres fonctions, donc c'est en fait assez simple en soi. Une fois que vous descendez de quelques niveaux, les fonctions sont encore assez simples, faisant quelque chose comme "ajouter un objet à une tuile de carte".
  • Certes, l'état du jeu est une structure de Big Data. Mais encore une fois, il est simplement construit en composant des structures de données de niveau inférieur. Il s'agit également de «données pures» plutôt que d'avoir des méthodes intégrées ou une définition de classe requise (vous pouvez le considérer comme un objet JSON immuable très efficace si vous le souhaitez), donc il y a très peu de passe-partout.

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:

  • La structure des données sur l'état du jeu est immuable, de sorte qu'un grand nombre de traitements peuvent facilement être effectués en parallèle. Par exemple, il est parfaitement sûr d'avoir un rendu appelant draw-screen dans un thread différent de la logique du jeu - ils ne peuvent pas s'influencer mutuellement ou voir un état incohérent. C'est étonnamment difficile avec un gros graphe d'objets mutables ......
  • Vous pouvez prendre un instantané de l'état du jeu à tout moment. Les rediffusions sont triviales (grâce aux structures de données persistantes de Clojure, les copies ne prennent pratiquement pas de mémoire car la plupart des données sont partagées). Vous pouvez également exécuter update-game pour "prédire l'avenir" pour aider l'IA à évaluer différents mouvements par exemple.
  • Nulle part je n'ai dû faire de compromis difficiles pour m'intégrer au paradigme de la POO, comme définir une hiérarchie de classe rigide. En ce sens, la structure de données fonctionnelle se comporte davantage comme un système flexible basé sur un prototype.

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 )

mikera
la source
2
Je pense qu'un jeu est l'un des pires exemples possibles pour démontrer les supposés «avantages» de l'immuabilité forcée. Les choses bougent constamment dans un jeu, ce qui signifie que vous devez constamment reconstruire votre état de jeu. Et si tout est immuable, cela signifie que vous devez non seulement reconstruire l'état du jeu, mais tout dans le graphique qui contient une référence à celui-ci, ou qui contient une référence à cela, et ainsi de suite récursivement jusqu'à ce que vous recycliez l'ensemble programme à 30+ FPS, avec des tonnes de churn GC pour démarrer! Il n'y a aucun moyen d'obtenir de bonnes performances de cela ...
Mason Wheeler
7
Bien sûr, les jeux sont difficiles avec l'immuabilité - c'est pourquoi je l'ai choisi pour démontrer qu'il peut toujours fonctionner! Cependant, vous seriez surpris de voir ce que les structures de données persistantes peuvent faire - la plupart de l'état du jeu n'a pas besoin d'être reconstruit, seulement ce qui change. Et bien sûr, il y a des frais généraux, mais ce n'est qu'un petit facteur constant. Donnez-moi assez de cœurs et je vais battre votre moteur de jeu C ++ à un seul thread .....
mikera
3
@Mason Wheeler: En fait, il est possible d'obtenir des performances pratiquement égales (aussi bonnes qu'avec une mutation) avec des objets immuables, sans beaucoup de GC du tout. L'astuce dans Clojure est d'utiliser des structures de données persistantes : elles sont immuables pour le programmeur, mais en fait mutables sous le capot. Le meilleur des deux mondes.
Joonas Pulakka
4
@quant_dev Plus de cœurs sont moins chers que de meilleurs cœurs ... escapistmagazine.com/news/view/…
deworde
6
@quant_dev - ce n'est pas une excuse, c'est un fait mathématique et architectural que vous feriez mieux d'avoir un surcoût constant si vous pouvez compenser cela en adaptant vos performances de manière quasi-linéaire avec le nombre de cœurs. La raison pour laquelle les langages fonctionnels offriront en fin de compte des performances supérieures est que nous sommes arrivés au bout de la ligne pour les performances à cœur unique, et tout sera à propos de la concurrence et du parallélisme à l'avenir. les approches fonctionnelles (et l'immuabilité en particulier) sont importantes pour que cela fonctionne.
mikera
3

"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."

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.

Joonas Pulakka
la source
1
J'ai entendu la phrase "OO dans le grand, FP dans le petit" quelque part - je pense que Michael Feathers l'a citée. Cela signifie que la PF peut être bonne pour des parties particulières d'un grand programme, mais en général, elle devrait être OO.
dan
De plus, au lieu d'utiliser Clojure pour tout, même les choses qui sont exprimées plus proprement dans la syntaxe OO plus traditionnelle, que diriez-vous d'utiliser Clojure pour les bits de traitement des données où il est plus propre, et d'utiliser Java ou un autre langage OO pour les autres bits. Programmation polyglotte au lieu d'une programmation multiparadigm avec le même langage pour toutes les parties du programme. (Un peu comme la façon dont la plupart des applications Web utilisent SQL et OO pour différentes couches.)?
dan
@dan: utilisez l'outil qui convient le mieux au travail. Dans la programmation polyglotte, le facteur crucial est une communication pratique entre les langages, et Clojure et Java pourraient difficilement mieux jouer ensemble . Je crois que les programmes Clojure les plus importants utilisent au moins quelques bits des bibliothèques Java standard de JDK ici et là.
Joonas Pulakka
2

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."

Maglob
la source
3
Je ne suis pas sûr que votre affirmation d'ouverture soit vraie. La racine de "la plupart" des complexités? Dans la programmation que j'ai faite ou vue, le problème n'est pas tant un état mutable qu'un manque d'abstraction et une surabondance de détails à travers le code.
dan
1
@Dan: Intéressant. J'ai vu beaucoup du contraire, en fait: les problèmes proviennent de la surutilisation de l'abstraction, ce qui rend difficile à la fois de comprendre et, si nécessaire, de fixer les détails de ce qui se passe réellement.
Mason Wheeler
1

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.

String x = "abc";
StringBuffer y = new StringBuffer(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString());

à 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

String x = "abc";
WuHoString y = new WuHoString(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString())

Il est impossible de savoir à quoi correspond la dernière ligne.

Dans un style de programmation fonctionnelle, il s'écrirait plus comme suit:

String x;
equals(toString(reverse(x)), toString(reverse(WuHoString(x))))

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.

WuHoUnited
la source
3
C'est un peu drôle que vous disiez que les langages OO ne sont pas construits pour des objets immuables et que vous utilisiez ensuite un exemple avec des chaînes (qui sont immuables dans la plupart des langages OO, y compris Java). Je dois également souligner qu'il existe des langages OO (ou plutôt multi-paradigmes) qui sont conçus en mettant l'accent sur les objets immuables (Scala par exemple).
sepp2k
@ sepp2k: Habituez-vous. Les défenseurs de la PF jettent toujours des exemples bizarres et artificiels qui n'ont rien à voir avec le codage du monde réel. C'est le seul moyen de donner une belle apparence aux concepts de base de la PF, comme l'immuabilité forcée.
Mason Wheeler
1
@Mason: Hein? La meilleure façon de rendre l'immutabilité belle ne serait-elle pas de dire "Java (et C #, python, etc.) utilise des chaînes immuables et cela fonctionne très bien"?
sepp2k
1
@ sepp2k: Si les chaînes immuables fonctionnent si bien, pourquoi les classes de style StringBuilder / StringBuffer continuent-elles à apparaître partout? C'est juste un autre exemple d'une inversion d'abstraction qui vous gêne.
Mason Wheeler
2
De nombreux langages orientés objet vous permettent de créer des objets immuables. Mais l'idée de lier les méthodes à la classe la décourage vraiment de mon point de vue. L'exemple String n'est pas vraiment un exemple artificiel. Chaque fois que j'appelle un mehtod en java, je tente de savoir si mes paramètres vont être modifiés au sein de cette fonction.
WuHoUnited
0

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.

duyt
la source
0

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 Cstructure, 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:

data TPLValue = Null
              | Number Integer
              | String String
              | List [TPLValue]
              | Function [TPLValue] TPLValue
              -- There's more in the real code...

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 Nullou un Numberavec une Integervaleur ou même un Functionavec 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 TPLValueet instance de Showce 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 Extractableclasse de type qui me permet d'écrire une fonction qui prend un TPLValueet retourne une valeur normale appropriée. Ainsi extractpeut convertir un Numberen un Integerou un Stringen Stringtant que Integeret Stringsont des instances de Extractable.

Enfin, la logique principale de mon programme est dans plusieurs fonctions comme evalet apply. Ce sont vraiment le noyau - ils prennent des TPLValues et les transforment en plus de TPLValues, 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.

Tikhon Jelvis
la source
Ouais, je dois aimer 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.
Mason Wheeler
evalne dépend pas beaucoup de la structure de Lisp - vous pouvez en avoir evaldans 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.
Tikhon Jelvis
3
Oui, j'ai entendu les "macros sont géniales" parler à plusieurs reprises auparavant. Mais je n'ai jamais vu un exemple réel d'une macro Lisp faisant quelque chose qui 1) est pratique et quelque chose que vous voudriez réellement faire dans du code du monde réel et 2) ne peut pas être accompli aussi facilement dans n'importe quel langage qui prend en charge les fonctions.
Mason Wheeler
1
@MasonWheeler court-circuit and. court-circuit or. 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.
1
@MattFenwick: OK, j'aurais vraiment dû ajouter un troisième point aux deux ci-dessus: 3) n'est déjà intégré à aucun langage de programmation sain. Parce que c'est le seul exemple de macro vraiment utile que je vois, et quand vous dites "Hé, regardez-moi, mon langage est si flexible que je peux implémenter mon propre court-circuit and!" J'entends, "hé regardez-moi, ma langue est tellement paralysée qu'elle ne vient même pas avec un court-circuit andet je dois réinventer la roue pour tout !"
Mason Wheeler
0

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

fmap :: Functor f => (a -> b) -> f a -> f b 

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

  1. une méthode pour appliquer une fonction simple aux éléments d'une liste
  2. une méthode pour appliquer la même fonction simple aux éléments d'un tableau
  3. une méthode pour appliquer la même fonction simple aux valeurs d'un hachage,
  4. .... ensemble
  5. .... arbre
  6. ... 10. tests unitaires pour les mêmes

Et pourtant, ces cinq méthodes sont fondamentalement le même code, donnez ou prenez. En revanche, à Haskell, j'aurais besoin de:

  1. Une instance de Functor pour liste, tableau, carte, ensemble et arborescence (principalement prédéfinie ou peut être dérivée automatiquement par le compilateur)
  2. la fonction simple

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.

Ingo
la source
FTR: le vérificateur de type Ceylan et le backend JavaScript prennent désormais en charge les types supérieurs (et également les types de rang supérieur ). Il est considéré comme une fonctionnalité "expérimentale". Cependant, notre communauté a eu du mal à trouver des applications pratiques pour cette fonctionnalité, c'est donc une question ouverte de savoir si cela fera jamais partie "officielle" de la langue. Je ne m'y attendais pas à être pris en charge par le Java back - end à un certain stade.
Gavin King
-2

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.

David Clark
la source
2
Bienvenue aux programmeurs. Veuillez envisager d'atténuer la rhétorique dans votre réponse et de soutenir certaines de vos revendications avec des références externes.
1
Tout programmeur qui permettrait aux données utilisateur de devenir automatiquement du code exécutable est évidemment ignorant . Pas stupide. Le faire de cette façon est souvent facile et évident, et s'ils ne savent pas pourquoi c'est une mauvaise idée et qu'une meilleure solution existe, vous ne pouvez pas vraiment leur en vouloir. (Quiconque continue de le faire après avoir appris qu'il existe une meilleure façon, est évidemment stupide.)
Mason Wheeler