2015-04-20 2 views
4

Le code Scala suivant fonctionne:Comment paramètre de type « extrait » instancier une autre classe

object ReducerTestMain extends App { 

    type MapOutput = KeyVal[String, Int] 

    def mapFun(s:String): MapOutput = KeyVal(s, 1) 

    val red = new ReducerComponent[String, Int]((a: Int, b: Int) => a + b) 

    val data = List[String]("a", "b", "c", "b", "c", "b") 

    data foreach {s => red(mapFun(s))} 
    println(red.mem) 
    // OUTPUT: Map(a -> 1, b -> 3, c -> 2) 
} 

class ReducerComponent[K, V](f: (V, V) => V) { 
    var mem = Map[K, V]() 

    def apply(kv: KeyVal[K, V]) = { 
    val KeyVal(k, v) = kv 
    mem += (k -> (if (mem contains k) f(mem(k), v) else v)) 
    } 
} 

case class KeyVal[K, V](key: K, value:V) 

Mon problème est que je voudrais instancier ReducerComponent comme ceci:

val red = new ReducerComponent[MapOutput, Int]((a: Int, b: Int) => a + b) 

ou mieux encore:

val red = new ReducerComponent[MapOutput](_ + _) 

Cela signifie beaucoup de choses:

  1. Je voudrais saisir vérifier que MapOutput est du type KeyVal[K, C],
  2. Je veux saisir vérifier que C est le même type utilisé dans f,
  3. Je dois aussi « extraire » K afin d'instancier mem, et de vérifier les paramètres de apply.

Est-ce beaucoup à demander? :) Je voulais écrire quelque chose comme

class ReducerComponent[KeyVal[K,V]](f: (V, V) => V) {...} 

Au moment où j'instancier ReducerComponent tout ce que j'ai est f et MapOutput, donc infère V est OK. Mais alors j'ai seulement KeyVal[K,V] comme un paramètre de type d'une classe, qui peut être différent de KeyVal[_,_].

Je sais ce que je demande est probablement fou si vous comprenez comment l'inférence de type fonctionne, mais je ne le fais pas! Et je ne sais même pas ce qui serait une bonne façon de procéder - à part de faire des déclarations de type explicites tout le long de mon code de haut niveau. Devrais-je simplement changer toute l'architecture?

Répondre

3

Pour cela, vous aurez besoin de types dépendants du chemin. Je recommande ce qui suit:

D'abord, écrire un trait qui a vos types pertinents en tant que membres, afin que vous puissiez y accéder dans les définitions:

trait KeyValAux { 
    type K 
    type V 
    type KV = KeyVal[K, V] 
} 

Maintenant, vous pouvez créer une usine pour ReducerComponent:

object ReducerComponent { 
    def apply[T <: KeyValAux](f: (T#V, T#V) => T#V) = 
    new ReducerComponent[T#K, T#V](f) 
} 

Notez qu'ici, nous pouvons simplement accéder aux membres du type. Nous ne pouvons pas faire cela pour les paramètres de type.

Maintenant, définir votre MapOutput en termes de KeyValAux (peut-être un autre nom est plus approprié pour votre cas d'utilisation):

type MapOutput = KeyValAux { type K = String; type V = Int } 

def mapFun(s:String): MapOutput#KV = KeyVal(s, 1) 

val red = ReducerComponent[MapOutput](_ + _) 

MISE À JOUR

Comme @ dk14 mentionne dans les commentaires, si vous voulez toujours la syntaxe de paramètre de type, vous pouvez effectuer les opérations suivantes:

trait OutputSpec[KK, VV] extends KeyValAux { 
    type K = KK 
    type V = VV 
} 

un puis écrire:

type MapOutput = OutputSpec[String, Int] 

Vous pouvez écrire OutputSpec en fonction du type:

type OutputSpec[KK, VV] = KeyValAux { type K = KK; type V = VV } 

Cela ne génère pas une classe inutilisée supplémentaire.

+0

il pourrait être préférable de définir 'trait KeyValAux [KK, VV] {type K = KK; type V = VV; tapez KV = KeyVal [K, V]} 'pour rendre la définition de' MapOutput' un peu plus agréable: 'type MapOutput = KeyValAux [String, Int]' – dk14

+1

Ensuite, il n'y a plus moyen d'écrire 'ReducerComponent.apply'. Mais nous pourrions écrire une sous-classe qui fait cela. – gzm0

5

Il suffit d'écrire une usine simple:

case class RC[M <: KeyVal[_, _]](){ 
    def apply[K,V](f: (V,V) => V)(implicit ev: KeyVal[K,V] =:= M) = new ReducerComponent[K,V](f) 
} 

def plus(x: Double, y: Double) = x + y 

scala> RC[KeyVal[Int, Double]].apply(plus) 
res12: ReducerComponent[Int,Double] = [email protected] 

scala> RC[KeyVal[Int, Double]]()(plus) 
res16: ReducerComponent[Int,Double] = [email protected] 

Comme vous pouvez le voir, a ReducerComponent type approprié. La preuve implicite est utilisée ici pour capturer K et V à partir de votre M <: KeyVal[_, _].

P.S. La version ci-dessus nécessite de spécifier explicitement les types de paramètres pour votre f, comme (_: Double) + (_: Double). Si vous voulez éviter cela:

case class RC[M <: KeyVal[_, _]](){ 
    def factory[K,V](implicit ev: KeyVal[K,V] =:= M) = new { 
     def apply(f: (V,V) => V) = new ReducerComponent[K,V](f) 
    } 
} 

scala> RC[KeyVal[Int, Double]].factory.apply(_ + _) 
res5: ReducerComponent[Int,Double] = [email protected] 


scala> val f = RC[KeyVal[Int, Double]].factory 
f: AnyRef{def apply(f: (Double, Double) => Double): ReducerComponent[Int,Double]} = [email protected] 

scala> f(_ + _) 
res13: ReducerComponent[Int,Double] = [email protected] 

Mise à jour. Si vous voulez generelize keyval - utilisez la fonction de type:

type KV[K,V] = KeyVal[K,V] //may be anything, may implement `type KV[K,V]` from some supertrait 

case class RC[M <: KV[_, _]](){ 
    def factory[K,V](implicit ev: KV[K,V] =:= M) = new { 
    def apply(f: (V,V) => V) = new ReducerComponent[K,V](f) 
    } 
} 

Mais gardez à l'esprit que apply de votre question prend encore KeyVal[K,V].

Vous pouvez également passer KV dans une classe:

class Builder[KV[_,_]] { 
    case class RC[M <: KV[_, _]](){ 
    def factory[K,V](implicit ev: KV[K,V] =:= M) = new { 
     def apply(f: (V,V) => V) = new ReducerComponent[K,V](f) 
    } 
    } 
} 

scala> val b = new Builder[KeyVal] 
scala> val f = b.RC[KeyVal[Int, Double]].factory 
scala> f(_ + _) 
res2: ReducerComponent[Int,Double] = [email protected] 
+1

Bien! Je n'ai pas pensé à utiliser des preuves de cette façon. +1 car il n'a pas besoin de membres de type (ce qui tend à confondre les gens). – gzm0

+0

Merci, je n'étais pas vraiment sûr que cela fonctionnera :) – dk14

+0

En fait, j'ai erreur de compilation 'arguments de type [A $ A292.this.MapOutput] ne sont pas conformes à la méthode appliquer les limites de paramètre de type [M <: Map [_, _]] ' – Odomontois