2016-07-22 8 views
-1

est donc ici le code:Pourquoi le type générique ne fonctionne pas avec Inheritance dans scala?

package week4 
object expr { 
    abstract class Expr[T] { 
    def eval:T = this match { 
     case Number(x) => x 
     case Sum(e1, e2) => e1.eval + e2.eval 
    } 
    def show: String = this match { 
     case Number(x) => "" + x 
     case Sum(e1, e2) => "(" + e1.show + "+" + e2.show + ")" 
    } 
    } 
    case class Number[T](val value: T) extends Expr { 
    } 
    case class Sum[T](val e1: Expr[T], val e2: Expr[T]) extends Expr { 
    } 
} 

sauf que je reçois l'erreur sur l'ensemble des comparaisons de cas:

constructeur

ne peut pas être instancié type prévu; trouvé: week4.expr.Number [T (dans le numéro de classe)] requis: week4.expr.Expr [T (dans class Expr)] Remarque: Rien <: T (et week4.expr.Number [T] <: week4.expr.Expr [Nothing]), mais la classe Expr est invariante dans le type T. Vous voudrez peut-être définir T + + T comme .

Qu'est-ce que je fais mal?

+0

Si la réponse fournie est valide, il serait bon de la marquer comme acceptée. :-) – stefanobaghino

Répondre

8

Il y a principalement deux erreurs dans votre code:

  • Lors de l'extension Expr vous oublié de passer le paramètre de type
  • Dans la branche Sum de votre modèle vous correspondant essayez de résumer deux T s, sans donner suffisamment de preuves au compilateur du fait que l'opérateur + est défini sur le type T.

est ici une solution révisée qui fonctionne:

object expr { 

    abstract class Expr[T](implicit evidence: Numeric[T]) { 
    def eval: T = this match { 
     case Number(x) => x 
     case Sum(e1, e2) => evidence.plus(e1.eval, e2.eval) 
    } 
    def show: String = this match { 
     case Number(x) => "" + x 
     case Sum(e1, e2) => "(" + e1.show + "+" + e2.show + ")" 
    } 
    } 

    case class Number[T : Numeric](val value: T) extends Expr[T] 

    case class Sum[T : Numeric](val e1: Expr[T], val e2: Expr[T]) extends Expr[T] 

} 

Voici un exemple exécutée dans le shell Scala:

scala> import expr._ 
import expr._ 

scala> Sum(Sum(Number(2), Number(3)), Number(4)) 
expression: expr.Sum[Int] = Sum(Sum(Number(2),Number(3)),Number(4)) 

scala> println(expression.show + " = " + expression.eval) 
((2+3)+4) = 9 

Je suis sûr que le paramètre de type manquant pour le constructeur de type Expr est juste une distraction et il n'a pas besoin d'explications supplémentaires.

Le code de mon exemple introduit un paramètre implicite evidence et l'utilisation de la classe de type Numeric. Dans Scala, une classe de type est un modèle qui utilise des traits et des paramètres implicites pour définir des capacités pour les classes avec un certain degré de flexibilité.

Afin de sommer deux valeurs génériques, le compilateur doit avoir un moyen de savoir que les deux T s savent comment être sommés. Cependant, les types numériques ne sont pas dans une hiérarchie de type qui peut être exploitée en disant que T est un sous-type d'une classe hypothétique Number (en écrivant quelque chose comme abstract class Expr[T <: Number]).

La bibliothèque standard Scala a cependant introduit la classe de type Numeric, qui est fondamentalement un trait qui définit un ensemble d'opérations qui ont un sens pour tous les types numériques (d'où le nom). La méthode plus est une gauche non implémentée pour quiconque veut adhérer à ce trait.

La bibliothèque standard Scala implémente ces caractéristiques pour différents types numériques, vous permettant ainsi d'utiliser sans effort cette même implémentation générique avec différents types.

Voici un exemple:

scala> val expression = Sum(Sum(Number(2.0), Number(3.0)), Number(4.0)) 
expression: expr.Sum[Double] = Sum(Sum(Number(2.0),Number(3.0)),Number(4.0)) 

scala> println(expression.show + " = " + expression.eval) 
((2.0+3.0)+4.0) = 9.0 

Spécification d'une preuve implicite comme cela peut être fait explicitement (comme dans abstract class Expr[T](implicit evidence: Numeric[T])) ou en utilisant la soi-disant notation « contexte lié » (comme dans case class Number[T : Numeric]), qui est sucre essentiellement syntaxique pour la variante explicite qui renonce à deux référencer explicitement une instance de classe de type. J'ai utilisé la variante explicite dans le premier cas parce que je devais référencer l'instance de classe de type dans mon code pour additionner les deux valeurs (evidence.plus(e1.eval, e2.eval)) mais j'ai utilisé la notation «contexte lié» dans ce dernier cas car je la trouve plus naturelle lisible.

Si vous le souhaitez, vous pouvez également mettre en œuvre Numeric[T] pour vos propres classes (par exemple .: Numeric[Rational]) sans avoir à faire face à une hiérarchie de type statique.

Ceci est bien sûr une explication très précipitée des classes de types: pour une explication plus approfondie, vous proposez ce très bon blog post sur le sujet.

+0

Suggère également de lire sur la variance de type en plus des classes de caractères. – sebszyller

+0

@ 3jckd absolument, il peut devenir un sujet assez important lorsqu'il s'agit de hiérarchies de types. S'il vous arrive de lire quel type de variance est et finissent par ne pas le comprendre, cette image est un de mes favoris personnel qui peut venir à aider: https://twitter.com/stefanobaghino/status/597016186820423680 – stefanobaghino

+1

Le mot-clé 'new' peut être omis lors de l'instanciation de classes de cas comme "Sum" et "Number", ou est-ce que je manque quelque chose? –