2010-11-30 15 views
38

Je n'ai pas trouvé de réponse à cette question dans d'autres questions. Supposons que j'ai une superclasse abstraite Abstract0 avec deux sous-classes, Concrete1 et Concrete1. Je veux être en mesure de définir Abstrait0 quelque chose commeComment utiliser Scala cette typage, types abstraits, etc pour implémenter un type de soi?

def setOption(...): Self = {...} 

où l'auto serait le sous-type de béton. Cela permettrait à des appels enchaînant à setOption comme ceci:

val obj = new Concrete1.setOption(...).setOption(...) 

et toujours obtenir Concrete1 que le type de obj inférées.

Ce que je ne veux pas est de définir ceci:

abstract class Abstract0[T <: Abstract0[T]] 

car il rend plus difficile pour les clients de traiter ce type. J'ai essayé diverses possibilités, y compris un type abstrait:

abstract class Abstract0 { 
    type Self <: Abstract0 
} 

class Concrete1 extends Abstract0 { 
    type Self = Concrete1 
} 

mais il est impossible de mettre en œuvre setOption, parce que this dans Abstrait0 n'a pas de type auto. Et en utilisant this: Self => ne fonctionne pas non plus dans Abstract0.

Quelles sont les solutions à ce problème?

+0

Une option consiste à définir, par exemple, 'def = self protégée this.asInstanceOf [Self]' et 'def setOption (...) = {...; self} ', mais cela a l'air un peu moche ... –

Répondre

50

C'est ce que this.type est pour:

scala> abstract class Abstract0 { 
    | def setOption(j: Int): this.type 
    | } 
defined class Abstract0 

scala> class Concrete0 extends Abstract0 { 
    | var i: Int = 0 
    | def setOption(j: Int) = {i = j; this} 
    | } 
defined class Concrete0 

scala> (new Concrete0).setOption(1).setOption(1) 
res72: Concrete0 = [email protected] 

Comme vous pouvez le voir setOption retourne le type réel utilisé, non Abstrait0. dans le commentaire (comment retourner les nouvelles instances Pour répondre à la question de suivi de JPP: Si Concrete0 avait setOtherOption alors (new Concrete0).setOption(1).setOtherOption(...) travaillerait

MISE À JOUR L'approche générale décrite dans la question est la bonne (en utilisant des types abstraits) Cependant, la. création des nouvelles instances doit être explicite pour chaque sous-classe

une approche est:..

abstract class Abstract0 { 
    type Self <: Abstract0 

    var i = 0 

    def copy(i: Int) : Self 

    def setOption(j: Int): Self = copy(j) 
} 

class Concrete0(i: Int) extends Abstract0 { 
    type Self = Concrete0 
    def copy(i: Int) = new Concrete0(i) 
} 

un autre est de suivre le modèle de constructeur utilisé dans la bibliothèque de collection de Scala est, setOption reçoit un constructeur implicite paramètre, ce qui a Les avantages que la construction de la nouvelle instance peut être fait avec plus de méthodes que simplement «copier» et que les constructions complexes peuvent être faites. Par exemple. setSpecialOption peut spécifier que l'instance de retour doit être SpecialConcrete.

est ici une illustration de la solution:

trait Abstract0Builder[To] { 
    def setOption(j: Int) 
    def result: To 
} 

trait CanBuildAbstract0[From, To] { 
    def apply(from: From): Abstract0Builder[To] 
} 


abstract class Abstract0 { 
    type Self <: Abstract0 

    def self = this.asInstanceOf[Self] 

    def setOption[To <: Abstract0](j: Int)(implicit cbf: CanBuildAbstract0[Self, To]): To = { 
    val builder = cbf(self) 
    builder.setOption(j) 
    builder.result 
    } 

} 

class Concrete0(i: Int) extends Abstract0 { 
    type Self = Concrete0 
} 

object Concrete0 { 
    implicit def cbf = new CanBuildAbstract0[Concrete0, Concrete0] { 
     def apply(from: Concrete0) = new Abstract0Builder[Concrete0] { 
      var i = 0 
      def setOption(j: Int) = i = j 
      def result = new Concrete0(i) 
     } 
    } 
} 

object Main { 
    def main(args: Array[String]) { 
    val c = new Concrete0(0).setOption(1) 
    println("c is " + c.getClass) 
    } 
} 

MISE À JOUR 2: En réponse à la deuxième commentaire de JPP. En cas de plusieurs niveaux d'imbrication, utilisez un paramètre de type au lieu de membre de type et de faire Abstrait0 dans un trait:

trait Abstract0[+Self <: Abstract0[_]] { 
    // ... 
} 

class Concrete0 extends Abstract0[Concrete0] { 
    // .... 
} 

class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] { 
// .... 
} 
+0

Très belle fonction de langue!Je ne me souviens pas de l'avoir vu dans le livre Programming in Scala. Mais maintenant, une question de suivi: cela ne fonctionne que lorsque vous renvoyez cela. Que faire si je veux renvoyer un autre objet du même type concret, par exemple pour une méthode de clonage? (Je sais que je reçois clone gratuitement comme 'copie' dans les classes de cas, mais je serais intéressé par la question néanmoins.) –

+0

Merci pour l'explication de suivi. Une dernière question. Si 'Concrete0' lui-même a une sous-classe' RefinedConcrete0', je ne peux pas remplacer le type concret Self, déjà spécifié dans 'Concrete0'. Je suppose que le modèle de constructeur est alors l'option préférée? Ou y a-t-il autre chose qui pourrait être utilisé à la place? Je suppose que l'article 33 de C++ plus efficace peut également être appliqué ici: Faire des classes non-feuille Résumé ... –

+0

Quelle serait la différence entre avoir this.type' et 'Abstract0'? Je n'en vois pas. Pour moi, cela fonctionne tout simplement parce qu'il suit de toute façon les règles de la variance. – Debilski

4

Tel est le cas précis d'utilisation de this.type. Ce serait comme:

def setOption(...): this.type = { 
    // Do stuff ... 
    this 
}