English 中文(简体)
scala: mixins depending on type of arguments
原标题:

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

Example: Say I have two algorithms, MCMC, and Importance, represented as traits:

trait MCMC extends Model {
  def propose...
}

trait Importance extends Model {
  def forward...
}

I have a model class Normal, which takes a mean argument, which is itself a Model. Now, if mean implements MCMC, I want Normal to implement MCMC, and if mean implements Importance, I want Normal to implement Importance.

I can write: class Normal(mean: Model) extends Model { // some common stuff goes 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 make sure the right kind of Normal gets created with a given mean. But the obvious question is, what if mean implements both MCMC and Importance? Then I want Normal to implement both of them too. But I don t want to create a new class that reimplements propose and forward. If NormalMCMC and NormalImportance didn t take arguments, I could make them traits and mix them in. But here I want the mixing in to depend on the type of the argument. Is there a good solution?

问题回答

Using self types allows you to separate the Model-Algorithm implementations from the instantiations and mix them in:

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

Thanks to Kevin, Mitch, and Naftoli Gugenheim and Daniel Sobral on the scale-users mailing list, I have a good answer. The two previous answers work, but lead to an exponential blowup in the number of traits, classes and constructors. However, using implicits and view bounds avoids this problem. The steps of the solution are:

1) Give Normal a type parameter representing the type of its argument. 2) Define implicits that take a Normal with the right type of argument to one that implements the appropriate algorithm. For example, makeImportance takes a Normal[Importance] and produces a NormalImportance. 3) The implicits need to be given a type bound. The reason is that without the type bound, if you try to pass a Normal[T] to makeImportance where T is a subtype of Importance, it will not work because Normal[T] is not a subtype of Normal[Importance] because Normal is not covariant. 4) These type bounds need to be view bounds to allow the implicits to chain.

Here s the full 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)
}

Much of your problem seems to be that NormalMCMC and NormalImportance take arguments but, as you correctly imply, traits can t have constructors.

Instead, you can take the parameters that you d want to supply via a trait constructor (if such a thing existed) and make them abstract members of the trait.

The members then get realised when the trait is constructed.

Given:

trait Foo {
  val x : String //abstract
}

you can use it as either of the following:

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

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

Which gives you back the equivalent functionality of using Trait constructors.

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

new Bar with Foo

In some scenarios it can also help to make x lazy, which can gives you more flexibility if initialization order should become an issue.





相关问题
How to flatten a List of different types in Scala?

I have 4 elements:List[List[Object]] (Objects are different in each element) that I want to zip so that I can have a List[List[obj1],List[obj2],List[obj3],List[obj4]] I tried to zip them and I ...

To use or not to use Scala for new Java projects? [closed]

I m impressed with Twitter and investigating to use Scala for a new large scale web project with Hibernate and Wicket. What do you think about Scala, and should I use it instead of Java? EDIT: And, ...

Why does Scala create a ~/tmp directory when I run a script?

When I execute a Scala script from the command line, a directory named "tmp" is created in my home directory. It is always empty, so I simply deleted it without any apparent problem. Of course, when I ...

Include jar file in Scala interpreter

Is it possible to include a jar file run running the Scala interpreter? My code is working when I compile from scalac: scalac script.scala -classpath *.jar But I would like to be able to include a ...

Scala and tail recursion

There are various answers on Stack Overflow which explain the conditions under which tail recursion is possible in Scala. I understand the limitations and how and where I can take advantage of tail ...

热门标签