2017-02-10 3 views
3

(Scala 2.11.8)héritage et l'inférence de type auto-récursif

I ont un caractère GenTableLike avec la signature de type auto-récursif complexe qui définit procédé ++ pour concaténer les implémentations de table compatibles. J'ai aussi une hiérarchie GenTableLike >: KeyTable >: MapKeyTable. Cependant, ++ 'MapKeyTable ne parvient pas à déduire les types auto-récursifs.

extrait est un peu simplifié Ici, dans quel problème se reproduit encore:

trait GenTableLike[RKT, 
        CKT, 
        +A, 
        +SelfType[+A2] <: GenTableLike[RKT, CKT, A2, SelfType, TransposedType], 
        +TransposedType[+A2] <: GenTableLike[CKT, RKT, A2, TransposedType, SelfType]] { 
    def ++[B >: A, 
     T2 <: GenTableLike[RKT, CKT, B, ST, TT], 
     ST[+A2] <: GenTableLike[RKT, CKT, A2, ST, TT], 
     TT[+A2] <: GenTableLike[CKT, RKT, A2, TT, ST]](that: T2): SelfType[B] = ??? 
} 

trait KeyTable[RKT, CKT, +A] 
    extends GenTableLike[RKT, 
         CKT, 
         A, 
         KeyTable.Curried[RKT, CKT]#Self, 
         KeyTable.Curried[CKT, RKT]#Self] 

object KeyTable { 
    /** Type helper for defining self-recursive type */ 
    type Curried[RKT, CKT] = { 
    type Self[+A] = KeyTable[RKT, CKT, A] 
    } 
} 

class MapKeyTable[RKT, CKT, +A] 
    extends KeyTable[RKT, CKT, A] 
    with GenTableLike[RKT, 
        CKT, 
        A, 
        MapKeyTable.Curried[RKT, CKT]#Self, 
        MapKeyTable.Curried[CKT, RKT]#Self] 

object MapKeyTable { 
    /** Type helper for defining self-recursive type */ 
    type Curried[RKT, CKT] = { 
    type Self[+A] = MapKeyTable[RKT, CKT, A] 
    } 
} 

val t1: MapKeyTable[Int, String, Int] = ??? 
val t2: MapKeyTable[Int, String, Any] = ??? 

// The following works 
t1.++[Any, MapKeyTable[Int, String, Any], ({ type ST[+A2] = MapKeyTable[Int, String, A2] })#ST, ({ type TT[+A2] = MapKeyTable[String, Int, A2] })#TT](t2) 
t1.++[Any, MapKeyTable[Int, String, Any], MapKeyTable.Curried[Int, String]#Self, MapKeyTable.Curried[String, Int]#Self](t2) 
// Error: inferred type arguments [Int,MapKeyTable[Int,String,Any],Nothing,Nothing] do not conform to method ++'s type parameter bounds [B >: Int,T2 <: GenTableLike[Int,String,B,ST,TT],ST[+A2] <: GenTableLike[Int,String,A2,ST,TT],TT[+A2] <: GenTableLike[String,Int,A2,TT,ST]] 
t1 ++ t2 

que je fais quelque chose de mal ici?

Notes:

auto-type et le type transposé sont utilisés pour définir des types de retour de la fonction. J'ai aussi une mise en œuvre IndexedTable définie comme suit, donc je ne peux pas retravailler auto type d'accepter 3 arguments

trait IndexedTable[+A] 
    extends GenTableLike[Int, 
         Int, 
         A, 
         IndexedTable, 
         IndexedTable] 

class IndexedSeqTable[+A] 
    extends IndexedTable[A] 
    with GenTableLike[Int, 
         Int, 
         A, 
         IndexedSeqTable, 
         IndexedSeqTable] 
+0

Peut-être l'un des https://github.com/slamdata/matryoshka https://www.youtube.com/watch?v=rBmx9NsNSXM&t=1975s https: // tpolecat.github.io/2013/10/12/typeclass.html pourrait aider. – Reactormonk

Répondre

0

J'ai réussi à trouver une solution à mon problème que je pense est mieux que la proposition de @ PH88. J'ai supprimé les contraintes de SelfType/TransposedType et utilisé ST forSome { type ST[+A2] } syntaxe dans ++ type d'argument, comme dans la description de https://issues.scala-lang.org/browse/SI-8039

Les effets secondaires de cette solution:

  1. Impossible de définir les implémentations de méthode dans GenTableLike en termes de d'autres méthodes (puisqu'elles renverront un SelfType de nature inconnue) - c'est-à-dire qu'elles ne peuvent pas réutiliser les méthodes du code GenTableLike. Cela peut être contourné en fournissant la couche d'héritage supplémentaire ci-dessous GenTableLike et au-dessus KeyTable et IndexedTable
  2. Sous-classes peuvent définir les non-tables que leurs auto/types transposés, mais cela ne semble pas être un gros problème - il n'y a aucune raison pour eux de le faire alors.

code résultant:

trait GenTableLike[RKT, 
        CKT, 
        +A, 
        +SelfType[+A2], 
        +TransposedType[+A2]] { 
    def ++[B >: A](that: GenTableLike[RKT, 
            CKT, 
            B, 
            ST forSome { type ST[+A2] }, 
            TT forSome { type TT[+A2] }]): SelfType[B] = ??? 
} 

// Common ancestor for all table classes 
trait GenTable[RKT, CKT, +A] 
    extends GenTableLike[RKT, 
         CKT, 
         A, 
         GenTable.Curried[RKT, CKT]#Self, 
         GenTable.Curried[CKT, RKT]#Self] { 
    // Here we can implement common methods reusing other methods due to proper SelfType bounds 
} 

object GenTable { 
    /** Type helper for defining self-recursive type */ 
    private type Curried[RKT, CKT] = { 
    type Self[+A] = GenTable[RKT, CKT, A] 
    } 
} 

trait KeyTable[RKT, CKT, +A] 
    extends GenTable[RKT, CKT, A] 
    with GenTableLike[RKT, 
        CKT, 
        A, 
        KeyTable.Curried[RKT, CKT]#Self, 
        KeyTable.Curried[CKT, RKT]#Self] 

object KeyTable { 
    /** Type helper for defining self-recursive type */ 
    type Curried[RKT, CKT] = { 
    type Self[+A] = KeyTable[RKT, CKT, A] 
    } 
} 

class MapKeyTable[RKT, CKT, +A] 
    extends KeyTable[RKT, CKT, A] 
    with GenTableLike[RKT, 
        CKT, 
        A, 
        MapKeyTable.Curried[RKT, CKT]#Self, 
        MapKeyTable.Curried[CKT, RKT]#Self] { 
    override def ++[B >: A](that: GenTableLike[RKT, 
              CKT, 
              B, 
              ST forSome { type ST[+A2] }, 
              TT forSome { type TT[+A2] }]): MapKeyTable[RKT, CKT, B] = { 
    new MapKeyTable 
    } 
} 

object MapKeyTable { 
    /** Type helper for defining self-recursive type */ 
    type Curried[RKT, CKT] = { 
    type Self[+A] = MapKeyTable[RKT, CKT, A] 
    } 
} 

trait IndexedTable[+A] 
    extends GenTable[Int, Int, A] 
    with GenTableLike[Int, 
        Int, 
        A, 
        IndexedTable, 
        IndexedTable] 

class IndexedSeqTable[+A] 
    extends IndexedTable[A] 
    with GenTableLike[Int, 
         Int, 
         A, 
         IndexedSeqTable, 
         IndexedSeqTable] { 
    override def ++[B >: A](that: GenTableLike[Int, 
              Int, 
              B, 
              ST forSome { type ST[+A2] }, 
              TT forSome { type TT[+A2] }]): IndexedSeqTable[B] = { 
    new IndexedSeqTable 
    } 
} 


// Usage 

def s1: IndexedSeqTable[Int] = ??? 
def s2: IndexedSeqTable[Any] = ??? 

def t1: MapKeyTable[Int, Int, Int] = ??? 
def t2: MapKeyTable[Int, Int, Any] = ??? 

// All of this works with result being of proper type 
t1.++[Any](t2) 
t1 ++ t2 
s1 ++ s2 
t1 ++ s1 
s1 ++ t1 
s2 ++ t1 
1

Que diriez-vous tourner la SelfType et TransposeType en types abstraits? Ceci est plus simple et travail:

import scala.language.higherKinds 

trait GenTableLike[RKT, CKT, +A] { 
    type SelfType[+A2] <: GenTableLike[RKT, CKT, A2] 
    type TransposedType[+A2] <: GenTableLike[CKT, RKT, A2] 

    def ++[B >: A](that: GenTableLike[RKT, CKT, B]): SelfType[B] 
} 

trait KeyTable[RKT, CKT, +A] 
    extends GenTableLike[RKT, CKT, A] { 
    override type SelfType[+A2] <: KeyTable[RKT, CKT, A2] 
    override type TransposedType[+A2] <: KeyTable[CKT, RKT, A2] 
} 

class MapKeyTable[RKT, CKT, +A] 
    extends KeyTable[RKT, CKT, A] { 
    override type SelfType[+A2] = MapKeyTable[RKT, CKT, A2] 
    override type TransposedType[+A2] = MapKeyTable[CKT, RKT, A2] 

    override def ++[B >: A](that: GenTableLike[RKT, CKT, B]): MapKeyTable[RKT, CKT, B] = 
    new MapKeyTable[RKT, CKT, B] 
} 

val t1 = new MapKeyTable[Int, String, Int] 
val t2 = new MapKeyTable[Int, String, Any] 

// The following works 
t1.++[Any](t2) 
t1 ++ t2 


trait IndexedTable[+A] 
    extends GenTableLike[Int, Int, A] { 
    override type SelfType[+A2] <: IndexedTable[A2] 
    override type TransposedType[+A2] <: IndexedTable[A2] 
} 

class IndexedSeqTable[+A] 
    extends IndexedTable[A] { 
    override type SelfType[+A2] = IndexedSeqTable[A2] 
    override type TransposedType[+A2] = IndexedSeqTable[A2] 

    override def ++[B >: A](that: GenTableLike[Int, Int, B]): IndexedSeqTable[B] = new IndexedSeqTable[B] 
} 

Mis à jour le 15 février en réponse au commentaire d'Alex:

type récursif + héritage est difficile et je toujours éviter si possible :-).
Si SelfType et TransposeType sont utilisé comme types de retour de la fonction uniquement, que diriez-vous d'éliminer la SelfType et TransposeType tout à fait et utiliser constructeur implicite comme:

import scala.language.higherKinds 

trait GenTableLike[RKT, CKT, +A] { 

    def value: A 

    def ++[B >: A](that: GenTableLike[RKT, CKT, B])(implicit builder: GenTableLike.Builder[this.type, B]): builder.Self = { 
    builder.buildSelf(that.value) 
    } 

    def transpose(implicit builder: GenTableLike.Builder[this.type, A]) = builder.buildTranspose(value) 

} 

object GenTableLike { 

    trait Builder[-This, -A] { 
    type Self 
    type Transpose 

    def buildSelf(a: A): Self 

    def buildTranspose(a: A): Transpose 
    } 

} 

trait KeyTable[RKT, CKT, +A] 
    extends GenTableLike[RKT, CKT, A] { 
} 

class MapKeyTable[RKT, CKT, +A](override val value: A) 
    extends KeyTable[RKT, CKT, A] { 
} 

object MapKeyTable { 

    implicit def builder[RKT, CKT, A] = new GenTableLike.Builder[MapKeyTable[RKT, CKT, A], A] { 
    override type Self = MapKeyTable[RKT, CKT, A] 
    override type Transpose = MapKeyTable[CKT, RKT, A] 

    override def buildSelf(a: A): Self = new MapKeyTable[RKT, CKT, A](a) 

    override def buildTranspose(a: A): Transpose = new MapKeyTable[CKT, RKT, A](a) 
    } 

} 

class MapKeyTableEx[RKT, CKT, +A](override val value: A) 
    extends MapKeyTable[RKT, CKT, A](value) 

object MapKeyTableEx { 
    implicit def builder[RKT, CKT, A] = new GenTableLike.Builder[MapKeyTableEx[RKT, CKT, A], A] { 
    override type Self = MapKeyTableEx[RKT, CKT, A] 
    override type Transpose = MapKeyTableEx[CKT, RKT, A] 

    override def buildSelf(a: A): Self = new MapKeyTableEx[RKT, CKT, A](a) 

    override def buildTranspose(a: A): Transpose = new MapKeyTableEx[CKT, RKT, A](a) 
    } 

} 


val t1 = new MapKeyTable[Int, String, Int](1) 
val t2 = new MapKeyTable[Int, String, Any]("b") 
val t3 = new MapKeyTableEx[Int, String, Int](1) 
val t4 = new MapKeyTableEx[Int, String, Any]("b") 

// The following works 
t1 ++ t2 
t1 ++ t3 
t2 ++ t3 
t3 ++ t4 

t1.transpose 
t2.transpose 
t3.transpose 
t4.transpose 
+0

J'ai essayé cela, le problème avec cette approche est que vous ne pouvez pas étendre 'MapKeyTable' et surcharger sa définition' SelfType' - 'MapKeyTable' pour' SelfType' utilise '=' et pas '<:'. D'un autre côté, vous ne pouvez pas déclarer 'SelfType' en utilisant' <: 'car il ne vous permettra pas de retourner' MapKeyTable' à partir de '++' (ce qui est logique - '' '' '' '' '' '' '' '' '' '' '' '' ' la classe remplace 'SelfType' et pas' ++ ') –

+0

(Concernant votre deuxième solution) C'est une façon de le résoudre, merci pour cela. Si je comprends bien, c'est le genre de choses que font les collections Scala, mais plus simples. Le problème avec ceci est que nous devons standardiser ce que le constructeur devrait accepter pour construire une table, alors que différentes implémentations peuvent avoir des opinions différentes à ce sujet - les forcer à un format unique conduira à des implémentations inefficaces. –

+0

Je vais quand même vous récompenser pour vos efforts. Merci! –