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

Pendant que j’apprends le Haskell, j’ai remarqué sa classe de type , qui est supposé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 plutôt proche de l’interface de Java (en citant la page Interface de Java (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 éléments 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 dans Java, ou peut-être sont-elles fondamentalement différentes?

EDIT: J’ai remarqué que même haskell.org admet qu’ils sont similaires . Si elles sont si similaires (ou sont-elles?), Alors pourquoi type class est-il traité avec un tel battage?

PLUS EDIT: Wow, tant de bonnes réponses! Je suppose que je devrai laisser la communauté décider quel est le meilleur. Cependant, tout en lisant les réponses, tous semblent simplement dire “il y a beaucoup de choses que la classe peut faire alors que l’interface ne peut ou ne doit pas faire face aux génériques” . Je ne peux pas m’empêcher de m’interroger, y a-t-il quelque chose que les interfaces peuvent faire alors que les classes de caractères ne peuvent pas? En outre, j’ai remarqué que Wikipedia affirme que la typeclass a été inventée à l’origine dans le document de 1989 * “Comment rendre le polymorphism ad hoc moins ad hoc”, alors que Haskell est encore dans sa phase, alors que le projet Java a été lancé en 1991 Donc, peut-être qu’à la place de typeclass ressemble-t-elle à des interfaces, à l’inverse, les interfaces ont été influencées par la classe? Existe-t-il des documents / papiers à l’appui ou réfutent cela? Merci pour toutes les réponses, elles sont toutes très instructives!

Merci pour toutes les entrées!

    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 qui ne contient pas t ). En effet, avec le type de relation d’inheritance en Java et dans les langages similaires, la méthode appelée dépend du type d’object auquel elle est appelée, et rien d’autre.

    Cela signifie qu’il est vraiment difficile de faire des choses comme add :: t -> t -> t avec une interface, où il est polymorphe sur plusieurs parameters, car l’interface ne permet pas de spécifier le type d’argument et le type de retour de la méthode est du même type que le type d’object sur lequel elle est appelée (c’est-à-dire le type “self”). Avec Generics, il existe de nombreuses façons de faire semblant en créant une interface avec un paramètre générique qui devrait être du même type que l’object lui-même, comme cela est Comparable , où vous devez utiliser les Foo implements Comparable pour que le type compareTo(T otherobject) ait le type t -> t -> Ordering . Mais cela nécessite toujours que le programmeur suive cette règle, et provoque également des maux de tête lorsque des personnes souhaitent créer une fonction utilisant cette interface, elles doivent avoir des parameters de type génériques récursifs.

    De plus, vous n’aurez pas de choses comme empty :: t car vous n’appelez pas de fonction ici, donc ce n’est pas une méthode.

    Ce qui est similaire entre les interfaces et les classes de type, c’est qu’elles nomment et décrivent un ensemble d’opérations connexes. Les opérations elles-mêmes sont décrites par leurs noms, leurs entrées et leurs sorties. De même, de nombreuses implémentations de ces opérations pourraient différer dans leur implémentation.

    Avec cela, voici quelques différences notables:

    • Les méthodes d’interfaces sont toujours associées à une instance d’object. En d’autres termes, il existe toujours un paramètre ‘this’ implicite qui est l’object 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 dans le cadre 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.
    • Une classe de type vous permet de définir une implémentation par défaut pour toutes les opérations définies. Les interfaces sont uniquement des spécifications de type, pas d’implémentation.

    En général, je pense qu’il est juste de dire que les classes de type 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é pouvoir implémenter une interface pour un type dans une bibliothèque compilée? Celles-ci sont faciles à réaliser avec les classes de type.

    Les classes de type ont été créées de manière structurée pour exprimer le “polymorphism ad hoc”, qui est fondamentalement le terme technique utilisé pour désigner les fonctions surchargées . Une définition de classe de type ressemble à ceci:

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

    Cela signifie que lorsque vous utilisez la fonction foo pour 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 des opérateurs dans des langages comme C, sauf plus flexible et généralisé.

    Les interfaces ont un objective similaire dans les langages OO, mais le concept sous-jacent est quelque peu différent. Les langages OO comportent une notion intégrée de hiérarchies de types que Haskell ne possède pas, ce qui complique les choses car les interfaces peuvent impliquer à la fois une surcharge par sous-typage (méthodes d’appel sur les instances appropriées, sous-types implémentant les interfaces). et par dissortingbution à base de type plat (puisque deux classes implémentant une interface peuvent ne pas avoir une super-classe 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 considérer les 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 répartition ont des moyens de répartition beaucoup plus flexibles – les interfaces ne s’appliquent généralement qu’à la classe implémentée, alors que les classes de type sont définies pour un type , qui peut apparaître n’importe où dans la signature des fonctions. L’équivalent de cela dans les interfaces OO permettrait à l’interface de définir des moyens de transmettre un object 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 requirejs dans le contexte appelant, définissent des méthodes qui prendre des arguments du même type que la classe implémentant l’interface, et diverses autres choses qui ne traduisent pas du tout.

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

    J’ai lu les réponses ci-dessus. Je pense pouvoir 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 y a 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 ou auto-type. Les traits de Scala font aussi bien (this.type). Notez que les “self types” dans Scala sont une fonctionnalité totalement indépendante. Java / C # nécessite une solution de contournement avec les génériques pour approcher ce comportement.
    • Les classes de type Haskell vous permettent de définir des fonctions (y compris des constantes) sans paramètre de type “this”. Les interfaces Java / C # et les caractéristiques Scala nécessitent un paramètre d’entrée “this” sur toutes les fonctions.
    • Les classes de type Haskell vous permettent de définir les implémentations par défaut des fonctions. Il en va de même pour les traits Scala et les interfaces Java 8+. C # peut s’approcher de quelque chose comme ça avec les méthodes d’extensions.

    Regardez le discours de Phillip Wadler Faith, Evolution et Programming Languages . Wadler a travaillé sur Haskell et a été un consortingbuteur majeur de Java Generics.

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

    Les exemples énumérés dans l’article sont: le problème de l’expression, le problème de l’intégration du cadre, le problème de l’extensibilité indépendante, la tyrannie de la décomposition dominante, la dispersion et l’enchevêtrement.

    Dans Master mind of Programming , il y a une interview à propos de Haskell avec Phil Wadler, l’inventeur des classes de types, qui expliquent les similitudes entre les interfaces en Java et les classes de types en Haskell:

    Une méthode Java comme:

      public static > 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 xy = 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.

    Je ne peux pas parler au “battage médiatique”, si cela semble bien aller. Mais oui, les classes de type sont similaires dans de nombreux domaines. Une différence que je peux penser est que cela vous permet de fournir un comportement pour certaines opérations de la classe de type:

     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, identiques (==) , et non-égales (/=) , 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 ne deviez en fournir qu’un) et vice versa.

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

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

    et la manière dont cela fonctionnerait serait que vous ne deviez fournir qu’une de ces méthodes pour implémenter l’interface. Donc, je dirais que la possibilité de fournir une sorte d’implémentation partielle du comportement que vous voulez au niveau de l’ interface est une différence.

    Ils sont similaires (lisez: ont un usage similaire), et probablement implémentés de la même façon: 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 une table de fonctions , pas de méthodes . Les méthodes sont liées à un object, les classes Haskell ne le sont pas.

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

    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 gratuit à plusieurs endroits. Il est donc facile de définir Num comme une classe de caractères. Ainsi, dans Haskell, nous obtenons les avantages syntaxiques de la surcharge d’opérateur sans avoir d’opérateurs surchargés de magie, mais uniquement des classes de caractères 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 => Ssortingng -> a . Donc, si vous disposez d’assez d’autres informations de type sur la manière 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 toute liste de choses lisibles. Je ne pense pas que ce soit tout à fait possible en Java.

    Et tout ceci n’est que des classes de parameters standard à un seul paramètre sans aucune supercherie. Une fois que nous avons introduit des classes de types multi-parameters, alors un tout nouveau monde de possibilités s’ouvre, et encore plus avec les dépendances fonctionnelles et les familles de types, ce qui vous permet d’intégrer beaucoup plus d’informations et de calculs dans le système de types.