Type class
這篇文章的情境是針對每種Number類型的class 都要做 Statistic運算
那麼就得自己實作各種比如Double, Int, 等之類的Statistic運算method
當然可以透過method overload
但就是得不斷重複定義相同的class
另一個更簡單的Generic作法就是Scala的Type class
今天定義個NumberLike trait type class
object Math {
trait NumberLike[T] {
def plus(x: T, y: T): T
def divide(x: T, y: Int): T
def minus(x: T, y: T): T
}
}
所謂的Type class定義
1 他會接受一個或多個以上的Type parameters
所以這邊NumberLike[T] 就是定義他吃一個Type parameters
2 然後這個trait的任何method都是stateless, 比如只針對method所接受到的method做運算
接著Library author可預先定義, 某些已知Type的 Statistic運算
object Math {
trait NumberLike[T] {
def plus(x: T, y: T): T
def divide(x: T, y: Int): T
def minus(x: T, y: T): T
}
object NumberLike {
implicit object NumberLikeDouble extends NumberLike[Double] {
def plus(x: Double, y: Double): Double = x + y
def divide(x: Double, y: Int): Double = x / y
def minus(x: Double, y: Double): Double = x - y
}
implicit object NumberLikeInt extends NumberLike[Int] {
def plus(x: Int, y: Int): Int = x + y
def divide(x: Int, y: Int): Int = x / y
def minus(x: Int, y: Int): Int = x - y
}
}
}
這篇先定義好
NumberLikeDouble
NumberLikeInt
這邊本文有提到其實未來不頓欸每一種都實作
NumberLike只是一個拿來narrow down
但這邊我不太清楚, 容後再述
這邊要注意是implicit的keyword,
這是讓Type class實現的一大要素
來看看怎麼用
object Statistics {
import Math.NumberLike
def mean[T](xs: Vector[T])(implicit ev: NumberLike[T]): T =
ev.divide(xs.reduce(ev.plus(_, _)), xs.size)
}
這邊開始實作Statistic運算的mean method
mean也吃一個[T] 的type parameter
然後Vector[T]參數就是說
是這個T class的collection要做Statistic運算
然後該注意到是
implicit ev: NumberLike[T]
因為他已經先 import Math.NumberLike 裡面有先定義好的NumberLikeDouble
NumberLikeDouble其實是NumberLike[Double]
那麼當call mean用 Vector[Double]時候
比如
val numbers = Vector[Double](13, 23.0, 42, 45, 61, 73, 96, 100, 199, 420, 900, 3839)
println(Statistics.mean(numbers))
他就會轉換成
mean[Double](xs: Vector[Double])(implicit ev: NumberLike[Double])
這時候就會call到
implicit object NumberLikeDouble extends NumberLike[Double]
這個implicit parameter
因為NumberLikeDouble 繼承 NumberLike[Double]
這就是Type class初步應用
但應該有更簡潔的應用
所以接著
就好比mean[T]只吃一個, 而非吃兩個mean[T, F]
這時候implicit parameter的(implicit ev: NumberLike[T])可做簡化
比如
Context bound T : NumberLike就是說
這就是Type class初步應用
但應該有更簡潔的應用
所以接著
Context bound
如果今天只吃一個Type parameter就好比mean[T]只吃一個, 而非吃兩個mean[T, F]
這時候implicit parameter的(implicit ev: NumberLike[T])可做簡化
比如
object Statistics {
import Math.NumberLike
def mean[T](xs: Vector[T])(implicit ev: NumberLike[T]): T =
ev.divide(xs.reduce(ev.plus(_, _)), xs.size)
def median[T : NumberLike](xs: Vector[T]): T = xs(xs.size / 2)
def quartiles[T: NumberLike](xs: Vector[T]): (T, T, T) =
(xs(xs.size / 4), median(xs), xs(xs.size / 4 * 3))
def iqr[T: NumberLike](xs: Vector[T]): T = quartiles(xs) match {
case (lowerQuartile, _, upperQuartile) =>
implicitly[NumberLike[T]].minus(upperQuartile, lowerQuartile)
}
}
Context bound T : NumberLike就是說
implicit value of type NumberLike[T]在這個scope下必須要提供
所以就好比已經幫你宣告好(implicit ev: NumberLike[T])在第二個parameter
你可以省去(implicit ev: NumberLike[T])這段
另一個特殊的是
implicitly[NumberLike[T]].minus(upperQuartile, lowerQuartile)
implicity是一個keyword method
他會根據把你給他的type提出那個implicit parameter
就是說提出ev 這個parameter讓你直接存取使用
比如說剛剛的
mean[Double](xs: Vector[Double])(implicit ev: NumberLike[Double])
implicitly[NumberLike[T]]會變成implicitly[NumberLike[Double]]
進而幫你抓到NumberLikeDouble的個object
讓你直接call minus
就好比implicitly[NumberLike[Double]].minus = NumberLikeDouble.minus
你可以說那直接用ev這個name去存取
但是因為剛剛用Context bound T : NumberLike
我們也省去宣告(implicit ev: NumberLike[T])
只有def iqr[T: NumberLike](xs: Vector[T])
scope內是有implicit NumberLike[T]但它沒有名字!!
所以得透過implicity去抓取
大致以上
待經驗補完
他會根據把你給他的type提出那個implicit parameter
就是說提出ev 這個parameter讓你直接存取使用
比如說剛剛的
mean[Double](xs: Vector[Double])(implicit ev: NumberLike[Double])
implicitly[NumberLike[T]]會變成implicitly[NumberLike[Double]]
進而幫你抓到NumberLikeDouble的個object
讓你直接call minus
就好比implicitly[NumberLike[Double]].minus = NumberLikeDouble.minus
你可以說那直接用ev這個name去存取
但是因為剛剛用Context bound T : NumberLike
我們也省去宣告(implicit ev: NumberLike[T])
只有def iqr[T: NumberLike](xs: Vector[T])
scope內是有implicit NumberLike[T]但它沒有名字!!
所以得透過implicity去抓取
大致以上
待經驗補完
沒有留言:
張貼留言