scala: mixins в зависимости от типа аргументов - PullRequest
7 голосов
/ 10 января 2010

У меня есть набор классов моделей и набор алгоритмов, которые можно запускать на моделях. Не все классы моделей могут выполнять все алгоритмы. Я хочу, чтобы классы моделей могли объявлять, какие алгоритмы они могут выполнять. Алгоритмы, которые может выполнять модель, могут зависеть от ее аргументов.

Пример: скажем, у меня есть два алгоритма, MCMC и Важность, представленные в виде черт:

trait MCMC extends Model {
  def propose...
}

trait Importance extends Model {
  def forward...
}

У меня есть класс модели Normal, который принимает средний аргумент, который сам является Моделью. Теперь, если Mean реализует MCMC, я хочу, чтобы Normal реализовал MCMC, а если mean реализует Важность, я хочу, чтобы Normal реализовал Важность.

Я могу написать: Класс Normal (имеется в виду: Model) расширяет Model { // некоторые общие вещи идут здесь }

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
}

Я могу создавать фабричные методы, которые гарантируют, что правильный тип Normal будет создан с заданным средним значением. Но очевидный вопрос заключается в том, что если средство Mean реализует и MCMC, и важность Тогда я хочу, чтобы Normal тоже их обоих реализовал. Но я не хочу создавать новый класс, который предлагает и продвигает переопределения. Если бы NormalMCMC и NormalImportance не принимали аргументы, я мог бы сделать их чертами и смешать их. Но здесь я хочу, чтобы смешивание зависело от типа аргумента. Есть ли хорошее решение?

Ответы [ 3 ]

7 голосов
/ 11 января 2010

Использование self types позволяет вам отделить реализации Модель-Алгоритм от экземпляров и смешать их в:

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 голосов
/ 12 января 2010

Благодаря Кевину, Митчу, Нафтоли Гугенхайму и Даниэлю Собралу в списке рассылки для пользователей шкалы, у меня хороший ответ. Два предыдущих ответа работают, но приводят к экспоненциальному увеличению числа признаков, классов и конструкторов. Однако использование импликатов и границ просмотра позволяет избежать этой проблемы. Шаги решения:

1) Дайте Normal параметр типа, представляющий тип его аргумента. 2) Определите имплики, которые принимают Normal с правильным типом аргумента к тому, который реализует соответствующий алгоритм. Например, makeImportance принимает Normal [Importance] и создает NormalImportance. 3) имплицитам нужно дать ограничение типа. Причина в том, что без привязки типа, если вы попытаетесь передать Normal [T] в makeImportance, где T - это подтип Importance, он не будет работать, потому что Normal [T] не является подтипом Normal [Importance], потому что Normal - это не ковариантный. 4) Эти типы границ должны быть границами просмотра, чтобы позволить имплицитам связываться.

Вот полное решение:

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)
}
1 голос
/ 10 января 2010

Кажется, большая часть вашей проблемы заключается в том, что NormalMCMC и NormalImportance принимают аргументы, но, как вы правильно понимаете, черты не могут иметь конструкторов.

Вместо этого вы можете взять параметры, которые вы хотите предоставить через конструктор признаков (если такая вещь существовала), и сделать их абстрактными членами свойства.

Члены тогда осознают, когда черта построена.

Дано:

trait Foo {
  val x : String //abstract
}

Вы можете использовать его как одно из следующих:

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

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

Что возвращает вам эквивалентную функциональность использования конструкторов Trait.

Обратите внимание, что если тип Bar уже имеет неабстрактный val x : String, то вы можете просто использовать

new Bar with Foo

В некоторых сценариях это также может помочь сделать x ленивым, что может дать вам большую гибкость, если порядок инициализации станет проблемой.

...