方法参数中带抽象类型的特征

时间:2015-09-11 05:35:07

标签: scala types subclass traits abstract-type

我是Scala的新手,正在构建统计估算工具。请考虑以下内容:定义了特征probabilityDistribution,它保证从中继承的类能够执行某些功能,例如计算密度。概率分布的两个这样的示例可以是二项式和β分布。这两个函数的支持分别为IntDouble

设置

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)

问题 当然,我们使用带地图的拉链。但是,这不起作用:

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](...)

其中IntDoubleArray[Double]甚至元组(Array[Double], Array[Array[Double]])均来自likelihoodSupport。但是,由于其中一些类是最终的,所以这是不可能的。

One(Crummy)Fix

请注意,这可以通过在每个子类中使用模式匹配和多态方法来处理,但是正如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中轻松解决。

1 个答案:

答案 0 :(得分:2)

根据单独的pv列表(至少没有强制转换,或者编写自己的HList库),您无法执行此操作。这应该是显而易见的:如果您更改其中一个列表中的元素顺序,则类型不会更改(与HList不同),但现在分配将与错误类型的值配对!

最简单的方法是添加一个演员:

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

请注意,这可能是运行时的无操作,并且由于JVM类型擦除而导致ClassCastExceptiondensity调用。

如果您想要一个更安全的替代演员,那么这样的事情应该有用(有关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值进行多个密度查询

并且编译器不能证明这些值具有正确的类型。