2010-01-10 14 views
7

J'ai un ensemble de classes de modèles, et un ensemble d'algorithmes qui peuvent être exécutés sur les modèles. Toutes les classes de modèles ne peuvent pas exécuter tous les algorithmes. Je veux que les classes de modèles puissent déclarer quels algorithmes ils peuvent effectuer. Les algorithmes qu'un modèle peut effectuer peuvent dépendre de ses arguments.scala: mixins en fonction du type d'arguments

Exemple: Dire que j'ai deux algorithmes, MCMC, et l'importance, représentés comme des traits:

trait MCMC extends Model { 
    def propose... 
} 

trait Importance extends Model { 
    def forward... 
} 

J'ai une classe de modèle normal, qui prend un argument moyen, qui est lui-même un modèle. Maintenant, si mean implémente MCMC, je veux que Normal implémente MCMC, et si mean implémente Importance, je veux que Normal implémente Importance.

Je peux écrire: classe normale (moyenne: Model) étend le modèle { // des choses communes va ici }

class NormalMCMC(mean: MCMC) extends Normal(mean) with MCMC { 
    def propose...implementation goes here 
} 

class NormalImportance(mean: Importance) extends Normal(mean) with Importance { 
    def forward...implementation goes here 
} 

Je peux créer des méthodes d'usine qui font que le bon type de normal obtient créé avec un moyen donné. Mais la question évidente est, et si le moyen implémente à la fois MCMC et Importance? Ensuite, je veux que Normal les implémente tous les deux. Mais je ne veux pas créer une nouvelle classe que réimplémente proposer et transmettre. Si NormalMCMC et NormalImportance ne prenaient pas d'arguments, je pourrais en faire des traits et les mélanger. Mais ici, je veux que le mixage dépende du type de l'argument. Y a-t-il une bonne solution?

Répondre

1

Une grande partie de votre problème semble être que NormalMCMC et NormalImportance prennent des arguments, mais, comme vous le supposez correctement, les traits ne peuvent pas avoir de constructeurs. Au lieu de cela, vous pouvez prendre les paramètres que vous voudriez fournir via un constructeur de traits (si une telle chose existait) et en faire des membres abstraits du trait.

Les membres sont ensuite réalisés lorsque le trait est construit.

Étant donné:

trait Foo { 
    val x : String //abstract 
} 

vous pouvez l'utiliser comme une des opérations suivantes:

new Bar with Foo { val x = "Hello World" } 

new Bar { val x = "Hello World" } with Foo 

Ce qui vous rend la fonctionnalité équivalente à l'aide des constructeurs de traits.

Notez que si le type Bar dispose déjà d'un non-abstrait val x : String alors vous pouvez simplement utiliser

new Bar with Foo 

Dans certains scénarios, il peut aussi aider à faire x paresseux, ce qui peut vous donne plus de flexibilité si l'ordre d'initialisation devrait devenir un problème.

7

L'utilisation self types vous permet de séparer les implémentations Modèle-algorithme des instanciations et les mélanger:

trait Model 
trait Result 
trait MCMC extends Model { 
    def propose: Result 
} 
trait Importance extends Model { 
    def forward: Result 
} 

class Normal(val model: Model) extends Model 

trait NormalMCMCImpl extends MCMC { 
    self: Normal => 
    def propose: Result = { //... impl 
    val x = self.model // lookie here... I can use vals from Normal 
    } 
} 
trait NormalImportanceImpl extends Importance { 
    self: Normal => 
    def forward: Result = { // ... impl 
     ... 
    } 
} 

class NormalMCMC(mean: Model) extends Normal(mean) 
           with NormalMCMCImpl 

class NormalImportance(mean: Model) extends Normal(mean) 
            with NormalImportanceImpl 

class NormalImportanceMCMC(mean: Model) extends Normal(mean) 
             with NormalMCMCImpl 
             with NormalImportanceImpl 
4

Merci à Kevin, Mitch et Naftali Gugenheim et Daniel Sobral sur les utilisateurs à l'échelle liste de diffusion, J'ai une bonne réponse. Les deux réponses précédentes fonctionnent, mais conduisent à une explosion exponentielle du nombre de traits, de classes et de constructeurs. Cependant, l'utilisation d'implicits et de limites d'affichage évite ce problème.Les étapes de la solution sont:

1) Donnez à Normal un paramètre de type représentant le type de son argument. 2) Définir les implicits qui prennent un Normal avec le bon type d'argument à celui qui implémente l'algorithme approprié. Par exemple, makeImportance prend un Normal [Importance] et produit un NormalImportance. 3) Les implicits doivent recevoir un type lié. La raison en est que sans le type lié, si vous essayez de passer un Normal [T] à makeImportance où T est un sous-type d'Importance, cela ne fonctionnera pas car Normal [T] n'est pas un sous-type de Normal [Importance] pas covariant. 4) Ces limites de type doivent être des limites de vue pour permettre aux implicites de s'enchaîner.

est ici la solution complète:

class Model 

trait Importance extends Model { 
    def forward: Int 
} 

trait MCMC extends Model { 
    def propose: String 
} 

class Normal[T <% Model](val arg: T) extends Model 

class NormalImportance(arg: Importance) extends Normal(arg) with Importance { 
    def forward = arg.forward + 1 
} 

class NormalMCMC(arg: MCMC) extends Normal(arg) with MCMC { 
    def propose = arg.propose + "N" 
} 

object Normal { 
    def apply[T <% Model](a: T) = new Normal[T](a) 
} 

object Importance { 
    implicit def makeImportance[T <% Importance](n: Normal[T]): Importance = 
    new NormalImportance(n.arg) 
} 

object MCMC { 
    implicit def makeMCMC[T <% MCMC](n: Normal[T]): MCMC = new NormalMCMC(n.arg) 
} 

object Uniform extends Model with Importance with MCMC { 
    def forward = 4 
    def propose = "Uniform" 
} 

def main(args: Array[String]) { 
    val n = Normal(Normal(Uniform)) 
    println(n.forward) 
    println(n.propose) 
}