[英]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.