Pourquoi les types de données algébriques de Haskell sont-ils "fermés"?

corrigez-moi si je me trompe, mais il semble que les types de données algébriques dans Haskell sont utiles dans de nombreux cas où vous utiliseriez les classes et l'héritage dans les langues OO. Mais il y a une grande différence: une fois qu'un type de données algébriques est déclaré, il ne peut pas être étendu ailleurs. Il est "fermé". Dans OO, vous pouvez étendre des classes déjà définies. Par exemple:

data Maybe a = Nothing | Just a

il n'y a aucun moyen que je puisse ajouter une autre option à ce type plus tard sans modifier la présente Déclaration. Quels sont donc les avantages de ce système? Il semble que la voie OO serait beaucoup plus extensible.

56
demandé sur Don Stewart 2009-05-16 01:17:04

8 réponses

le fait que ADT sont fermés rend beaucoup plus facile d'écrire des fonctions totales. Qui sont des fonctions qui produisent toujours un résultat, pour toutes les valeurs possibles de son type, par exemple.

maybeToList :: Maybe a -> [a]
maybeToList Nothing  = []
maybeToList (Just x) = [x]

si Maybe était ouvert, quelqu'un pourrait ajouter un constructeur supplémentaire et la fonction maybeToList se casserait soudainement.

dans OO ce n'est pas un problème, quand vous utilisez l'héritage pour étendre un type, parce que quand vous appelez une fonction pour laquelle il n'est pas une surcharge spécifique, il peut juste utiliser l'implémentation pour une superclasse. C'est-à-dire: vous pouvez appeler printPerson(Person p) très bien avec un Student objet si Student est une sous-classe de Person .

à Haskell, vous utiliserez généralement des classes d'encapsulation et de type lorsque vous avez besoin d'étendre vos types. Par exemple:

class Eq a where
   (==) :: a -> a -> Bool

instance Eq Bool where
  False == False = True
  False == True  = False
  True  == False = False
  True  == True  = True

instance Eq a => Eq [a] where
  []     == []     = True
  (x:xs) == (y:ys) = x == y && xs == ys
  _      == _      = False

maintenant ,la fonction == est complètement ouverte, vous pouvez ajouter vos propres types en faisant une instance de la Eq de la classe.


notez qu'il y a eu des travaux sur l'idée de extensible datatypes , mais cela ne fait certainement pas encore partie de Haskell.

66
répondu Tom Lokhorst 2009-05-15 22:11:59

la réponse a à voir avec la façon dont le code est facile à étendre, une tension entre les classes et les types de données algébriques que Phil Wadler appelé "le problème d'expression":

  • Avec types de données algébriques,

    • "151900920 C'est très bon marché pour ajouter une nouvelle choses : vous venez de définir une nouvelle fonction. Toutes les vieilles fonctions sur ces choses continuer à travailler sans changement.

    • "151900920 C'est très cher pour ajouter une nouvelle genre de chose : vous devez ajouter un nouveau constructeur d'un type de données existant, et que vous avez à modifier et recompiler à chaque fonction qui utilise ce type de .

  • avec classes,

    • Il est très bon marché pour ajouter un nouveau genre de chose : il suffit d'ajouter une nouvelle sous-classe, et au besoin vous définissez des méthodes spécialisées, dans cette classe, pour toutes les opérations existantes. La superclasse et toutes les autres sous-classes continuent de fonctionner sans changement.

    • "151900920 C'est très cher pour ajouter une nouvelle choses : vous avez à ajouter une nouvelle méthode déclaration à la superclasse et potentiellement ajouter une définition de méthode à chaque sous-classe existante . Dans la pratique, la charge varie selon la méthode.

ainsi, les types de données algébriques sont fermés parce qu'un type fermé supporte bien certaines sortes d'évolution de programme. Par exemple, si vos types de données définissent un langage, il est facile d'ajouter de nouvelles passes de compilateur sans invalider anciens ou changer les données.

il est possible d'avoir des types de données" ouvertes", mais sauf dans des circonstances soigneusement contrôlées, la vérification de type devient difficile. Todd Millstein fait quelques très beau travail sur un langage de conception qui prend en charge open algébrique de types et extensible fonctions, le tout avec un type modulaire checker. J'ai trouvé son journal très agréable à lire.

