La programmation fonctionnelle est-elle un sur-ensemble orienté objet?

26

Plus je fais de programmation fonctionnelle, plus j'ai l'impression qu'il ajoute une couche d'abstraction supplémentaire qui ressemble à la couche d'un oignon - englobant toutes les couches précédentes.

Je ne sais pas si cela est vrai, alors en dérogeant aux principes de POO avec lesquels je travaille depuis des années, quelqu'un peut-il expliquer comment fonctionnel décrit ou non avec précision l'un d'entre eux: encapsulation, abstraction, héritage, polymorphisme

Je pense que nous pouvons tous dire, oui, il a une encapsulation via des tuples, ou les tuples comptent-ils techniquement comme un fait de "programmation fonctionnelle" ou sont-ils simplement une utilité du langage?

Je sais que Haskell peut répondre à l'exigence d '"interfaces", mais encore une fois pas certain si sa méthode est un fait fonctionnel? Je suppose que le fait que les foncteurs aient une base mathématique pourrait-on dire que ceux-ci sont définitivement définis dans l'attente de la fonctionnalité, peut-être?

Veuillez décrire en détail comment vous pensez que la fonctionnalité répond ou non aux 4 principes de la POO.

Edit: Je comprends très bien les différences entre le paradigme fonctionnel et le paradigme orienté objet et je réalise qu'il y a beaucoup de langages multiparadigmes de nos jours qui peuvent faire les deux. Je suis vraiment à la recherche de définitions de la façon dont fp (pensez puriste, comme haskell) peut faire l'une des 4 choses énumérées, ou pourquoi il ne peut pas en faire. c.-à-d. "L'encapsulation peut se faire avec des fermetures" (ou si je me trompe dans cette croyance, veuillez expliquer pourquoi).

Jimmy Hoffa
la source
7
Ces 4 principes ne "font" pas de POO. La POO "résout" simplement ces problèmes en utilisant des classes, la recherche de classe et leurs instances. Mais je voudrais moi aussi une réponse s'il existe des moyens de les atteindre dans la programmation fonctionnelle.
Euphoric
2
@Euphoric Selon la définition, cela fait de la POO.
Konrad Rudolph
2
@KonradRudolph Je sais que beaucoup de gens revendiquent ces choses et les avantages qu'elles apportent en tant que propriétés uniques de la POO. En supposant que "polymorphisme" signifie "polymorphisme de sous-type", je peux aller avec les deux derniers faisant partie intégrante de la POO. Mais je n'ai pas encore rencontré une définition utile de l'encapsulation et de l'abstraction qui exclut résolument les approches non-OOP. Vous pouvez masquer les détails et limiter l'accès à eux très bien, même dans Haskell. Et le Haskell a également un polymorphisme ad hoc, mais pas un polymorphisme de sous-type - la question est, le bit "sous-type" est-il important?
1
@KonradRudolph Cela ne le rend pas plus acceptable. Si quoi que ce soit, c'est une incitation à intensifier et à donner à ceux qui le propagent une raison de le réexaminer.
1
L'abstraction est intrinsèque à toute programmation, au moins à toute programmation au-delà du code machine brut. L'encapsulation existe depuis longtemps avant la POO, et elle est intrinsèque à la programmation fonctionnelle. Un langage fonctionnel n'est pas requis pour inclure une syntaxe explicite pour l'héritage ou le polymorphisme. Je suppose que cela revient à «non».
sdenham

Réponses:

44

La programmation fonctionnelle n'est pas une couche au-dessus de la POO; c'est un paradigme complètement différent. Il est possible de faire de la POO dans un style fonctionnel (F # a été écrit exactement dans ce but), et à l'autre bout du spectre, vous avez des trucs comme Haskell, qui rejette explicitement les principes d'orientation des objets.

Vous pouvez effectuer l'encapsulation et l'abstraction dans n'importe quel langage suffisamment avancé pour prendre en charge les modules et les fonctions. OO fournit des mécanismes spéciaux pour l'encapsulation, mais ce n'est pas quelque chose d'inhérent à OO. Le point d'OO est la deuxième paire que vous avez mentionnée: l'hérédité et le polymorphisme. Le concept est officiellement connu sous le nom de substitution Liskov, et vous ne pouvez pas l'obtenir sans la prise en charge au niveau du langage pour la programmation orientée objet. (Oui, il est possible de le simuler dans certains cas, mais vous perdez beaucoup des avantages qu'OO apporte à la table.)

La programmation fonctionnelle ne se concentre pas sur la substitution Liskov. Il se concentre sur l'augmentation du niveau d'abstraction et sur la minimisation de l'utilisation de l'état mutable et de routines avec des "effets secondaires", qui est un terme que les programmeurs fonctionnels aiment utiliser pour créer des routines qui font réellement quelque chose (par opposition à simplement calculer quelque chose). effrayant. Mais encore une fois, ce sont des paradigmes complètement séparés, qui peuvent être utilisés ensemble ou non, selon le langage et les compétences du programmeur.

