Interface de Java et classe de types de Haskell: différences et similitudes?

112

Pendant que j'apprenais Haskell, j'ai remarqué sa classe de type , qui est censée être une grande invention originaire de Haskell.

Cependant, dans la page Wikipedia sur la classe de type :

Le programmeur définit une classe de type en spécifiant un ensemble de noms de fonctions ou de constantes, ainsi que leurs types respectifs, qui doivent exister pour chaque type appartenant à la classe.

Ce qui me semble assez proche de l'interface de Java (citant la page Interface de Wikipédia (Java) ):

Une interface dans le langage de programmation Java est un type abstrait utilisé pour spécifier une interface (au sens générique du terme) que les classes doivent implémenter.

Ces deux aspects semblent assez similaires: la classe de type limite le comportement d'un type, tandis que l'interface limite le comportement d'une classe.

Je me demande quelles sont les différences et les similitudes entre la classe de type dans Haskell et l'interface en Java, ou peut-être sont-elles fondamentalement différentes?

EDIT: J'ai remarqué que même haskell.org admet qu'ils sont similaires . S'ils sont si similaires (ou le sont-ils?), Alors pourquoi la classe de type est-elle traitée avec un tel battage médiatique?

PLUS EDIT: Wow, tant de bonnes réponses! Je suppose que je vais devoir laisser la communauté décider laquelle est la meilleure. Cependant, en lisant les réponses, tous semblent simplement dire qu ' "il y a beaucoup de choses que typeclass peut faire alors que l'interface ne peut pas ou doit faire face aux génériques" . Je ne peux pas m'empêcher de me demander, y a-t-il quelque chose que les interfaces peuvent faire alors que les classes de types ne le peuvent pas? De plus, j'ai remarqué que Wikipédia affirme que typeclass a été inventé à l'origine dans l'article de 1989 * «Comment rendre le polymorphisme ad hoc moins ad hoc», alors que Haskell est toujours dans son berceau, tandis que le projet Java a été lancé en 1991 et publié pour la première fois en 1995 Alors peut-être qu'au lieu que typeclass soit similaire aux interfaces, c'est l'inverse, que les interfaces ont été influencées par typeclass?Y a-t-il des documents / papiers qui soutiennent ou réfutent cela? Merci pour toutes les réponses, elles sont toutes très instructives!

Merci pour toutes les contributions!

zw324
la source
3
Non, il n'y a vraiment rien que les interfaces puissent faire que les classes de types ne peuvent pas faire, avec l'avertissement majeur que les interfaces apparaissent généralement dans des langages qui ont des fonctionnalités intégrées introuvables dans Haskell. Si des classes de types avaient été ajoutées à Java, elles pourraient également utiliser ces fonctionnalités.
CA McCann
8
Si vous avez plusieurs questions, vous devriez poser plusieurs questions, ne pas essayer de les regrouper toutes en une seule question. Quoi qu'il en soit, pour répondre à votre dernière question: l'influence principale de Java est Objective-C (et non C ++ comme cela est souvent faussement rapporté), dont les principales influences sont à leur tour Smalltalk et C.Les interfaces de Java sont une adaptation des protocoles d'Objective-C qui sont à nouveau un formalisation de l'idée de protocole en OO, qui à son tour est basée sur l'idée de protocoles en réseau, en particulier l'ARPANet. Tout cela s'est passé bien avant l'article que vous avez cité. ...
Jörg W Mittag
1
... L'influence de Haskell sur Java est venue beaucoup plus tard et se limite aux génériques, qui ont été, après tout, co-conçus par l'un des concepteurs de Haskell, Phil Wadler.
Jörg W Mittag
5
Il s'agit d'un article Usenet de Patrick Naughton, l'un des concepteurs originaux de Java: Java était fortement influencé par Objective-C et non par C ++ . Malheureusement, il est si ancien que la publication originale n'apparaît même pas dans les archives de Google.
Jörg W Mittag
8
Il y a une autre question qui a été fermée comme une copie exacte de celle-ci, mais elle a une réponse beaucoup plus approfondie: stackoverflow.com/questions/8122109/...
Ben

Réponses:

50

Je dirais qu'une interface est un peu comme une classe de type SomeInterface toù toutes les valeurs ont le type t -> whatever(où whateverne contient pas t). En effet, avec le type de relation d'héritage en Java et dans les langages similaires, la méthode appelée dépend du type d'objet sur lequel elle est appelée, et rien d'autre.

