web-dev-qa-db-fra.com

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

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

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

Le programmeur définit une classe de types 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 plutôt proche de l'interface Java (en citant page Interface (Java) de Wikipedia ):

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 sont 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 dans Java, ou peut-être sont-elles fondamentalement différentes?

EDIT: J'ai remarqué 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 traitée avec un tel battage médiatique?

PLUS DE MODIFICATION: Wow, tant de bonnes réponses! Je suppose que je devrai laisser la communauté décider quelle est la meilleure. Cependant, en lisant les réponses, tous semblent simplement dire que "il y a beaucoup de choses que la classe de type 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 ne peuvent pas? * "Comment rendre le polymorphisme ad hoc moins ad hoc", alors que Haskell est toujours dans son berceau, tandis que Java a été lancé en 1991 et publié pour la première fois en 1995. Donc Peut-être qu'au lieu que la classe de types soit similaire aux interfaces, c'est l'inverse, que les interfaces ont été influencées par la classe de types? Y a-t-il des documents/papiers qui soutiennent ou réfutent cela? réponses, elles sont toutes très éclairantes!

Merci pour toutes les contributions!

94
zw324

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

Cela signifie qu'il est vraiment difficile de créer des choses comme add :: t -> t -> t Avec une interface, où elle est polymorphe sur plusieurs paramètres, 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 les même type que le type de l'objet auquel il est appelé (ie le type "self"). Avec les génériques, il existe plusieurs façons 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 la façon dont Comparable<T> Le fait, où vous êtes censé utiliser Foo implements Comparable<Foo> Pour que le type compareTo(T otherobject) ait 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écursif.

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

41
newacct

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 connexes. Les opérations elles-mêmes sont décrites via 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 implémentation.

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 "ce" 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 distincte de son type associé ... même dans un autre module.
  • Une classe de type vous permet de définir une implémentation "par défaut" pour n'importe laquelle des opérations définies. Les interfaces sont strictement des spécifications de type uniquement, aucune implémentation.

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 certainement 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? Ce sont tous deux faciles à réaliser avec les classes de type.

38
Daniel Pratt

Les classes de types ont été créées pour exprimer de manière structurée le "polymorphisme ad hoc", qui est essentiellement le terme technique pour 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 la fonction foo à certains arguments d'un type appartenant à la classe Foobar, elle recherche une implémentation de foo spécifique à ce type, et l'utilise. Ceci est très similaire à la situation de surcharge d'opérateur dans des langages comme C++/C #, sauf plus flexible et généralisé.

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

Il convient également de noter que les classes de type ont des moyens de distribution beaucoup plus flexibles - les interfaces ne s'appliquent généralement qu'à la seule classe qui l'implémente, tandis que les classes de type 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 permettrait à 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 de quoi - type de retour est requis dans le contexte d'appel, définir des méthodes qui prennent 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 façon de travailler est quelque peu différente, et les classes de types sont à la fois beaucoup plus expressives et, dans certains cas, plus simples à utiliser en raison du travail sur des types fixes plutôt que sur des éléments d'une hiérarchie d'héritage.

21
C. A. McCann

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 elles mais il existe des différences d'implémentation:

  • 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 auto. Scala les traits font aussi bien (this.type). Notez que les "self types" dans Scala sont une fonctionnalité complètement indépendante. Java/C # nécessite une solution de contournement compliquée) avec des génériques pour approximer ce comportement.
  • Les classes de type Haskell vous permettent de définir des fonctions (y compris les constantes) sans paramètre de type "this" en entrée. 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. Faites ainsi Scala traits et Java 8+ interfaces. C # peut approximer quelque chose comme ça avec des méthodes d'extensions).
13
clay

Regardez la conférence de Phillip Wadler Faith, Evolution, and Programming Languages . Wadler a travaillé sur Haskell et a été un contributeur majeur à Java Generics.

9
mcandre

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 dans Java et les classes de types dans 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 type sont liées aux interfaces, mais la vraie correspondance serait une méthode statique paramétrée avec un type comme ci-dessus.

9
ewernli

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

Les exemples énumérés dans le document sont:

  • le problème d'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'emmêlement.
8
Channing Walton

Ils sont similaires (lire: ont une utilisation similaire) et probablement mis en œuvre de la même manière: les fonctions polymorphes dans Haskell prennent sous le capot une "table" répertoriant les fonctions associées à la classe de types.

Ce tableau peut souvent être déduit au moment de la compilation. C'est probablement moins vrai en Java.

Mais ceci est un tableau de fonctions, pas 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.

5
Alexandre C.

Je ne peux pas parler du niveau "hype", si cela semble très bien ainsi. Mais oui, les classes de types sont similaires à bien des égards. Une différence à laquelle je peux penser est que c'est Haskell que vous pouvez fournir un comportement pour certaines des classes de type opérations:

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

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

Donc, en Java probablement non 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'auriez qu'à 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 interface est une différence.

5
Chris

Comme Daniel le dit, les implémentations d'interface sont définies séparément à partir 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 de définir Num comme 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 le dictionnaire à utiliser pour vous.

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

Et tout cela n'est que des classes de caractères à paramètre unique standard sans aucune astuce. Une fois que nous introduisons des classes de caractères multi-paramètres, 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.

3
sclv