78
répondu Norman Ramsey 2012-09-14 13:32:47

Si vous écrivez une fonction comme

maybeToList Nothing = []
maybeToList (Just x) = [x]

alors vous savez que cela ne va pas produire une erreur d'exécution parce que vous avez couvert tous les cas. Cela cesse d'être vrai dès que le type peut-être est extensible. Dans les cas où vous avez besoin d'un type extensible (et ils sont plus rares que vous le pensez), la solution canonique de Haskell est d'utiliser une classe de type.

15
répondu Dave 2009-05-15 21:49:42

cochez la Case "Ouvrir les types de données et fonctions accessibles" http://lambda-the-ultimate.org/node/1453

Dans les langages orientés objet, il est facile à étendre les données, en définissant de nouvelles classes, mais il est difficile d'ajouter de de nouvelles fonctions. Fonctionnelle langues, la situation est inversée: ajouter de nouvelles fonctions pose non d'étendre les données (en ajoutant nouveaux constructeurs de données) nécessite modification du code existant . Problème de soutenir les deux directions extensibilité est connu sous le nom le problème d'expression . Nous présentons open types de données et fonctions ouvertes solution légère à l'expression problème dans la langue Haskell. Le l'idée est que les constructeurs de données ouvertes types et équations des fonctions ouvertes peut apparaître dispersé à travers un programme. En particulier, ils peuvent résident dans différents modules. Le destiné sémantique est comme suit: le le programme doit se comporter comme si les données types et fonctions ont été fermés, défini dans un seul endroit. L'ordre de les équations de fonction sont déterminées par meilleure correspondance des motifs, où un modèle spécifique l'emporte sur une différence de un. Nous montrons que notre la solution est applicable au problème d'expression, générique programmation, et exceptions. Nous esquisse deux implémentations. Un simple, dérivée de la sémantique, et un basé sur des modules mutuellement récursifs que permet une compilation séparée.

11
répondu Don Stewart 2009-05-15 23:17:16

D'abord, en contrepoint de la réponse de Charlie, ce n'est pas intrinsèque à la programmation fonctionnelle. OCaml a le concept de open unions ou polymorphic variants , qui essentiellement faire ce que vous voulez.

comme pour pourquoi , je crois que ce choix a été fait pour Haskell parce que

  • cela permet de prévoir les types - ils ne sont qu'un nombre fini de constructeurs pour chacun
  • il est facile de définir vos propres types.
  • de nombreuses fonctions de Haskell sont polymorphiques, et les classes vous permettent d'étendre des types personnalisés pour s'adapter aux paramètres de fonction (pensez aux Interfaces Java).

donc si vous préférez avoir un data Color r b g = Red r | Blue b | Green g type, il est facile à faire, et vous pouvez facilement le faire agir comme un monad ou un foncteur ou de toute autre façon d'autres fonctions ont besoin.

7
répondu rampion 2017-05-23 12:17:33

quelques excellentes réponses à cette question (certes ancienne), mais je sens que je dois jeter mes quelques cents.

il est impossible que je puisse ajouter une autre option à ce type plus tard sans modifier cette déclaration. Quels sont donc les avantages de ce système? Il semble que la voie OO serait beaucoup plus extensible.

la réponse à cela, je crois, est que le genre d'extensibilité que les sommes ouvertes vous donnent est pas toujours un plus, et que, en conséquence, le fait que OO forces ceci sur vous est une faiblesse.

l'avantage des syndicats fermés est leur exhaustivité : si vous avez corrigé toutes les alternatives au moment de la compilation, alors vous pouvez être certain qu'il n'y aura pas de cas imprévus que votre code ne peut pas gérer. C'est une propriété précieuse dans de nombreux domaines de problème, par exemple, dans l'abstrait l'arbre de syntaxe pour les langues. Si vous écrivez un compilateur, les expressions de la langue tombent dans un ensemble prédéfini et fermé de sous-bases-vous ne pas veulent que les gens soient en mesure d'ajouter de nouveaux sous-bases à l'exécution que votre compilateur ne comprend pas!

