2015-09-11 6 views
2

Я новичок в Scala и создаю инструменты для статистической оценки. Рассмотрим следующее: определена черта probabilityDistribution, которая гарантирует, что классы, которые наследуют от нее, смогут выполнять определенные функции, такие как вычисление плотности. Два таких примера распределения вероятностей могут быть биномиальным и бета-распределением. Поддержка этих двух функций - Int и Double, соответственно.Характеристика с абстрактным типом метода Аргумент

Настройка

trait probabilityDistribution extends Serializable { 
    type T 
    def density(x: T): Double 
} 

case class binomial(n: Int, p: Double) extends probabilityDistribution { 
    type T = Int 
    def density(x: Int): Double = x*p 
} 

case class beta(alpha: Double, beta: Double) extends probabilityDistribution { 
    type T = Double 
    def density(x: Double): Double = x*alpha*beta 
} 

Обратите внимание, что фактические математические реализации этих density методов упрощенным выше. Теперь рассмотрим модель смеси, в которой у нас есть несколько функций или переменных, которые поступают из разных распределений. Мы можем создать список probabilityDistribution s для представления наших функций.

val p = List(binomial(5, .5), beta(.5,.5)) 

Предположит, что теперь мы заинтересованы в поставках вектора гипотетических значений данных, и хочет запросить density функции для каждого соответствующего распределения вероятностей.

val v = List[Any](2, 0.75) 

Проблема Конечно, мы используем молнию с картой. Однако, это не работает:

p zip v map { case (x,y) => x.density(y) } 
### found : Any 
    # required: x.T 

Оговорка: Выбор контейнера Действительный вопрос заключается задаваться вопросом, почему я выбрал List[Any] в качестве контейнера для хранения значений данных, а не List[Double], или, возможно, List[T <: Double]. Рассмотрим случай, когда некоторые из наших вероятностных распределений имеют поддержку над векторами или даже матриц (например, многомерное нормальное и обратное Уишарта)

Идея обратиться предостережение может быть вместо вмещать наши входные значения в контейнере, который является более представительный тип ввода. например что-то вроде

class likelihoodSupport 
val v = List[likelihoodSupport](...) 

где Int, Double и Array[Double] и даже кортеж (Array[Double], Array[Array[Double]]) все унаследует от likelihoodSupport. Однако, поскольку некоторые из этих классов являются окончательными, это невозможно.

Один (вшивом) Фикс

Обратите внимание, что это может быть обработан с помощью сопоставления с образцом и полиморфный метод в пределах каждого подкласса, но, как Одерски может сказать это имеет код запах:

trait probabilityDistribution extends Serializable { 
    type T 
    def density[T](x: T): Double 
} 

case class binomial(n: Int, p: Double) extends probabilityDistribution { 
    type T = Int 
    def density[U](x: U): Double = x match {case arg: Int => arg * p } 
} 

case class beta(alpha: Double, beta: Double) extends probabilityDistribution { 
    type T = Double 
    def density[U](x: U): Double = x match {case arg: Double => arg * alpha * beta} 
} 

Теперь мы можем запустить

p zip v map { case (x,y) => x.density(y) } 

Plea Я знаю, что я пытаюсь сделать очень легко на таком красивом и мощном языке, но я не могу понять, как это сделать! Ваша помощь очень ценится.

Примечание Я не заинтересован в использовании дополнительных упаковок/импортов, поскольку, по моему мнению, эту проблему необходимо решить тривиально в базе Scala.

+2

Почему бы просто не использовать простые дженерики 'ProbabilityDistribution [T]'? –

+0

Для списка с известными различными типами элементов существует 'HList' (например, в бесформенном виде). –

+0

Я понимаю, что «простые дженерики» ничего не покупают; использование абстрактного типа в определении признака эквивалентно в этом случае. Кроме того, я не заинтересован в импорте дополнительных пакетов, так как я считаю, что эта проблема должна быть тривиально решена в базовой scala. – Andreas

ответ

2

Вы не можете сделать это, учитывая отдельные списки p и v (по крайней мере, без бросков или написав собственную библиотеку HList). Это должно быть очевидно: если вы измените порядок элементов в одном из этих списков, типы не будут меняться (в отличие от HList), но теперь дистрибутивы будут сопряжены со значениями неправильного типа!

Самый простой подход заключается в добавлении ролях:

p zip v map { case (x,y) => x.density(y.asInstanceOf[x.T]) } 

Обратите внимание, что это не может быть не-оп на время выполнения и привести к ClassCastException внутри density вызова вместо этого, благодаря типу стиранием виртуальной машины Java.

Если вы хотите более безопасную альтернативу гипса, что-то, как это должно работать (см http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html для получения дополнительной информации о ClassTags и связанных с ними типов):

// note that generics do buy you some convenience in this case: 
// abstract class probabilityDistribution[T](implicit val tag: ClassTag[T]) extends Serializable 
// will mean you don't need to set tag explicitly in subtypes 
trait probabilityDistribution extends Serializable { 
    type T 
    implicit val tag: ClassTag[T] 
    def density(x: T): Double 
} 

case class binomial(n: Int, p: Double) extends probabilityDistribution { 
    type T = Int 
    val tag = classTag[Int] 
    def density(x: Int): Double = x*p 
} 

p zip v map { (x,y) => 
    implicit val tag: ClassTag[x.T] = x.tag 
    y match { 
    case y: x.T => ... 
    case _ => ... 
    } 
} 

Или вы можете комбинировать распределения и значения (или структуры данных содержащие значения, функции, возвращающие значения и т.д.):

// alternately DistribWithValue(d: probabilityDistribution)(x: d.T) 
case class DistribWithValue[A](d: probabilityDistribution { type T = A }, x: A) { 
    def density = d.density(x) 
} 

val pv: List[DistribWithValue[_]] = List(DistribWithValue(binomial(5, .5), 2), DistribWithValue(beta(.5,.5), 0.75)) 

// if you want p and v on their own 
val p = pv.map(_.d) 
val v = pv.map(_.x) 

конечно, если вы хотите использовать probabilityDistribution в качестве аргумента метода а, в качестве вопрос название говорит, что это просто, например:

def density(d: probabilityDistribution)(xs: List[d.T]) = xs.map(d.density _) 

Проблемы возникают только конкретно, когда

Пользователь может пожелать, чтобы сделать несколько запросов плотности с различными значениями х, которые по своей сути не связаны с вероятностью распределение само

и компилятор не может доказать , что эти значения имеют правильный тип.

+0

Спасибо. Проблема с этим подходом заключается в том, что я не хочу _attach_ значение 'x' для объекта. Пользователь может пожелать сделать несколько запросов «плотности» с разными значениями «x», которые не связаны с самим распределением вероятности. Что касается 'asInstanceOf', невозможно ли сопоставить шаблон' y' на карте из-за стирания типа? – Andreas

+1

В этом случае, как я уже сказал, вам нужно либо 1) прибегнуть к кастам; 2) напишите свой собственный «HList» или используйте существующую библиотеку; 3) не имеют списка вероятностных распределений с разными 'T'. Я отредактирую, чтобы ответить на ваш вопрос. –