我是 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)
问题 当然,我们使用带 map 的 zip。但是,这不起作用:
p zip v map { case (x,y) => x.density(y) }
### found : Any
# required: x.T
警告:容器的选择
一个有效的问题是想知道为什么我选择 List[Any]
作为保存数据值的容器,而不是 List[Double]
,或者List[T <: Double]
。考虑当我们的一些概率分布支持向量甚至矩阵(例如多元正态和逆 Wishart)时的情况
解决这个问题的一个想法可能是将我们的输入值存放在更能代表我们的输入类型的容器中。例如类似的东西
class likelihoodSupport
val v = List[likelihoodSupport](...)
哪里Int
, Double
,和Array[Double]
甚至一个元组 (Array[Double], Array[Array[Double]])
全部继承自likelihoodSupport
。然而,由于其中一些类是最终的,这是不可能的。
一次(糟糕的)修复
请注意,这可以通过在每个子类中使用模式匹配和多态方法来处理,但正如 Odersky 可能会说的那样这有一种代码味道:
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) }
恳求我知道我想要做的事情应该很容易用如此美丽而强大的语言完成,但我不知道如何实现!非常感谢您的帮助。
注意我对使用额外的包/导入不感兴趣,因为我觉得这个问题应该在基础 Scala 中轻松解决。
最佳答案
给定单独的 p
和 v
列表(至少没有强制转换,或者通过编写自己的 HList
库),您无法做到这一点)。这应该是显而易见的:如果您更改这些列表之一中元素的顺序,类型不会更改(与 HList
不同),但分布现在将与错误类型的值配对!
最简单的方法是添加强制转换:
p zip v map { case (x,y) => x.density(y.asInstanceOf[x.T]) }
请注意,由于 JVM 类型删除,这在运行时可能是无操作,并导致 密度
调用内出现 ClassCastException
。
如果您想要一个更安全的强制转换替代方案,类似这样的方法应该可行(有关 ClassTags
和相关类型的更多信息,请参阅 http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html):
// 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 _)
问题仅在特定情况下出现
The user may wish to make multiple density queries with different x values that are not intrinsically related to the probability distribution itself
并且编译器无法证明这些值具有正确的类型。
关于scala - 方法参数中具有抽象类型的特征,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32516168/