en fait, compilateur ASTs sont l'un des classiques de la bande de quatre exemples motivant pour le modèle de visiteur, qui est la contrepartie OOP à des sommes fermées et exhaustive correspondance de modèle. Il est instructif de réfléchissez sur le fait que les programmeurs OO ont fini par inventer un modèle pour récupérer des sommes fermées.

de même, les programmeurs fonctionnels et procéduraux ont inventé des modèles pour obtenir l'effet des sommes. Le plus simple est l'encodage "record of functions", qui correspond aux interfaces OO. Un registre des fonctions est, en fait, un table de répartition . (Notez que les programmeurs C utilisent cette technique depuis des siècles!) Le truc c'est qu'il est très souvent, un grand nombre de fonctions possibles d'un type donné sont souvent infiniment nombreux. Donc, si vous avez un type d'enregistrement dont les champs sont des fonctions, alors cela peut facilement supporter un ensemble astronomique grand ou infini d'alternatives. De plus, étant donné que les enregistrements sont créés à l'exécution et peuvent être effectués de manière flexible en fonction des conditions d'exécution, les alternatives sont late bound .

Le Dernier commentaire que je ferais est que, dans mon esprit, OO a fait trop de gens croire que l'extensibilité est synonyme de late binding (par exemple, la possibilité d'ajouter de nouvelles sous-bases à un type à l'exécution), quand ce n'est tout simplement pas vrai en général. La liaison tardive est une technique pour l'extensibilité. Une autre technique est composition - construire des objets complexes à partir d'un vocabulaire fixe de blocs de construction et des règles pour les assembler. Le vocabulaire et les règles sont idéalement petit, mais conçu pour qu'ils ont des interactions riches qui vous permettent de construire de très complexe choses.

La programmation fonctionnelle

-et les saveurs typées de façon statique ML/Haskell en particulier-ont depuis longtemps mis l'accent sur la composition plutôt que sur la fixation tardive. Mais en réalité, les deux types de techniques existent dans les deux paradigmes, et devraient être dans la boîte à outils d'un bon programmeur.

il est également intéressant de noter que les langages de programmation eux-mêmes sont fondamentalement des exemples de composition. Un langage de programmation est finie, j'espère syntaxe simple qui vous permet de combiner ses éléments d'écrire de programme possible. (Cela revient en fait à l'exemple compilateurs/visiteur Pattern ci-dessus et le motive.)

5
répondu Luis Casillas 2015-03-24 19:10:25

une autre façon (plus ou moins) intuitive de regarder les types de données et les classes typeclasses par rapport aux classes orientées objet est la suivante:

une classe Foo dans une langue OO représente à la fois un type de béton Foo ainsi que la classe de tous les Foo - types: ceux qui sont directement ou indirectement dérivés de Foo .

en OO langues, vous venez d'arriver à implicitement programme contre la classe des Foo -types qui vous permet de "prolonger" Foo .

2
répondu Christian Klauser 2009-05-18 14:41:54

OK, par "ouvrir" ici vous voulez dire "peut être dérivé de" et pas ouvert dans le sens de Ruby et Smalltalk, où vous pouvez étendre une classe avec de nouvelles méthodes à l'exécution, Non?

en tout cas, remarquez deux choses: premièrement, dans la plupart des langues OO qui sont principalement basées sur l'héritage, il y a une façon de déclarer une classe pour restreindre sa capacité à être héritée. Java a "final", et il y a des hacks pour ça en C++. Donc sur ça, c'est juste faire comme option la valeur par défaut dans d'autres langages à objets.

Deuxièmement, vous peut encore créer un nouveau type qui utilise l'ADT fermé et ajoute d'autres méthodes ou différentes implémentations. Si vous n'êtes pas vraiment borné. Encore une fois, ils semblent formellement avoir la même force; ce que vous pouvez exprimer dans l'un peut être exprimé dans l'autre.

la vraie chose est que la programmation fonctionnelle est vraiment un paradigme différent ("modèle"). Si vous allez en elle avec la en espérant que ce soit comme une langue OO, vous serez surpris régulièrement.

1
répondu Charlie Martin 2009-05-15 21:24:17