2011-07-19 4 views
2

Un conte de deux traits qui semblent devraient jouer bien ensemble mais pas et je ne peux pas faire des queues de queue de pourquoi ce code ne fonctionne pas ou ce que l'erreur de compilation est vraiment essayer pour me dire.Génériques avec des types existentiels dans Scala

Alors ... nous avons

Un trait pour les parents ...

trait PolyTreeHasParents[P <: PolyTreeHasChildren[_]] { 


val _parents: ListBuffer[P] = ListBuffer() 

def isRootNode = _parents.size == 0 

def parents: List[P] = _parents.readOnly 

def addParent(parent: P): PolyTreeHasParents[P] = { 

    println(parent) 

    if (parent == this) 
     throw new IllegalArgumentException() 

    _parents += parent 

    // 

    this 
} 




} 

et un trait pour les enfants ...

trait PolyTreeHasChildren[C <: PolyTreeHasParents[_]] { 


val _children: ListBuffer[C] = ListBuffer() 

def isLeafNode = children == ListBuffer() 

def children: List[C] = _children.readOnly 

def += (child: C) : PolyTreeHasChildren[C] = { 
    addChild(child) 
} 

def addChild(child: C): PolyTreeHasChildren[C] = { 


    if (child == this) 
     throw new IllegalArgumentException() 

    _children += child 

    child.addParent(this) // <= ERROR HERE 

    this 

} 

}

Le pointeur à ERROR indique qu'une incompatibilité de type a été trouvée.

PolyTreeHasChildren.this.type(with underlying type PolyTreeHasChildren[C]) required: _$1 where type _$1 

J'aurais pensé que l'ajout

P :< PolyTreeHasParents[_] 

me aurait permis d'ajouter une référence aux parents de l'enfant.

Voici la partie bizarre ... à examiner, l'erreur est:

required: _$1 where type _$1 

Quoi !?

Hélas ... je suis à court d'idées sur la façon de faire ce travail de code: (

Répondre

3

Vous pouvez éviter ce apparente circularité infinie par deux moyens:

d'abord, supprimer les limites de type inutiles (https://stackoverflow.com/questions/1332574/common-programming-mistakes-for-scala-developers-to-avoid/5602321#5602321) - au moins dans votre mise en œuvre de PolyTreeHasParents, il n'y a actuellement besoin de dire que P doit être un sous-type de PolyTreeHasChildren

Deuxièmement, vous pouvez ajouter à PolyTreeHasChildren un autre argument de type spécifiant le type d'application, et l'utiliser comme. un type de soi. C'est un modèle commun dans la bibliothèque de collections, je pense.

Il ressemblerait à ceci:

import collection.mutable.ListBuffer 

trait PolyTreeHasParents[P] { 
    val _parents: ListBuffer[P] = ListBuffer() 
    def isRootNode = _parents.size == 0 
    def parents: List[P] = _parents.readOnly 
    def addParent(parent: P): PolyTreeHasParents[P] = { 
     require (parent != this) 
     _parents += parent 
     this 
    } 
} 

trait PolyTreeHasChildren[Repr, C <: PolyTreeHasParents[Repr]] { 
    me: Repr => 

    val _children: ListBuffer[C] = ListBuffer() 
    def isLeafNode = children == ListBuffer() 
    def children: List[C] = _children.readOnly 
    def += (child: C) : Repr = { 
     addChild(child) 
    } 

    def addChild(child: C): Repr = { 
     require (child != this) 
     _children += child 
     child.addParent(this) 
     this 
    } 
} 
1

Avez-vous essayé quelque chose comme

trait PolyTreeHasParents[P <: PolyTreeHasChildren[PolyTreeHasParents[P]]]  { } 

trait PolyTreeHasChildren[C <: PolyTreeHasParents[PolyTreeHasChildren[C]]]  { } 