Cela signifie qu'il est vraiment difficile de faire des choses comme add :: t -> t -> tavec une interface, où elle est polymorphe sur plus d'un paramètre, car il n'y a aucun moyen pour l'interface de spécifier que le type d'argument et le type de retour de la méthode sont du même type que le type de l'objet sur lequel il est appelé (c'est-à-dire le type "self"). Avec Generics, il existe en quelque sorte des moyens de simuler cela en créant une interface avec un paramètre générique qui devrait être du même type que l'objet lui-même, comme comment le Comparable<T>faire, où vous êtes censé utiliser Foo implements Comparable<Foo>pour que le compareTo(T otherobject)type de soit le type t -> t -> Ordering. Mais cela oblige toujours le programmeur à suivre cette règle, et provoque également des maux de tête lorsque les gens veulent créer une fonction qui utilise cette interface, ils doivent avoir des paramètres de type générique récursifs.

De plus, vous n'aurez pas des choses comme empty :: tparce que vous n'appelez pas une fonction ici, donc ce n'est pas une méthode.

newacct
la source
1
Les traits Scala (essentiellement les interfaces) autorisent ce.type afin que vous puissiez retourner ou accepter des paramètres du "type self". Scala a une fonction complètement distincte qu'ils appellent "types d'individu" btw qui n'a rien à voir avec cela. Rien de tout cela n'est une différence conceptuelle, juste une différence d'implémentation.
terre battue
45

Ce qui est similaire entre les interfaces et les classes de types, c'est qu'elles nomment et décrivent un ensemble d'opérations associées. Les opérations elles-mêmes sont décrites par leurs noms, entrées et sorties. De même, il peut y avoir de nombreuses implémentations de ces opérations qui différeront probablement dans leur mise en œuvre.

Avec cela à l'écart, voici quelques différences notables:

  • Les méthodes d'interfaces sont toujours associées à une instance d'objet. En d'autres termes, il y a toujours un paramètre implicite «this» qui est l'objet sur lequel la méthode est appelée. Toutes les entrées d'une fonction de classe de type sont explicites.
  • Une implémentation d'interface doit être définie comme faisant partie de la classe qui implémente l'interface. Inversement, une classe de type 'instance' peut être définie complètement séparée de son type associé ... même dans un autre module.

En général, je pense qu'il est juste de dire que les classes de types sont plus puissantes et flexibles que les interfaces. Comment définiriez-vous une interface pour convertir une chaîne en une valeur ou une instance du type d'implémentation? Ce n'est certes pas impossible, mais le résultat ne serait ni intuitif ni élégant. Avez-vous déjà souhaité qu'il soit possible d'implémenter une interface pour un type dans une bibliothèque compilée? Ces deux méthodes sont faciles à réaliser avec les classes de types.

Daniel Pratt
la source
1
Comment étendriez-vous les classes de types? Les classes de types peuvent-elles étendre d'autres classes de types, tout comme les interfaces peuvent étendre les interfaces?
CMCDragonkai
10
Il pourrait être utile de mettre à jour cette réponse à la lumière des implémentations par défaut de Java 8 dans les interfaces.
Réintégrer Monica le
1
@CMCDragonkai Oui, vous pouvez par exemple dire "class (Foo a) => Bar a where ..." pour spécifier que la classe de type Bar étend la classe de type Foo. Comme Java, Haskell a ici un héritage multiple.
Réintégrer Monica le
Dans ce cas, la classe de type n'est-elle pas la même que les protocoles de Clojure mais avec la sécurité de type?
nawfal
24

Les classes de types ont été créées comme une manière structurée d'exprimer le «polymorphisme ad hoc», qui est essentiellement le terme technique pour les fonctions surchargées . Une définition de classe de type ressemble à ceci:

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

Cela signifie que, lorsque vous utilisez appliquer la fonction fooà certains arguments d'un type qui appartiennent à la classe Foobar, il recherche une implémentation foospécifique à ce type et l'utilise. Ceci est très similaire à la situation avec la surcharge d'opérateurs dans des langages comme C ++ / C #, sauf plus flexible et généralisé.

Les interfaces servent un objectif similaire dans les langages OO, mais le concept sous-jacent est quelque peu différent; Les langages OO sont livrés avec une notion intégrée de hiérarchies de types que Haskell n'a tout simplement pas, ce qui complique les choses à certains égards car les interfaces peuvent impliquer à la fois une surcharge par sous-typage (c'est-à-dire, appeler des méthodes sur des instances appropriées, des sous-types implémentant des interfaces que leurs supertypes font) et par répartition basée sur le type plat (puisque deux classes implémentant une interface peuvent ne pas avoir une superclasse commune qui l'implémente également). Compte tenu de l'énorme complexité supplémentaire introduite par le sous-typage, je suggère qu'il soit plus utile de penser aux classes de types comme une version améliorée de fonctions surchargées dans un langage non-OO.

Il convient également de noter que les classes de types ont des moyens d'envoi beaucoup plus flexibles - les interfaces ne s'appliquent généralement qu'à la classe unique qui les implémente, tandis que les classes de types sont définies pour un type , qui peut apparaître n'importe où dans la signature des fonctions de la classe. L'équivalent de ceci dans les interfaces OO serait de permettre à l'interface de définir des moyens de passer un objet de cette classe à d'autres classes, de définir des méthodes statiques et des constructeurs qui sélectionneraient une implémentation en fonction du type de retour requis dans le contexte d'appel, de définir des méthodes qui prenez des arguments du même type que la classe implémentant l'interface, et diverses autres choses qui ne se traduisent pas vraiment du tout.

En bref: ils servent des objectifs similaires, mais leur mode de fonctionnement est quelque peu différent, et les classes de types sont à la fois beaucoup plus expressives et, dans certains cas, plus simples à utiliser car elles travaillent sur des types fixes plutôt que sur des éléments d'une hiérarchie d'héritage.

CA McCann
la source
J'avais du mal à comprendre les hiérarchies de types dans Haskell, que pensez-vous des systèmes de types de types comme Omega? Pourraient-ils simuler des hiérarchies de types?
CMCDragonkai
@CMCDragonkai: Je ne connais pas assez Omega pour vraiment dire, désolé.
CA McCann
16

J'ai lu les réponses ci-dessus. Je sens que je peux répondre un peu plus clairement:

Une "classe de type" Haskell et une "interface" Java / C # ou un "trait" Scala sont fondamentalement analogues. Il n'y a pas de distinction conceptuelle entre eux mais il existe des différences de mise en œuvre:

  • Les classes de type Haskell sont implémentées avec des «instances» distinctes de la définition du type de données. En C # / Java / Scala, les interfaces / traits doivent être implémentés dans la définition de classe.
  • Les classes de type Haskell vous permettent de renvoyer un type de ce type ou self. Les traits Scala font aussi bien (this.type). Notez que les «types d'auto» dans Scala sont une fonctionnalité totalement indépendante. Java / C # nécessite une solution de contournement désordonnée avec des génériques pour se rapprocher de ce comportement.
  • Les classes de type Haskell vous permettent de définir des fonctions (y compris des constantes) sans paramètre de type d'entrée "this". Les interfaces Java / C # et les traits Scala nécessitent un paramètre d'entrée «this» sur toutes les fonctions.
  • Les classes de type Haskell vous permettent de définir des implémentations par défaut pour les fonctions. Tout comme les traits Scala et les interfaces Java 8+. C # peut approcher quelque chose comme ça avec des méthodes d'extensions.
argile
la source
2
Juste pour ajouter un peu de littérature à ces points de réponses, j'ai lu On (Haskell) Type Classes and (C #) Interfaces today, qui se compare également aux interfaces C # au lieu de Javas, mais devrait bien donner une compréhension du côté conceptuel en disséquant la notion d'un interface à travers les frontières linguistiques.
daniel.kahlenberg
Je pense que certaines approximations pour des choses comme les implémentations par défaut sont faites plutôt plus efficacement en C # avec des classes abstraites peut-être?
Arwin
12

Dans Master minds of Programming , il y a une interview sur Haskell avec Phil Wadler, l'inventeur des classes de types, qui explique les similitudes entre les interfaces en Java et les classes de types en Haskell:

Une méthode Java comme:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

est très similaire à la méthode Haskell:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

Ainsi, les classes de types sont liées aux interfaces, mais la vraie correspondance serait une méthode statique paramétrée avec un type comme ci-dessus.

Ewernli
la source
Cela devrait être la réponse, car selon la page d 'accueil de Phil Wadler , il était le concepteur principal de Haskell, en même temps il a également conçu l' extension Generics pour Java, qui a ensuite été incluse dans le langage lui - même.
Wong Jia Hau
10

Regardez le discours de Phillip Wadler sur la foi, l'évolution et les langages de programmation . Wadler a travaillé sur Haskell et a été un contributeur majeur à Java Generics.

mcandre
la source
1
Le truc pertinent est quelque part autour de 25m (bien que le début soit assez drôle).
Fred Foo
8

Lisez Extension logicielle et intégration avec les classes de types où des exemples sont donnés sur la façon dont les classes de types peuvent résoudre un certain nombre de problèmes que les interfaces ne peuvent pas.

Les exemples énumérés dans l'article sont:

  • le problème de l'expression,
  • le problème d'intégration du framework,
  • le problème de l'extensibilité indépendante,
  • la tyrannie de la décomposition dominante, de la dispersion et de l'enchevêtrement.
Channing Walton
la source
3
Link est mort dessus. Essayez plutôt celui-ci .
Justin Leitgeb
1
Et bien maintenant, celui-là aussi est mort. Essayez celui-ci
RichardW
7

Je ne peux pas parler du niveau "hype", si cela semble bien. Mais oui, les classes de types sont similaires à bien des égards. Une différence que je peux penser est que ce Haskell vous pouvez fournir un comportement pour certains de la classe de type opérations :

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

qui montre qu'il y a deux opérations, égales (==)et non égales (/=), pour les choses qui sont des instances de la Eqclasse de type. Mais l'opération non-égale est définie en termes d'égaux (de sorte que vous n'avez qu'à en fournir un), et vice versa.

Donc en Java probablement pas légal, ce serait quelque chose comme:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

et la façon dont cela fonctionnerait est que vous n'avez besoin que de fournir une de ces méthodes pour implémenter l'interface. Je dirais donc que la possibilité de fournir une sorte d'implémentation partielle du comportement souhaité au niveau de l' interface est une différence.

Chris
la source
6

Elles sont similaires (lire: ont un usage similaire), et probablement implémentées de la même manière: les fonctions polymorphes dans Haskell prennent sous le capot une 'vtable' listant les fonctions associées à la classe de types.

Cette table peut souvent être déduite au moment de la compilation. C'est probablement moins vrai en Java.

Mais c'est un tableau de fonctions , pas de méthodes . Les méthodes sont liées à un objet, les classes de types Haskell ne le sont pas.

Voyez-les plutôt comme les génériques de Java.

Alexandre C.
la source
3

Comme le dit Daniel, les implémentations d'interface sont définies séparément des déclarations de données. Et comme d'autres l'ont souligné, il existe un moyen simple de définir des opérations qui utilisent le même type libre à plusieurs endroits. Il est donc facile à définir Numcomme une classe de types. Ainsi, dans Haskell, nous obtenons les avantages syntaxiques de la surcharge d'opérateurs sans avoir réellement d'opérateurs surchargés magiques - juste des classes de types standard.

Une autre différence est que vous pouvez utiliser des méthodes basées sur un type, même si vous n'avez pas encore de valeur concrète de ce type!

Par exemple read :: Read a => String -> a,. Donc, si vous avez suffisamment d'autres informations de type sur la façon dont vous utiliserez le résultat d'une "lecture", vous pouvez laisser le compilateur déterminer quel dictionnaire utiliser pour vous.

Vous pouvez également faire des choses comme instance (Read a) => Read [a] where...ce qui vous permet de définir une instance de lecture pour toute liste d'éléments lisibles. Je ne pense pas que ce soit tout à fait possible en Java.

Et tout cela n'est que des classes de types standard à paramètre unique sans aucune supercherie. Une fois que nous introduisons les classes de types à paramètres multiples, un tout nouveau monde de possibilités s'ouvre, et plus encore avec des dépendances fonctionnelles et des familles de types, qui vous permettent d'intégrer beaucoup plus d'informations et de calculs dans le système de types.

sclv
la source
1
Les classes de type normales sont destinées aux interfaces comme les classes de types à paramètres multiples sont destinées à la distribution multiple dans la POO; vous gagnez une augmentation correspondante de la puissance du langage de programmation et du mal de tête du programmeur.
CA McCann