Mason Wheeler
la source
1
Eh bien, l'héritage (dans ces cas exceptionnellement rares où il est nécessaire) est réalisable par rapport à la composition, et il est plus propre que l'héritage au niveau du type. Le polymorphisme est naturel, surtout en présence de types polymorphes. Mais bien sûr, je conviens que FP n'a rien à voir avec la POO et ses principes.
SK-logic
Il est toujours possible de le simuler - vous pouvez implémenter des objets dans le langage de votre choix. Je suis d'accord avec tout le reste cependant :)
Eliot Ball
5
Je ne pense pas que le terme "effets secondaires" ait été inventé (ou est principalement utilisé) par les programmeurs fonctionnels.
sepp2k
4
@ sepp2k: Il n'a pas dit qu'ils avaient inventé le terme, juste qu'ils l'utilisaient en utilisant à peu près le même ton que celui que l'on utiliserait normalement pour se référer aux enfants qui refusent de descendre de leur pelouse.
Aaronaught
16
@Aaronaught ce ne sont pas les enfants sur ma pelouse qui me dérangent, ce sont leurs effets secondaires sanglants! S'ils cessaient de muter partout sur ma pelouse, cela ne me dérangerait pas du tout.
Jimmy Hoffa
10

Je trouve l'intuition suivante utile pour comparer OOP et FP.

Plutôt que de considérer FP comme un surensemble de la POO, pensez à la POO et à la FP comme deux façons alternatives d'examiner un modèle de calcul sous-jacent similaire dans lequel vous avez:

  1. Une opération qui est exécutée,
  2. quelques arguments d'entrée à l'opération,
  3. quelques données / paramètres fixes qui peuvent influencer la définition de l'opération,
  4. une certaine valeur de résultat, et
  5. peut-être un effet secondaire.

Dans la POO, cela est capturé par

  1. Une méthode qui est exécutée,
  2. les arguments d'entrée d'une méthode,
  3. l'objet sur lequel la méthode est invoquée, contenant des données locales sous forme de variables membres,
  4. la valeur de retour de la méthode (éventuellement nulle),
  5. les effets secondaires de la méthode.

Dans FP, cela est capturé par

  1. Une fermeture qui est exécutée,
  2. les arguments d'entrée de la fermeture,
  3. les variables capturées de la fermeture,
  4. la valeur de retour de la fermeture,
  5. les effets secondaires possibles de la fermeture (dans des langues pures comme Haskell, cela se produit de manière très contrôlée).

