简体   繁体   English

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

[英]Trait with Abstract Type in Method Argument

I am new to Scala and am building tools for statistical estimation.我是 Scala 的新手,正在构建用于统计估计的工具。 Consider the following: a trait probabilityDistribution is defined, which guarantees that classes which inherit from it will be able to perform certain functions, such as compute a density.考虑以下情况:定义了一个特征probabilityDistribution ,它保证从它继承的类将能够执行某些功能,例如计算密度。 Two such examples of probability distributions might be a binomial and beta distribution.概率分布的两个此类示例可能是二项式分布和 beta 分布。 The support of these two functions is Int and Double , respectively.这两个函数的支持分别是IntDouble

Set Up设置

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
}

Note that the actual mathematical implementations of the density methods are simplified above.请注意,上面简化了density方法的实际数学实现。 Now, consider a Mixture Model, in which we have several features or variables which come from different distributions.现在,考虑一个混合模型,其中我们有几个来自不同分布的特征或变量。 We may choose to create a list of probabilityDistribution s to represent our features.我们可以选择创建一个probabilityDistribution列表来表示我们的特征。

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

Suppose that we are now interested in supplying a vector of hypothetical data values, and wish to query the density functions for each respective probability distribution.假设我们现在有兴趣提供假设数据值的向量,并希望查询每个概率分布的density函数。

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

The Problem Of course, we use a zip with map.问题当然,我们使用带有地图的 zip。 However, this doesn't work:但是,这不起作用:

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

Caveat: Choice of Container A valid question is to wonder why I have chosen List[Any] as the container to hold data values, rather than List[Double] , or perhaps List[T <: Double] .警告:容器选择一个有效的问题是想知道为什么我选择List[Any]作为保存数据值的容器,而不是List[Double] ,或者List[T <: Double] Consider the case when some of our probability distributions have a support over vectors or even matrices (eg multivariate normal and inverse Wishart)考虑我们的一些概率分布支持向量甚至矩阵的情况(例如多元正态和逆 Wishart)

An idea to address the caveat might be to instead house our input values in a container that is more representative of our input type.解决这个问题的一个想法可能是将我们的输入值存放在一个更能代表我们的输入类型的容器中。 eg something like例如像

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

where Int , Double , and Array[Double] and even a tuple (Array[Double], Array[Array[Double]]) all inherit from likelihoodSupport .其中IntDoubleArray[Double]甚至一个元组(Array[Double], Array[Array[Double]])都继承自likelihoodSupport As some of these classes are final, however, this is not possible.然而,由于其中一些课程是最终课程,这是不可能的。

One (Crummy) Fix一个(糟糕的)修复

Note that this can be handled by using pattern matching and a polymorphic method within each subclass, but as Odersky might say this has a code smell :请注意,这可以通过在每个子类中使用模式匹配和多态方法来处理,但正如 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}
}

We can now run我们现在可以运行

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

Plea I know what I'm trying to do should be very easily accomplished in such a beautiful and powerful language, but I can't figure out how!恳求我知道我想要做的事情应该很容易用这样一种美丽而强大的语言来完成,但我不知道怎么做! Your help is much appreciated.非常感谢您的帮助。

Note I am not interested in using additional packages/imports, as I feel this problem should be trivially solved in base Scala.注意我对使用额外的包/导入不感兴趣,因为我觉得这个问题应该在基础 Scala 中轻松解决。

You can't do it given the separate p and v lists (at least without casts, or by writing your own HList library).给定单独的pv列表(至少没有强制转换,或者通过编写自己的HList库),您无法做到这一点。 This should be obvious: if you change the order of elements in one of these lists, the types won't change (unlike for HList ), but distributions will now be paired with values of a wrong type!这应该是显而易见的:如果您更改这些列表之一中元素的顺序,则类型不会更改(与HList不同),但分布现在将与错误类型的值配对!

The simplest approach is to add a cast:最简单的方法是添加一个演员表:

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

Note that this may be a no-op at the runtime and lead to a ClassCastException inside density call instead, thanks to JVM type erasure.请注意,由于 JVM 类型擦除,这可能是运行时的空操作,并导致density调用内部出现ClassCastException

If you want a safer alternative to the cast, something like this should work (see http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html for more information on ClassTags and related types):如果你想要一个更安全的替代方案,这样的事情应该可以工作(有关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 _ => ... 
  }
}

Or you can combine distributions and values (or data structures containing values, functions returning values, etc.):或者您可以组合分布和值(或包含值的数据结构、返回值的函数等):

// 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)

Of course, if you want to use a probabilityDistribution as a method argument , as the question title says, it's simple, for example:当然,如果你想使用一个probabilityDistribution作为方法参数,正如问题标题所说,很简单,例如:

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

The problems only arise specifically when问题仅在特定情况下出现

The user may wish to make multiple density queries with different x values that are not intrinsically related to the probability distribution itself用户可能希望使用与概率分布本身没有内在关系的不同 x 值进行多个密度查询

and the compiler can't prove that these values have the correct type.并且编译器无法证明这些值具有正确的类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM