Traits in Rust semble au moins superficiellement similaire à typeclasses in Haskell, cependant j'ai vu des gens écrire qu'il y a des différences entre eux . - Je me demandais exactement quelles sont ces différences.
Au niveau de base, il n'y a pas beaucoup de différence, mais ils sont toujours là.
Haskell décrit les fonctions ou valeurs définies dans une classe de types comme des "méthodes", tout comme les traits décrivent les méthodes OOP dans les objets qu’elles contiennent. Cependant, Haskell les traite différemment, les traitant comme des valeurs individuelles plutôt que les épingler à un objet comme OOP en amènerait un à faire. Il s'agit de la différence de niveau de surface la plus évidente.
La seule chose que Rust ne pouvait pas faire, jusqu'à récemment, était des caractères typés d'ordre supérieur , tels que le infamous Functor
et Monad
typeclasses.
Cela signifie que les traits Rust ne pouvaient décrire que ce qui est souvent appelé un "type concret", en d'autres termes, sans argument générique. Haskell, cependant, peut créer des classes de caractères d'ordre supérieur qui utilisent des types similaires à la façon dont les fonctions d'ordre supérieur utilisent d'autres fonctions, en utilisant l'une pour en décrire une autre. Nous n'avons pas pu implémenter les classes de types susmentionnées dans Rust, car elles ne prenaient pas en charge cette fonctionnalité considérée comme basique, voire essentielle, dans Haskell. Heureusement, cela a été implémenté récemment avec éléments associés . * Cependant, ce n'est pas rouille idiomatique, et est généralement considéré comme un "hack".
Il est également mentionnable, comme indiqué dans les commentaires, que GHC (le principal compilateur de Haskell) prend en charge d'autres options pour les classes de types, y compris les classes de types multi-paramètres (c'est-à-dire de nombreux types impliqués) et dépendances fonctionnelles) , une belle option qui permet des calculs au niveau du type et mène à familles de types . À ma connaissance, Rust n'a ni funDeps ni familles de types, bien que cela puisse le devenir à l'avenir. †
Dans l'ensemble, bien que les traits et les classes de types puissent sembler équivalents en surface, ils ont beaucoup de différences lorsqu'ils sont comparés plus profondément, lorsque l'on considère de nombreuses qualités.
* Sur une note secondaire, Swift, malgré ses traits, n'a pas un tel mécanisme de typage supérieur. Une future mise à jour de la langue, peut-être?
† Un bel article sur les classes de caractères de Haskell (y compris celles de type supérieur) peut être trouvé ici , et un aussi bon sur les traits de Rust est conservé ici , bien que ce soit malheureusement un peu périmé.
Je pense que les réponses actuelles négligent les différences les plus fondamentales entre les traits Rust et les classes de type Haskell. Ces différences ont à voir avec la façon dont les traits sont liés aux constructions de langage orienté objet. Pour plus d'informations à ce sujet, voir le livre de rouille .
Une déclaration de trait crée un type de trait . Cela signifie que vous pouvez déclarer des variables d'un tel type (ou plutôt des références du type). Vous pouvez également utiliser des types de trait comme paramètres sur des fonctions, des champs de structure et des instanciations de paramètres de type.
Une variable de référence de trait peut au moment de l'exécution contenir des objets de différents types, tant que le type d'exécution de l'objet référencé implémente le trait.
// The shape variable might contain a Square or a Circle,
// we don't know until runtime
let shape: &Shape = get_unknown_shape();
// Might contain different kinds of shapes at the same time
let shapes: Vec<&Shape> = get_shapes();
Ce n'est pas ainsi que fonctionnent les classes de types. Les classes de types ne créent aucun type , vous ne pouvez donc pas déclarer de variables avec le nom de classe. Les classes de type agissent comme des limites sur les paramètres de type, mais les paramètres de type doivent être instanciés avec un type concret, pas la classe de type elle-même.
Vous ne pouvez pas avoir une liste de différentes choses de différents types qui implémentent la même classe de type. (Au lieu de cela, les types existentiels sont utilisés dans Haskell pour exprimer une chose similaire.) Remarque 1
Les méthodes de trait peuvent être distribuées dynamiquement . Ceci est fortement lié aux éléments décrits dans la section ci-dessus.
La répartition dynamique signifie que le type d'exécution de l'objet qu'un point de référence est utilisé pour déterminer la méthode qui est appelée via la référence.
let shape: &Shape = get_unknown_shape();
// This calls a method, which might be Square.area or
// Circle.area depending on the runtime type of shape
print!("Area: {}", shape.area());
Encore une fois, des types existentiels sont utilisés pour cela dans Haskell.
Il me semble que les traits sont essentiellement le même concept que les classes de types. De plus, ils ont la fonctionnalité d'interfaces orientées objet.
D'un autre côté, les classes de types de Haskell sont plus avancées, car Haskell a par exemple des types de type supérieur et des extensions comme les classes de types multi-paramètres. Mais je pense qu'ils sont essentiellement les mêmes.
Note 1: Les versions récentes de Rust ont une mise à jour pour différencier l'utilisation des noms de trait comme types et l'utilisation des noms de trait comme limites. Dans un type de trait le nom est préfixé par le mot clé dyn
. Voir par exemple ceci réponse pour plus d'informations.
Les "traits" de Rust sont analogues aux classes de types de Haskell.
La principale différence avec Haskell est que les traits n'interviennent que pour les expressions avec notation par points, c'est-à-dire de la forme a.foo (b).
Les classes de types Haskell s'étendent aux types d'ordre supérieur. Rust traits seulement ne prennent pas en charge les types d'ordre supérieur car ils sont absents de la langue entière, c'est-à-dire que ce n'est pas une différence philosophique entre les traits et les classes de types