Avec cette interprétation, un objet peut être vu comme une collection de fermetures (ses méthodes) capturant toutes les mêmes variables non locales (les variables membres de l'objet communes à toutes les fermetures de la collection). Cette vue est également soutenue par le fait que dans les langages orientés objet, les fermetures sont souvent modélisées comme des objets avec exactement une méthode.

Je pense que les différents points de vue proviennent du fait que la vue orientée objet est centrée sur les objets (les données) tandis que la vue fonctionnelle est centrée sur les fonctions / fermetures (les opérations).

Giorgio
la source
8

Cela dépend de qui vous demandez une définition de la POO. Demandez à cinq personnes et vous obtiendrez probablement six définitions. Wikipédia dit :

Les tentatives pour trouver une définition ou une théorie consensuelle derrière les objets n'ont pas été couronnées de succès

Donc, chaque fois que quelqu'un donne une réponse très définitive, prenez-la avec un grain de sel.

Cela dit, il y a un bon argument à faire valoir que, oui, FP est un surensemble de POO en tant que paradigme. En particulier, la définition d'Alan Kay du terme programmation orientée objet ne contredit pas cette notion (mais celle de Kristen Nygaard ). Tout ce qui préoccupait Kay était que tout est un objet, et que la logique est mise en œuvre en passant des messages entre les objets.

Peut-être plus intéressant pour votre question, les classes et les objets peuvent être considérés en termes de fonctions et de fermetures renvoyées par les fonctions (qui agissent à la fois comme classes et constructeurs). Cela est très proche de la programmation basée sur des prototypes, et en fait, JavaScript permet de le faire précisément.

var cls = function (x) {
    this.y = x;
    this.fun = function () { alert(this.y); };
    return this;
};

var inst = new cls(42);
inst.fun();

(Bien sûr, JavaScript permet la mutation de valeurs, ce qui est illégal dans la programmation purement fonctionnelle, mais il n'est pas non plus requis dans une définition stricte de la POO.)

La question la plus importante est cependant: est-ce une classification significative de la POO? Est-il utile de le considérer comme un sous-ensemble de programmation fonctionnelle? Je pense que dans la plupart des cas, ce n'est pas le cas.

Konrad Rudolph
la source
1
Je pense qu'il peut être utile de réfléchir à l'endroit où la ligne doit être tracée lorsque je change de paradigme. S'il est dit qu'il n'y a aucun moyen de réaliser un polymorphisme sous-type dans fp, alors je ne m'embêterai jamais à essayer d'utiliser fp dans la modélisation de quelque chose qui lui irait bien. Si toutefois c'est possible, je peux prendre le temps de trouver une bonne façon de le faire (bien qu'une bonne façon ne soit pas possible) lorsque je travaille fortement dans un espace fp mais que je souhaite un polymorphisme sous-type dans quelques espaces de niche. De toute évidence, si la majorité du système lui convient cependant, il serait préférable d'utiliser la POO.
Jimmy Hoffa
6

FP comme OO n'est pas un terme bien défini. Il existe des écoles aux définitions différentes, parfois contradictoires. Si vous prenez ce qu'ils ont en commun, vous vous mettez à:

  • la programmation fonctionnelle est la programmation avec des fonctions de première classe

  • La programmation OO est une programmation avec polymorphisme d'inclusion combiné avec au moins une forme restreinte de surcharge résolue dynamiquement. (Remarque: dans les cercles OO, le polymorphisme est généralement considéré comme le polymorphisme d' inclusion , tandis que dans les écoles FP, il signifie généralement le polymorphisme paramétrique .)

Tout le reste est soit présent ailleurs, soit absent dans certains cas.

FP et OO sont deux outils de construction d'abstractions. Ils ont chacun leurs propres forces et faiblesses (par exemple, ils ont une direction d'extension préférée différente dans le problème d'expression), mais aucune n'est intrinsèquement plus puissante que l'autre. Vous pouvez construire un système OO sur un noyau FP (CLOS en est un). Vous pouvez utiliser un framework OO pour obtenir des fonctions de première classe (voir la façon dont les fonctions lambda sont définies en C ++ 11 par exemple).

AProgrammer
la source
Je pense que vous voulez dire «fonctions de première classe» plutôt que «fonctions de premier ordre».
dan_waterworth
Errr ... C ++ 11 Les lambdas ne sont guère des fonctions de première classe: chaque lambda a son propre type ad-hoc (à toutes fins pratiques, une structure anonyme), incompatible avec un type de pointeur de fonction native. Et std::function, auquel des pointeurs de fonction et des lambdas peuvent être attribués, est décidément générique et non orienté objet. Ce n'est pas une surprise, car la marque limitée de polymorphisme orientée objet (polymorphisme de sous-type) est strictement moins puissante que le polymorphisme paramétrique (même Hindley-Milner, et encore moins le système complet F-oméga).
pyon
Je n'ai pas une tonne d'expérience avec les langages fonctionnels puristes, mais si vous pouvez définir des classes à méthode statique unique dans des fermetures et les transmettre à différents contextes, je dirais que vous (peut-être maladroitement) au moins à mi-chemin là sur les options de style fonctionnel. Il existe de nombreuses façons de contourner les paramètres stricts dans la plupart des langues.
Erik Reppen du
2

Non; La POO peut être considérée comme un surensemble de programmation procédurale et diffère fondamentalement du paradigme fonctionnel car elle a un état représenté dans les champs d'instance. Dans le paradigme fonctionnel, les variables sont des fonctions qui sont appliquées aux données constantes afin d'obtenir le résultat souhaité.

En fait, vous pouvez considérer la programmation fonctionnelle comme un sous-ensemble de POO; si vous rendez toutes vos classes immuables, vous pouvez considérer que vous avez une sorte de programmation fonctionnelle.

m3th0dman
la source
3
Les classes immuables n'effectuent pas de fonctions d'ordre supérieur, de compréhension de liste ou de fermeture. Fp n'est pas un sous-ensemble.
Jimmy Hoffa
1
@Jimmy Hoffa: Vous pouvez facilement simuler une fonction oreder supérieure en créant une classe qui a une méthode unique qui prend ou plusieurs objets d'un type similaire et renvoie également un objet de ce type similaire (type qui a une méthode et pas de champs) . La comparaison de liste n'est pas liée au langage de programmation et non au paradigme (Smalltalk le prend en charge et est OOP). Les fermetures sont présentes en C # et seront également insérées en Java.
m3th0dman le
oui C # a des fermetures, mais c'est parce que c'est multi-paradigme, des fermetures ont été ajoutées avec d'autres pièces fp à C # (pour lesquelles je suis éternellement reconnaissant) mais leur présence dans un langage oop ne les rend pas oop. Bon point sur la fonction d'ordre supérieur cependant, l'encapsulation d'une méthode dans une classe permet le même comportement.
Jimmy Hoffa le
2
Oui, mais si vous utilisez des fermetures pour modifier l'état, programmez-vous toujours dans un paradigme fonctionnel? Le point est - le paradigme fonctionnel concerne le manque d'état, pas les fonctions d'ordre élevé, la récursivité ou les fermetures.
m3th0dman le
réflexion intéressante sur la définition de fp .. Je vais devoir y réfléchir davantage, merci de partager vos observations.
Jimmy Hoffa du
2

Répondre:

Wikipedia a un excellent article sur la programmation fonctionnelle avec certains des exemples que vous demandez. @Konrad Rudolph a déjà fourni le lien vers l' article OOP .

Je ne pense pas qu'un paradigme soit un super-ensemble de l'autre. Ce sont des perspectives différentes sur la programmation et certains problèmes sont mieux résolus d'un point de vue et certains d'un autre.

Votre question est encore compliquée par toutes les implémentations de FP et OOP. Chaque langue a ses propres bizarreries qui sont pertinentes pour toute bonne réponse à votre question.

Randonnée de plus en plus tangentielle:

J'aime l'idée qu'une langue comme Scala essaie de vous donner le meilleur des deux mondes. Je crains que cela ne vous donne aussi les complications des deux mondes.

Java est un langage OO, mais la version 7 a ajouté une fonctionnalité "essayer avec des ressources" qui peut être utilisée pour imiter une sorte de fermeture. Ici, il imite la mise à jour d'une variable locale "a" au milieu d'une autre fonction, sans la rendre visible pour cette fonction. Dans ce cas, la première moitié de l'autre fonction est le constructeur ClosureTry () et la seconde moitié est la méthode close ().

public class ClosureTry implements AutoCloseable {

    public static void main(String[] args) {
        int a = 1;
        try(ClosureTry ct = new ClosureTry()) {
            System.out.println("Middle Stuff...");
            a = 2;
        }
        System.out.println("a: " + a);
    }

    public ClosureTry() {
        System.out.println("Start Stuff Goes Here...");
    }

    /** Interface throws exception, but we don't have to. */
    public void close() {
        System.out.println("End Stuff Goes Here...");
    }
}

Sortie:

Start Stuff Goes Here...
Middle Stuff...
End Stuff Goes Here...
a: 2

Cela peut être utile pour son objectif prévu d'ouvrir un flux, d'écrire dans le flux et de le fermer de manière fiable, ou pour simplement associer deux fonctions d'une manière que vous n'oubliez pas d'appeler la seconde après avoir effectué un travail entre elles . Bien sûr, c'est tellement nouveau et inhabituel qu'un autre programmeur peut supprimer le bloc try sans se rendre compte qu'ils cassent quelque chose, c'est donc actuellement une sorte d'anti-pattern, mais intéressant que cela puisse être fait.

Vous pouvez exprimer n'importe quelle boucle dans la plupart des langues impératives comme une récursivité. Les objets et les variables peuvent être rendus immuables. Des procédures peuvent être écrites pour minimiser les effets secondaires (même si je dirais qu'une véritable fonction n'est pas possible sur un ordinateur - le temps qu'il faut pour exécuter et les ressources processeur / disque / système qu'il consomme sont des effets secondaires inévitables). Certains langages fonctionnels peuvent être conçus pour effectuer de nombreuses, sinon toutes, les opérations orientées objet. Ils ne doivent pas être mutuellement exclusifs, bien que certaines langues aient des limitations (comme interdire toute mise à jour des variables) qui empêchent certains modèles (comme les champs mutables).

Pour moi, les parties les plus utiles de la programmation orientée objet sont le masquage des données (encapsulation), le traitement d'objets suffisamment similaires comme les mêmes (polymorphisme) et la collecte de vos données et méthodes qui opèrent ensemble sur ces données (objets / classes). L'héritage est peut-être le fleuron de la POO, mais pour moi, c'est la partie la moins importante et la moins utilisée.

Les parties les plus utiles de la programmation fonctionnelle sont l'immuabilité (jetons / valeurs au lieu de variables), les fonctions (pas d'effets secondaires) et les fermetures.

Je ne pense pas que ce soit orienté objet, mais je dois dire que l'une des choses les plus utiles en informatique est la capacité de déclarer une interface, puis de disposer de diverses fonctionnalités et données pour implémenter cette interface. J'aime également avoir quelques données mutables avec lesquelles travailler, donc je suppose que je ne suis pas totalement à l'aise dans les langages exclusivement fonctionnels, même si j'essaie de limiter la mutabilité et les effets secondaires dans toutes mes conceptions de programmes.

GlenPeterson
la source