types Existentiels ne sont pas bons ici. Vous dites essentiellement "mon parent a des enfants d'un genre inconnu". Vous devriez dire quelque chose comme "mon parent a des enfants qui sont comme moi". Clause de non-responsabilité: Je n'ai pas encore eu l'occasion de tester cela (pas de véritable ordinateur à proximité, seulement un téléphone portable).

MISE À JOUR: non, cela ne fonctionne pas. Dans le cas où vous êtes toujours intéressé, voici un code de travail. Je l'ai ajouté symétrique (addParent appelle addChild, addChild appelle addParent) afin d'illustrer comment cela fonctionne quand des dépendances circulaires sont vraiment nécessaires. J'ai utilisé l'idée de 0 __ d'injecter sel-type là-dedans. import scala.collection.mutable.ListBuffer

trait PolyTreeHasParents[Repr <: PolyTreeHasParents[Repr, P], 
     P <: PolyTreeHasChildren[P, Repr]] { 

me: Repr => 

    val _parents: ListBuffer[P] = ListBuffer() 
    def isRootNode = _parents.size == 0 
    def parents: List[P] = _parents.readOnly 
    def addParent(parent: P): Repr = { 
     if (! _parents.contains(parent) { 
      println(parent) 
      if (parent == this) 
      throw new IllegalArgumentException() 
      _parents += parent 
      parent.addChild(this) 
     } 
     this 
    } 
} 

trait PolyTreeHasChildren[Repr <: PolyTreeHasChildren[Repr, C], 
     C <: PolyTreeHasParents[C, Repr]] { 

me: Repr => 

    val _children: ListBuffer[C] = ListBuffer() 
    def isLeafNode = children == ListBuffer() 
    def children: List[C] = _children.readOnly 
    def += (child: C) : PolyTreeHasChildren[Repr, C] = { 
     addChild(child) 
    } 
    def addChild(child: C): Repr = { 
     if (! _children.contains(child) { 
      println(child) 
      if (child == this) 
       throw new IllegalArgumentException() 
      _children += child 
      child.addParent(this) 
     } 
     this 
    } 
} 

// Usage example 
class PP extends PolyTreeHasChildren[PP, CC] 
class CC extends PolyTreeHasParents[CC, PP] 
+0

Nice! Travaillé comme un charme! Merci de souligner que le trait ne devrait pas être en mesure d'ajouter un enfant qu'il a déjà ajouté. Faites quelques petits changements ... au lieu de if (Parent == this) ..., changez cela pour require (child! = This). Aussi ... déplacé de listes mutables à celles immuables. – user465342

2

Le message d'erreur semble bizarre mais il existe une méthode dans cette folie. Ce qu'il dit est: vous passez dans un paramètre de type connu. Mais vous l'avez spécifié comme paramètre d'un type inconnu qui est skolomisé dans ce cas à _ $ 1.

Dans votre code, vous pouvez vous débarrasser complètement des paramètres de type:

trait PolyTreeHasParents { 
    type P = PolyTreeHasChildren 
    val _parents: ListBuffer[P] = ListBuffer() 
    def isRootNode = _parents.size == 0 
    def parents: List[P] = _parents.readOnly 

    def addParent(parent: P): PolyTreeHasParents = { 

    if (!_parents.contains(parent)) { 
     println(parent) 
     if (parent == this) throw new IllegalArgumentException() 
     _parents += parent 
     parent.addChild(this) 
    } 
    this 
    } 
} 

trait PolyTreeHasChildren { 
    type C = PolyTreeHasParents 
    val _children: ListBuffer[C] = ListBuffer() 

    def isLeafNode = children == ListBuffer() 

    def children: List[C] = _children.readOnly 

    def +=(child: C): PolyTreeHasChildren = { 
    addChild(child) 
    } 

    def addChild(child: C): PolyTreeHasChildren = { 
    if (!_children.contains(child)) { 
     println(child) 
     if (child == this) 
     throw new IllegalArgumentException() 
     _children += child 
     child.addParent(this) 
    } 
    this 
    } 
} 

Voir ce comportement:

object Test { 
    def main(args: Array[String]) { 
    trait X extends PolyTreeHasParents with PolyTreeHasChildren 
    trait Y extends PolyTreeHasParents with PolyTreeHasChildren 
    val x0, x1, x2 = new X {} 
    val y0, y1, y2 = new Y {} 
    x0.addChild(x1) 
    x1.addChild(x2) 
    y2.addParent(y1) 
    y1.addParent(y0) 
    x0.addParent(y2) 
    } 
} 

Maintenant, nous allons comparer cela au comportement de la solution de "nm":

object Test { 
    def main(args: Array[String]) { 
    trait X extends PolyTreeHasParents[X, X] with PolyTreeHasChildren[X, X] 
    trait Y extends PolyTreeHasParents[Y, Y] with PolyTreeHasChildren[Y, Y] 
    val x0, x1, x2 = new X {} 
    val y0, y1, y2 = new Y {} 
    x0.addChild(x1) 
    x1.addChild(x2) 
    y2.addParent(y1) 
    y1.addParent(y0) 
// x0.addParent(y2) // will not compile 
    } 
}  
+0

Est-ce que cela fonctionne aussi bien si PolyTreeHasParents a une méthode addParent et PolyTreeHasChildren a une méthode addChild comme dans la réponse fournie par n.m? – user465342

+0

Bien sûr, voir le code ci-dessus. Ce qui rend ce code plus simple, c'est qu'il vous permet d'ajouter * n'importe quoi * de PolyTreeHasChildren à * n'importe quoi * de PolyTreeHasParents et l'inverse. Dans la solution de n.m., vous pouvez restreindre cela à des types spécifiques et vérifier le type. Mais je ne pourrais pas compenser à partir de votre question que vous avez vraiment besoin de cela. –

1

La complexité dans les paramètres de type bounds du fait que votre PolyTree est composé de 2 traits. L'un contenant les parents et l'autre contenant les enfants.

Je ne comprends pas quel pourrait être le cas d'utilisation de ces caractères séparés. Puisque dans un PolyTree, en général, tous vos nœuds peuvent avoir des enfants et/ou des parents. Donc je pense qu'on les mélange toujours dans les deux.

Si tel est le cas, alors on pourrait se débarrasser de la plupart des limites des paramètres de type complexité:

trait PolyTree[Self <: PolyTree[Self] ] { 
    self: Self => 

    private val _parents: ListBuffer[Self] = ListBuffer() 
    def isRootNode = _parents.isEmpty 
    def parents: List[Self] = _parents.readOnly 

    def addParent(parent: Self): Self = { 

    if (!_parents.contains(parent)) { 
     println(parent) 
     if (parent == this) throw new IllegalArgumentException() 
     _parents += parent 
     parent.addChild(this) 
    } 
    this 
    } 

    private val _children: ListBuffer[Self] = ListBuffer() 
    def isLeafNode = _children.isEmpty 
    def children: List[Self] = _children.readOnly 

    def addChild(child: Self): Self = { 
    if (!_children.contains(child)) { 
     println(child) 
     if (child == this) 
     throw new IllegalArgumentException() 
     _children += child 
     child.addParent(this) 
    } 
    this 
    } 

} 

utilisation:

object UseCasePolyTree { 
    trait X extends PolyTree[X] 
    trait Y extends PolyTree[Y] 
    val x0, x1, x2 = new X {} 
    val y0, y1, y2 = new Y {} 
    x0.addChild(x1) 
    x1.addChild(x2) 
    val xx1: X = x2.parents.head 
    y2.addParent(y1) 
    y1.addParent(y0) 
// x0.addParent(y2) // will not compile 
} 

En dehors de cela: un polytree est acyclique. Vous devriez toujours ajouter du code pour empêcher la création de cycles.

Questions connexes