[英]Trait with Abstract Type in Method Argument
我是 Scala 的新手,正在构建用于统计估计的工具。 考虑以下情况:定义了一个特征probabilityDistribution
,它保证从它继承的类将能够执行某些功能,例如计算密度。 概率分布的两个此类示例可能是二项式分布和 beta 分布。 这两个函数的支持分别是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
列表来表示我们的特征。
val p = List(binomial(5, .5), beta(.5,.5))
假设我们现在有兴趣提供假设数据值的向量,并希望查询每个概率分布的density
函数。
val v = List[Any](2, 0.75)
问题当然,我们使用带有地图的 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 类型擦除,这可能是运行时的空操作,并导致density
调用内部出现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 _)
问题仅在特定情况下出现
用户可能希望使用与概率分布本身没有内在关系的不同 x 值进行多个密度查询
并且编译器无法证明这些值具有正确的类型。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.