2015年3月29日 星期日

Scala simple note - Type class & Context bound (could complement later)

最近讀了這篇文章, 對type class 有點了解, 先來做個紀錄, 未來實作或者更加了解之後會來補足

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初步應用

但應該有更簡潔的應用
所以接著

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去抓取

大致以上
待經驗補完


沒有留言:

張貼留言