2010-11-26 37 views
5

Pourquoi existe-t-il différentes notations pour exprimer l'héritage? Dans les génériques, je dois utiliser le <: -operator - dans un héritage de classe normal, je dois utiliser le mot-clé extends.Différentes notations pour exprimer l'héritage

Par exemple, je dois écrire ceci:

class X[A <: B] extends Y 

Mais pourquoi ne pas écrire quelque chose comme ceci:

class X[A <: B] <: Y 

ou

class X[A extends B] extends Y // like Java 

Je n'ai aucun problème avec la notation actuelle , mais je veux savoir s'il y a une raison de noter la hiérarchie des types de génériques d'une manière différente.

Répondre

12

Eh bien, il n'y a rien arrêter Scala de le faire, mais, en fait, ils n'expriment pas la même chose du tout . Et, en fait, vous pouvez le voir en Java, où vous pouvez écrire X super Y, mais vous ne pouvez pas dire class X super Y.

Le mot-clé extends exprime une relation entre classes, l'un d'héritage. D'autre part, <: et >: expriment une relation entre types, l'une des frontières. Quand je dis X <: Y, alors il est valide à la fois pour X et Y pour être String, par exemple, alors que String extends String serait sans signification. Il est également le cas que List[String] <: List[AnyRef], bien que, encore une fois, List[String] extends List[AnyRef] est sans signification. Et, juste pour faire le point, c'est pas vrai que Set[String] <: Set[AnyRef]. Dans tous ces exemples que je viens de donner, nous parlons de la même classe, mais pas nécessairement, à peu près le même type.

Et, bien sûr, il existe d'autres relations entre types, telles que les limites d'affichage (<%) et les limites de contexte (:).

Donc, juste parce que extends implique <:, il ne suit pas que <: implique extends, qui, seul, est une raison suffisante pour éviter d'utiliser le même mot clé. Ajoutez à cela les autres relations entre les types, et vous avez à peu près une affaire fermée.

+1

++ 1 pour parler de la différence entre les classes et les types –

+0

en ce qui concerne les classes et les types en tant que choses différentes, il est logique d'utiliser des notations différentes. Merci pour l'explication. – sschaef

7

Il devient un peu plus évident lorsque vous étendez (sans jeu de mots) votre exemple au-delà de ce cas simple.

héritage multiple:

class C[A] extends X with Y with Z 

Mixins:

val x = new X with Y 

paramétrisation:

class X[A <: B] extends Y[A] 

multiples (liés) Type params:

class X[A >: B, B](x: A, xs: Seq[B]) 

limites Contexte:

class X[A : Manifest] 

Bounds Voir:

class X[A <% Ordered[A]] 

Générique Méthodes:

class Foo[B] { 
    def bar[A <: B](x: A) = ... 
} 

Comme vous pouvez le voir, les relations qui peuvent être spécifiées dans un paramètre de type sont beaucoup plus riches que la simple hiérarchie linéaire disponible lors de la déclaration d'une classe, surtout quand vous faible pour les limites.

Il est également intéressant de noter que les paramètres de type générique pour les classes ou les méthodes seront inférées très souvent, ce qui vous permet d'écrire:

val myList = List(1, 2, 3) 

au lieu de

val myList = List[Int](1, 2, 3) 

Ainsi, la manière dont les notations sont utilisés est très différent.

mise à jour

Un exemple particulier a le printemps vient à l'esprit, ce qui démontre l'utilisation simultanée des deux notations et montrant comment ils doivent rester distincts:

def someMethod[T <: Foo with Bar](x: T) = ... 

Cela nécessite que le type param T être un sous-type quelque chose mélangeant à la fois Foo et Bar.

de même avec les types de structure:

type Closable = { def close: Unit } //alias for a structural type 
def someMethod[T <: Closable](x: T) = ...