Scala: mixins depending on the type of arguments

I have a set of model classes and a set of algorithms that can be run on models. Not all model classes can execute all algorithms. I want model classes to be able to declare which algorithms they can execute. The algorithms that a model can execute may depend on its arguments.

Example: Let's say I have two algorithms: MCMC and value, presented as attributes:

trait MCMC extends Model { def propose... } trait Importance extends Model { def forward... } 

I have a Normal model class that takes a middle argument, which itself is a model. Now, if the value implements MCMC, I want Normal to implement MCMC, and if the value implements Importance, I want Normal to apply the value.

I can write: the Normal (mean: Model) class extends the model {// Some general things go here}

 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 } 

I can create factory methods that ensure that the correct Normal type is created with the given value. But the obvious question is: what if the average implements both MCMC and value? Then I want Normal to implement both of them as well. But I do not want to create a new class that reimplements offers and more. If NormalMCMC and NormalImportance did not accept the arguments, I could make them traits and mix them. But here I want the confusion to depend on the type of argument. Is there a good solution?

+7
scala mixins traits
source share
3 answers

Using self types , you can separate Model-Algorithm implementations from instances and mix them into:

 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 
+7
source share

Thanks to Kevin, Mitch and Naftoli Guggenheim and Daniel Sbrallu on the user mailing list, I have a good answer. The two previous answers work, but lead to an exponential bloat of the number of features, classes, and constructors. However, using implications and viewing boundaries avoids this problem. Solution Steps:

1) Give Normal a type parameter representing the type of its argument. 2) Define implications that accept normal with the correct type of argument into one that implements the corresponding algorithm. For example, makeImportance takes the value Normal [Value] and creates NormalImportance. 3) Implications should be tied to type. The reason is that without type binding, if you try to pass Normal [T] to makeImportance, where T is a subtype of importance, it will not work because Normal [T] is not a subtype of Normal [value], because Normal is not covariant. 4) These type restrictions must be evaluated to allow implicit chaining.

Here's the complete solution:

 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) } 
+4
source share

Most of your problem, apparently, is that NormalMCMC and NormalImportance accept arguments, but, as you rightly mean, traits cannot have constructors.

Instead, you can take the parameters that you want to provide through the attribute constructor (if such a thing exists) and make them abstract members of the attribute.

Then the elements are implemented in the construction of the characteristic.

Given:

 trait Foo { val x : String //abstract } 

You can use it as one of the following:

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

Which gives you equivalent functionality using Trait constructors.

Note that if the Bar type already has a non-abstract val x : String , then you can just use

 new Bar with Foo 

In some scenarios, this can also help make x lazy, which can give you more flexibility if the initialization order should be a problem.

+1
source share

All Articles