Je ne pense pas comprendre les classes de types. J'ai lu quelque part que penser que les classes de type sont des "interfaces" (de OO) implémentées par un type est faux et trompeur. Le problème, c'est que j'ai du mal à les voir comme quelque chose de différent et que c'est faux.
Par exemple, si j'ai une classe de type (en syntaxe Haskell)
class Functor f where
fmap :: (a -> b) -> f a -> f b
En quoi est-ce différent de l'interface [1] (en syntaxe Java)
interface Functor<A> {
<B> Functor<B> fmap(Function<B, A> fn)
}
interface Function<Return, Argument> {
Return apply(Argument arg);
}
Une différence possible à laquelle je peux penser est que l'implémentation de la classe de type utilisée lors d'une invocation donnée n'est pas spécifiée, mais déterminée à partir de l'environnement, par exemple en examinant les modules disponibles pour une implémentation de ce type. Cela semble être un artefact de mise en œuvre qui pourrait être traité dans un langage OO; comme le compilateur (ou le moteur d’exécution) pourrait rechercher un wrapper / extender / monkey-patcher qui expose l’interface nécessaire sur le type.
Qu'est-ce que je rate?
[1] Notez que l' f a
argument a été supprimé fmap
car étant donné qu'il s'agit d'un langage OO, vous appelez cette méthode sur un objet. Cette interface suppose que l' f a
argument a été corrigé.
C
sans la présence de downscasts?En plus de l'excellente réponse d'Andreas, n'oubliez pas que les classes de types sont conçues pour simplifier la surcharge , ce qui affecte l'espace de noms global. Il n'y a pas de surcharge dans Haskell autre que ce que vous pouvez obtenir via les classes de types. En revanche, lorsque vous utilisez des interfaces d'objet, seules les fonctions déclarées comme prenant des arguments de cette interface devront se soucier des noms de fonction dans cette interface. Ainsi, les interfaces fournissent des espaces de noms locaux.
Par exemple, vous aviez
fmap
une interface d'objet appelée "Functor". Il serait parfaitement correct d'en avoir une autrefmap
dans une autre interface, par exemple "Structor". Chaque objet (ou classe) peut choisir l'interface à implémenter. En revanche, en Haskell, vous ne pouvez en avoir qu’unfmap
dans un contexte particulier. Vous ne pouvez pas importer les classes de type Functor et Structor dans le même contexte.Les interfaces d'objet sont plus similaires aux signatures ML standard qu'aux classes de types.
la source
Dans votre exemple concret (avec la classe de type Functor), les implémentations de Haskell et de Java se comportent différemment. Imaginez que vous ayez peut-être le type de données et que vous souhaitiez qu'il s'agisse de Functor (il s'agit d'un type de données très populaire dans Haskell, que vous pouvez également facilement implémenter en Java). Dans votre exemple Java, vous allez obliger la classe Maybe à implémenter votre interface Functor. Vous pouvez donc écrire ce qui suit (juste du pseudo-code, car j’ai un contexte c # seulement):
Notez que le
res
type a Functor, pas peut-être. Cela rend donc l'implémentation Java presque inutilisable, car vous perdez des informations de type concrètes et devez effectuer des transtypages. (au moins, j’ai échoué à écrire une telle implémentation où les types étaient toujours présents). Avec les classes de type Haskell, vous obtiendrez peut-être Inters.la source
Maybe<Int>
.