Je viens d'un milieu orienté objet où j'ai appris que les classes sont ou au moins peuvent être utilisées pour créer une couche d'abstraction qui permet un recyclage facile du code qui peut ensuite être utilisé pour créer des objets ou être utilisé dans l'héritage.
Comme par exemple, je peux avoir une classe animale, puis hériter de chats et de chiens et de sorte que tous héritent de la plupart des mêmes traits, et de ces sous-classes, je peux ensuite créer des objets qui peuvent spécifier une race d'animal ou même le nom de celui-ci.
Ou je peux utiliser des classes pour spécifier plusieurs instances du même code qui gère ou contient des choses légèrement différentes; comme des nœuds dans un arbre de recherche ou plusieurs connexions de bases de données différentes et ce qui ne l'est pas.
Je suis récemment passé à la programmation fonctionnelle, alors je commençais à me demander:
comment les langages purement fonctionnels gèrent-ils des choses comme ça? Autrement dit, des langages sans aucun concept de classes et d'objets.
la source
Réponses:
De nombreux langages fonctionnels ont un système de modules. (De nombreux langages orientés objet le font d'ailleurs, d'ailleurs). Mais même en l'absence d'un, vous pouvez utiliser des fonctions comme modules.
JavaScript est un bon exemple. En JavaScript, les fonctions sont utilisées à la fois pour implémenter des modules et même une encapsulation orientée objet. Dans Scheme, qui a été la principale source d'inspiration pour JavaScript, il n'y a que des fonctions. Les fonctions sont utilisées pour implémenter presque tout: les objets, les modules (appelés unités dans Racket), même les structures de données.
OTOH, Haskell et la famille ML ont un système de modules explicite.
L'orientation objet concerne l'abstraction des données. C'est ça. La modularité, l'hérédité, le polymorphisme, voire l'état mutable sont des préoccupations orthogonales.
la source
module
. Je pense qu'il est malheureux que Racket ait un concept appelémodule
qui n'est pas un module, et un concept qui est un module mais pas appelémodule
. Quoi qu'il en soit, vous avez écrit: "les unités sont en quelque sorte à mi-chemin entre les espaces de noms et les interfaces OO". Eh bien, n'est-ce pas la définition de ce qu'est un module?map
et une unité dépendante d'une liaisonmap
sont différents en ce que le module doit faire référence à unemap
liaison spécifique , telle que celle deracket/base
, tandis que différents utilisateurs de l'unité peuvent donner des définitions différentes demap
l'unité.Il semble que vous posiez deux questions: "Comment pouvez-vous atteindre la modularité dans les langages fonctionnels?" qui a été traité dans d'autres réponses et "comment pouvez-vous créer des abstractions dans des langages fonctionnels?" auquel je répondrai.
Dans les langues OO, vous avez tendance à vous concentrer sur le nom, "un animal", "le serveur de courrier", "sa fourchette de jardin", etc. Les langages fonctionnels, en revanche, mettent l'accent sur le verbe, "marcher", "aller chercher du courrier" , "to prod", etc.
Il n'est donc pas surprenant que les abstractions dans les langages fonctionnels aient tendance à porter sur des verbes ou des opérations plutôt que sur des choses. Un exemple que j'atteins toujours lorsque j'essaie d'expliquer cela est l'analyse. Dans les langages fonctionnels, un bon moyen d'écrire des analyseurs syntaxiques consiste à spécifier une grammaire puis à l'interpréter. L'interpréteur crée une abstraction sur le processus d'analyse.
Un autre exemple concret de ceci est un projet sur lequel je travaillais il n'y a pas longtemps. J'écrivais une base de données à Haskell. J'avais un «langage intégré» pour spécifier les opérations au niveau le plus bas; par exemple, cela m'a permis d'écrire et de lire des choses à partir du support de stockage. J'avais un autre «langage intégré» distinct pour spécifier les opérations au plus haut niveau. Ensuite, j'ai eu, ce qui est essentiellement un interprète, pour convertir les opérations du niveau supérieur au niveau inférieur.
C'est une forme d'abstraction remarquablement générale, mais ce n'est pas la seule disponible dans les langages fonctionnels.
la source
Bien que la "programmation fonctionnelle" n'entraîne pas d'implications profondes pour les problèmes de modularité, des langages particuliers abordent la programmation dans son ensemble de différentes manières. La réutilisation et l'abstraction du code interagissent en ce que moins vous exposez, plus il est difficile de réutiliser le code. Mis à part l'abstraction, je vais aborder deux questions de réutilisabilité.
Les langages OOP à typage statique utilisent traditionnellement le sous-typage nominal, ce qui signifie qu'un code conçu pour la classe / module / interface A ne peut traiter que la classe / module / interface B lorsque B mentionne explicitement A. Les langages de la famille de programmation fonctionnelle utilisent principalement le sous-typage structurel, ce qui signifie ce code conçu pour A peut gérer B chaque fois que B possède toutes les méthodes et / ou tous les champs de A. B aurait pu être créé par une équipe différente avant d'avoir besoin d'une classe / interface plus générale A. Par exemple, dans OCaml, le sous-typage structurel s'applique au système de modules, au système d'objets de type POO et à ses types de variantes polymorphes tout à fait uniques.
La différence la plus importante entre OOP et FP wrt. la modularité est que l '"unité" par défaut dans OOP regroupe en tant qu'objet diverses opérations sur le même cas de valeurs, tandis que l' "unité" par défaut dans FP regroupe en tant que fonction la même opération pour différents cas de valeurs. Dans FP, il est toujours très facile de regrouper des opérations, par exemple sous forme de modules. (BTW, ni Haskell ni F # n'ont un système de modules de la famille ML à part entière.) Le problème de l'expressionest la tâche d'ajouter progressivement à la fois de nouvelles opérations travaillant sur toutes les valeurs (par exemple, attacher une nouvelle méthode à des objets existants), et de nouveaux cas de valeurs que toutes les opérations devraient prendre en charge (par exemple, ajouter une nouvelle classe avec la même interface). Comme discuté dans la première conférence Ralf Laemmel ci-dessous (qui a de nombreux exemples en C #), l'ajout de nouvelles opérations est problématique dans les langages POO.
La combinaison de POO et de FP dans Scala pourrait en faire l'une des langues les plus puissantes. modularité. Mais OCaml est toujours ma langue préférée et à mon avis personnel et subjectif, il ne manque pas de Scala. Les deux conférences Ralf Laemmel ci-dessous discutent de la solution au problème d'expression dans Haskell. Je pense que cette solution, bien que parfaitement fonctionnelle, rend difficile l'utilisation des données résultantes avec un polymorphisme paramétrique. Résoudre le problème d'expression avec des variantes polymorphes dans OCaml, expliqué dans l'article de Jaques Garrigue lié ci-dessous, n'a pas cette lacune. Je fais également un lien vers des chapitres de manuels qui comparent les utilisations de la modularité non-OOP et OOP dans OCaml.
Vous trouverez ci-dessous des liens spécifiques à Haskell et OCaml développant le problème d'expression :
la source
En fait, le code OO est beaucoup moins réutilisable, et c'est par conception. L'idée derrière la POO est de restreindre les opérations sur des éléments de données particuliers à un certain code privilégié qui se trouve soit dans la classe, soit à l'endroit approprié dans la hiérarchie d'héritage. Cela limite les effets néfastes de la mutabilité. Si une structure de données change, il n'y a que peu d'endroits dans le code qui peuvent être responsables.
Avec l'immuabilité, vous ne vous souciez pas de savoir qui peut opérer sur une structure de données donnée, car personne ne peut modifier votre copie des données. Cela facilite la création de nouvelles fonctions pour travailler sur les structures de données existantes. Il vous suffit de créer les fonctions et de les regrouper en modules qui semblent appropriés du point de vue du domaine. Vous n'avez pas à vous soucier de leur emplacement dans la hiérarchie d'héritage.
L'autre type de réutilisation de code consiste à créer de nouvelles structures de données pour travailler sur les fonctions existantes. Ceci est géré dans des langages fonctionnels utilisant des fonctionnalités comme les génériques et les classes de types. Par exemple, la classe de type Ord de Haskell vous permet d'utiliser la
sort
fonction sur n'importe quel type avec uneOrd
instance. Les instances sont faciles à créer si elles n'existent pas déjà.Prenez votre
Animal
exemple et envisagez d'implémenter une fonction d'alimentation. L'implémentation POO simple consiste à maintenir une collection d'Animal
objets et à parcourir tous ces éléments, en appelant lafeed
méthode sur chacun d'eux.Cependant, les choses deviennent délicates lorsque vous descendez dans les détails. Un
Animal
objet sait naturellement quel type de nourriture il mange et combien il a besoin pour se sentir rassasié. Il ne sait pas naturellement où la nourriture est conservée et combien est disponible, donc unFoodStore
objet vient de devenir une dépendance de chacunAnimal
, soit en tant que champ de l'Animal
objet, soit transmis en tant que paramètre de lafeed
méthode. Alternativement, pour garder laAnimal
classe plus cohérente, vous pouvez vous déplacerfeed(animal)
vers l'FoodStore
objet, ou vous pouvez créer une abomination d'une classe appelée unAnimalFeeder
ou un tel.Dans FP, il n'y a aucune tendance à ce que les champs d'un
Animal
restent toujours regroupés, ce qui a des implications intéressantes pour la réutilisabilité. Disons que vous avez une liste deAnimal
dossiers, avec des domaines commename
,species
,location
,food type
,food amount
, etc. Vous avez également une liste desFoodStore
enregistrements avec des champs tels quelocation
,food type
etfood amount
.La première étape de l'alimentation pourrait consister à faire correspondre chacune de ces listes d'enregistrements à des listes de
(food amount, food type)
paires, avec des nombres négatifs pour les quantités des animaux. Vous pouvez ensuite créer des fonctions pour faire toutes sortes de choses avec ces paires, comme additionner les quantités de chaque type de nourriture. Ces fonctions n'appartiennent pas parfaitement à unAnimal
ou à unFoodStore
module, mais sont hautement réutilisables par les deux.Vous vous retrouvez avec un tas de fonctions qui font des choses utiles avec
[(Num A, Eq B)]
qui sont réutilisables et modulaires, mais vous avez du mal à savoir où les mettre ou comment les appeler en tant que groupe. L'effet est que les modules FP sont plus difficiles à classer, mais la classification est beaucoup moins importante.la source
L'une des solutions les plus courantes consiste à diviser le code en modules, voici comment cela se fait en JavaScript:
L' article complet expliquant ce modèle en JavaScript , outre qu'il existe de nombreuses autres façons de définir un module, telles que RequireJS , CommonJS , Google Closure. Un autre exemple est Erlang, où vous avez à la fois des modules et des comportements qui appliquent l'API et le modèle, jouant un rôle similaire à celui des interfaces dans la POO.
la source