Double Possible:
Java de l'Interface et Haskell type de classe: les différences et les similitudes?Quand j'ai commencé à apprendre haskell m'a dit que typeclasses sont plus puissants que les différentes interfaces.
Un an plus tard, j'ai utilisé des interfaces et typeclasses largement et je n'ai pas encore de voir un exemple ou une explication de la façon dont ils sont différents. C'est pas une révélation qui vient naturellement, j'ai raté quelque chose d'évident, ou il n'est en fait pas vraiment de différence.
Une recherche sur internet n'a pas tourné jusqu'rien de substantiel. Si oui, avez-vous la réponse?
Réponses
Trop de publicités?Vous pouvez regarder ce à partir de plusieurs angles. D'autres personnes ne seront pas d'accord, mais je pense que la programmation orientée objet interfaces sont un bon endroit pour commencer de comprendre les classes de type (certainement par rapport à partir de rien).
Les gens aiment à rappeler que sur le plan conceptuel, les classes de type classer les types, comme dans beaucoup de jeux", l'ensemble des types à l'appui de ces opérations, le long de avec d'autres attentes qui ne peuvent pas être codées dans le langage lui-même". Il est logique, et il est parfois fait de déclarer un type de classe sans méthodes, en disant: "seulement de rendre votre type d'une instance de cette classe si elle répond à certaines exigences". Ce qui se produit très rarement avec la programmation orientée objet interfaces.
En termes de béton différences, il y a de multiples façons dans les classes de type sont plus puissants que la programmation orientée objet interfaces:
Le plus important est que typeclasses découpler la déclaration d'un type implémente une interface à partir de la déclaration du type lui-même. Avec la programmation orientée objet interfaces, vous liste les interfaces d'un type d'outils lorsque vous le définir, et il n'y a aucun moyen de les ajouter plus tard. Avec les classes de type, si vous faites un nouveau type de classe qui un type donné "le module en fonction de la hiérarchie" pourrait mettre en œuvre, mais ne connaissent pas, vous pouvez écrire une instance de la déclaration. Si vous avez un type et un type de classe de séparer des tiers qui ne connaissent pas les uns les autres, vous pouvez écrire une instance de la déclaration. En cas analogue avec la programmation orientée objet interfaces, vous êtes la plupart du temps simplement bloqué, bien que la programmation orientée objet, les langues ont évolué "design patterns" (adaptateur) pour contourner la limitation.
La prochaine plus grande (c'est subjectif, bien sûr), c'est que tandis que sur le plan conceptuel, de la programmation orientée objet interfaces sont un ensemble de méthodes qui peuvent être appelées sur les objets qui implémentent l'interface, type de classes sont un ensemble de méthodes qui peuvent être utilisées avec des types qui sont membres de la classe. La distinction est importante. Parce que le type de méthodes de la classe sont définis sur la base du type, plutôt que l'objet, il n'y a pas d'obstacle pour avoir des méthodes à objets multiples du type de paramètres (promotion de l'égalité et des opérateurs de comparaison), ou qui retourne un objet du type de la suite (diverses opérations arithmétiques), ou même des constantes de type (minimum et maximum lié). La programmation orientée objet interfaces ne peuvent tout simplement pas faire cela, et de la programmation orientée objet, les langues ont évolué les modèles de conception (par exemple, virtual clone de la méthode) pour contourner la limitation.
La programmation orientée objet interfaces ne peuvent être définis pour les types, les classes de type peuvent également être définies pour ce que l'on appelle de type "constructeurs". Les différents types de collection définie à l'aide de modèles et de génériques dans les différents dérivés du C, programmation orientée objet, les langues sont de type constructeurs: Liste prend un type T comme un argument et les constructions du type List<T>. Les classes de Type vous permettent de déclarer des interfaces de type constructeurs: dire, une opération de cartographie pour les types de collection qui appelle une fonction sur chaque élément d'une collection, et recueille les résultats dans un nouvel exemplaire de la collection, peut - être avec un autre type d'élément! Encore une fois, vous ne pouvez pas faire cela avec la programmation orientée objet interfaces.
Si un paramètre donné doit implémenter plusieurs interfaces, avec des classes de type c'est trivial à la liste de ceux qu'il doit être un membre de, avec la programmation orientée objet interfaces, vous ne pouvez spécifier qu'une seule et même interface que le type d'un pointeur ou d'une référence. Si vous avez besoin de mettre en œuvre le plus, vos seules options sont peu attrayantes, comme l'écrit l'un de l'interface dans la signature, et un casting pour les autres, ou en ajoutant des paramètres distincts pour chaque interface et en exigeant qu'elles pointent vers le même objet. Vous ne pouvez même pas le résoudre par la déclaration d'une nouvelle interface vide qui hérite de ceux dont vous avez besoin, parce qu'un type ne sont pas automatiquement être considérée comme la mise en œuvre de votre nouvelle interface juste parce qu'il met en œuvre ses ancêtres. (Si vous pouvez déclarer les implémentations après le fait, ce ne serait pas un problème, mais oui, vous ne pouvez pas le faire.)
En quelque sorte le cas inverse de celle ci-dessus, vous pouvez exiger que les deux paramètres ont des types qui implémentent une interface particulière et qu'elles soient du même type. Avec la programmation orientée objet interfaces que vous ne pouvez spécifier que la première partie.
Exemple de déclaration pour les classes de type sont plus souples. Avec la programmation orientée objet interfaces, vous pouvez seulement dire "je suis à la déclaration d'un type X, et qu'il implémente l'interface Y", où X et Y sont spécifiques. Avec les classes de type, vous pouvez dire "tous les types de Liste dont les types d'éléments de satisfaire à ces conditions sont membres de Y". (Vous pouvez aussi dire "tous les types qui sont les membres de X et de Y sont également membres du Z", bien qu'en Haskell c'est problématique pour un certain nombre de raisons.)
Soi-disant "super-classe contraintes" sont plus souples que le simple héritage de l'interface. Avec la programmation orientée objet interfaces, vous pouvez seulement dire "pour un type pour implémenter cette interface, il doit aussi mettre en place ces autres interfaces". C'est le cas le plus fréquent avec les classes de type, mais de la superclasse des contraintes aussi vous laisser dire des choses comme "SomeTypeConstructor<ThisType> doit mettre en œuvre une telle interface", ou "les résultats de ce type de fonction s'applique au type doit satisfaire à telle ou telle contrainte", et ainsi de suite.
C'est actuellement une extension du langage en Haskell (comme le sont les fonctions de type), mais vous pouvez déclarer des classes de type impliquant plusieurs types. Par exemple, un isomorphisme de classe: la classe des paires de types où vous pouvez convertir sans perte de l'un à l'autre et à l'arrière. Encore une fois, pas possible avec la programmation orientée objet interfaces.
Je suis sûr qu'il ya plus.
Il est intéressant de noter que dans la programmation orientée objet les langues qui ajouter génériques, certaines de ces limitations peuvent être effacées (quatrième, cinquième, éventuellement deuxième points).
De l'autre côté, il y a deux choses importantes qui de la programmation orientée objet interfaces peuvent faire et les classes de type natif n'est pas le cas:
Exécution dynamique de l'expédition. En programmation orientée objet, les langues, il est trivial de faire circuler et de stocker des pointeurs vers un objet qui implémente une interface, et appeler des méthodes sur elle au moment de l'exécution qui doit être réglée conformément à la dynamique, l'exécution type de l'objet. En revanche, le type de classe, les contraintes sont par défaut tous déterminés au moment de la compilation -- et peut-être étonnamment, dans la grande majorité des cas c'est tout ce dont vous avez besoin. Si vous avez besoin d'dynamique d'expédition, vous pouvez utiliser ce que l'on appelle existentielle types (qui sont actuellement une extension du langage en Haskell): une construction où il "oublie" ce que le type de l'objet, et ne se souvient (à votre choix), qu'il a obéi à un certain type de classe contraintes. À partir de ce point, il se comporte essentiellement exactement de la même manière que les pointeurs ou des références à des objets de mise en œuvre d'interfaces de programmation orientée objet, les langues, et les classes de type ont pas de déficit dans ce domaine. (Il convient de rappeler que si vous avez deux existentiels en œuvre le même type de classe, et une classe de type de méthode, qui nécessite deux paramètres de type, vous ne pouvez pas utiliser la existentiels comme paramètres, parce que vous ne pouvez pas savoir si oui ou non le existentiels eu le même type. Mais par rapport à la programmation orientée objet les langues, qui ne peut pas avoir de telles méthodes, en premier lieu, ce n'est pas une perte.)
Exécution de moulage d'objets à des interfaces. En programmation orientée objet, les langues, vous pouvez prendre un pointeur ou une référence au moment de l'exécution et de tester si il implémente une interface, et de "cast" pour que l'interface s'il n'. Les classes de Type n'est pas nativement avoir quelque chose d'équivalent (qui est en quelque sorte un avantage, car elle préserve une propriété appelée 'parametricity", mais je ne vais pas entrer dans les ici). Bien sûr, il n'y a rien qui vous empêche d'ajouter un nouveau type de classe (ou l'élargissement d'une existante) avec des méthodes de jeter des objets du type de existentiels de quel type de cours que vous souhaitez. (Vous pouvez également mettre en place une telle capacité, plus génériquement comme une bibliothèque, mais il est beaucoup plus impliqué. J'ai l'intention de le terminer et de le transférer à Hackage un jour, je le promets!)
(Je tiens à souligner que, bien que vous *pouvez* faire ces choses, beaucoup de gens considèrent l'émulation de la POO de cette façon, le mauvais style, et vous suggérons d'utiliser la plus simple des solutions, telles que des enregistrements de fonctions à la place de classes de type. Avec plein de première classe de fonctions, que l'option n'est pas moins puissant.)
Sur le plan opérationnel, la programmation orientée objet interfaces sont généralement mis en œuvre par le stockage d'un pointeur ou des pointeurs dans l'objet lui-même qui pointent vers des tableaux de pointeurs de fonction pour les interfaces de l'objet implémente. Les classes de Type sont généralement mises en œuvre (pour les langues qui ne polymorphisme par la boxe, comme Haskell, plutôt que de polymorphisme par multiinstantiation, comme en C++) par "dictionnaire de passage": le compilateur implicitement passe le pointeur de la table des fonctions (et constantes) comme un paramètre caché pour chaque fonction qui utilise le type de classe, et la fonction reçoit un exemplaire, peu importe combien d'objets sont impliqués (c'est pourquoi vous obtenez de faire les choses mentionnées dans le deuxième point ci-dessus). La mise en œuvre des existentielle types ressemble beaucoup à ce que la POO langues: un pointeur vers la classe du type de dictionnaire est stocké avec l'objet comme "preuve" que les "oubliées" de type est un membre de.
Si vous avez jamais lu sur les "concepts" proposition de C++ (comme il a été initialement proposé pour le C++11), c'est essentiellement Haskell des classes de type repensée pour C++de modèles. Parfois, je pense qu'il serait agréable d'avoir une langue qui prend tout simplement C++-avec-des concepts, des déchirures de l'orienté objet et les fonctions virtuelles moitié, nettoie la syntaxe et autres verrues, et ajoute existentielle types pour quand vous avez besoin d'exécution en fonction de leur type dynamique de l'expédition. (Mise à jour: la Rouille est essentiellement cela, avec beaucoup d'autres belles choses.)
Je suppose que vous parlez d'Haskell type de classes. Ce n'est pas vraiment la différence entre les interfaces et les classes de type. Comme le nom l'indique, un type de classe est juste une classe de types avec un ensemble commun de fonctions (ainsi que les types, si vous activez la TypeFamilies extension).
Cependant, Haskell type de système est en lui-même plus puissant que, par exemple, C#'s type de système. Qui permet d'écrire des classes de type en Haskell, qui vous ne pouvez pas vous exprimer en C#. Même un type de classe d'aussi simple que d' Functor
ne peut pas être exprimée en C#:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Le problème avec le C# est que les génériques ne peuvent pas être génériques eux-mêmes. En d'autres termes, en C# uniquement les types de genre *
peuvent être polymorphes. Haskell permet de type polymorphe constructeurs, de sorte que les types de toutes sortes peuvent être polymorphes.
C'est la raison pour laquelle beaucoup de puissantes fonctions génériques en Haskell (mapM
, liftA2
, etc.) ne peut pas être exprimée dans la plupart des langues avec le moins puissant système de type.
Voir une réponse «officielle» sur http://www.haskell.org/haskellwiki/OOP_vs_type_classes
La principale différence - ce qui en fait le type de classes beaucoup plus flexibles que les interfaces - est ce type-classes sont indépendants l'un de ses types de données et peuvent être ajoutés par la suite. Une autre différence (au moins pour Java), c'est que vous pouvez fournir des implémentations par défaut. Un exemple:
//Java
public interface HasSize {
public int size();
public boolean isEmpty();
}
Avoir cette interface est agréable, mais il n'y a pas moyen de l'ajouter à une classe existante, sans la modifier. Si vous avez de la chance, la classe est non-final (disons ArrayList
), de sorte que vous pouvez écrire une sous-classe qui implémente l'interface pour elle. Si la classe est finale (disons String
) vous êtes hors de la chance.
Comparez cela à Haskell. Vous pouvez écrire le type de classe:
--Haskell
class HasSize a where
size :: a -> Int
isEmpty :: a -> Bool
isEmpty x = size x == 0
Et vous pouvez ajouter des types de données à la classe sans les toucher:
instance HasSize [a] where
size = length
Une autre belle propriété de type-classes est l'implicite de l'invocation. E. g. si vous avez un Comparator
en Java, vous devez passer en tant que valeur explicite. En Haskell, l'équivalent Ord
peut être utilisé automatiquement, dès qu'une instance appropriée est portée.