2010-01-10 13 views
7

Tengo un conjunto de clases de modelos y un conjunto de algoritmos que se pueden ejecutar en los modelos. No todas las clases de modelos pueden realizar todos los algoritmos. Quiero que las clases modelo puedan declarar qué algoritmos pueden realizar. Los algoritmos que puede realizar un modelo pueden depender de sus argumentos.scala: mixins según el tipo de argumentos

Ejemplo: Digamos que tengo dos algoritmos, MCMC, e importancia, representados como rasgos:

trait MCMC extends Model { 
    def propose... 
} 

trait Importance extends Model { 
    def forward... 
} 

Tengo una clase del modelo normal, que toma un argumento media, que es en sí mismo un modelo. Ahora, si mean implementa MCMC, quiero que Normal implemente MCMC, y si mean implementa Importancia, quiero que Normal implemente Importancia.

Puedo escribir: clase normal (media: Modelo) se extiende modelo {// algunas cosas comunes va aquí}

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 
} 

puedo crear métodos de fábrica que hacen que el tipo correcto de la Normal se creado con una media determinada. Pero la pregunta obvia es, ¿qué pasa si Mean implementa tanto MCMC como Importance? Entonces quiero que Normal implemente ambos también. Pero no quiero crear una nueva clase que los reembolsos propongan y reenvíen. Si NormalMCMC y NormalImportance no toman argumentos, podría convertirlos en rasgos y mezclarlos. Pero aquí quiero que la mezcla dependa del tipo de argumento. ¿Hay una buena solución?

Respuesta

1

Gran parte de su problema parece ser que NormalMCMC y NormalImportance toman argumentos, pero, como implica correctamente, los rasgos no pueden tener constructores.

En su lugar, puede tomar los parámetros que desea suministrar a través de un constructor de rasgo (si tal cosa existía) y hacerlos miembros abstractos del rasgo.

Los miembros se hacen realidad cuando se construye el rasgo.

dado:

trait Foo { 
    val x : String //abstract 
} 

se puede utilizar como una de las siguientes:

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

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

Qué le devuelve la funcionalidad equivalente de usar constructores rasgo.

Tenga en cuenta que si el tipo de Bar ya tiene un no-abstracto val x : String, entonces puede simplemente usar

new Bar with Foo 

En algunos casos también puede ayudar a hacer x vago, que puede da más flexibilidad si el orden de inicialización debería convertirse en un problema.

7

Usando self types le permite separar las implementaciones Modelo-Algoritmo de las instancias y los mezcla en:

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

Gracias a Kevin, Mitch, y Naftoli Gugenheim y Daniel Sobral en la escala de lista de distribución, Tengo una buena respuesta. Las dos respuestas anteriores funcionan, pero conducen a una explosión exponencial en el número de rasgos, clases y constructores. Sin embargo, el uso de implícitos y límites de vista evita este problema.Los pasos de la solución son:

1) Otorgue a Normal un parámetro de tipo que represente el tipo de su argumento. 2) Defina implícitos que toman una Normal con el tipo correcto de argumento a uno que implementa el algoritmo apropiado. Por ejemplo, makeImportance toma una Normal [Importancia] y produce una Importancia Normal. 3) Los implicits deben tener un tipo vinculado. El motivo es que sin el tipo vinculado, si intenta pasar un [T] Normal para hacer Importancia donde T es un subtipo de Importancia, no funcionará porque Normal [T] no es un subtipo de Normal [Importancia] porque Normal es no covariante 4) Estos límites de tipo necesitan ser límites de vista para permitir que las implícitas encadenen.

Aquí es la solución completa:

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) 
} 
Cuestiones relacionadas