scala_5_type_class
intro
def insertionSort(xs: List[Int]): List[Int] = { def insert(y: Int, ys: List[Int]): List[Int] = ys match { case List() => y :: List() case z :: zs => if (y < z) y :: z :: zs else z :: insert(y, zs) } xs match { case List() => List() case y :: ys => insert(y, insertionSort(ys)) } }
假設有個排序演算法如上,程式碼的第一個問題就是沒有泛型,只能對Int排序。那如果改成 insertionSort[T](xs: List[T]): List[T]
顯然不行,因為只有Number型別才有 y < z
方法。為了讓這個函式實現多型(polymorphic ),需要將 y < z
這部分引數化。修改函式形式:
def insertionSort[T](xs: List[T])(lessThan: (T, T) => Boolean) = { def insert(y: T, ys: List[T]): List[T] = ys match { //...omit some lines case z :: zs => if (lessThan(y, z)) y :: z :: zs else … } xs match { //...omit some lines case y :: ys => insert(y, insertionSort(ys)(lessThan)) } }
這樣可以對任意型別排序,只要你提供排序方法lessThan.例如:
val fruits = List("apple", "pear", "orange", "pineapple") insertionSort(fruits)((x: String, y: String) => x.compareTo(y) < 0)
既然lessThan方法如此重要,我們定義一個trait,注意,trait帶型別引數:
trait Ordering[A]{ def lessThan(x:A,y:A):Int }
然後排序函式接受一個Ordering例項進行排序:
def insertionSort[T](xs: List[T])(ord: Ordering[T]): List[T] = { def insert(y: T, ys: List[T]): List[T] = ys match { case List() => y :: List() case z :: zs => if (ord.lessThan(y, z)) y :: z :: zs else z :: insert(y, zs) } xs match { case List() => List() case y :: ys => insert(y, insertionSort(ys)(ord)) } }
現在這個排序函式比較完美了,如果藉助隱式引數,那麼呼叫可以更簡潔:
def insertionSort[T](xs: List[T])(implicit ord: Ordering[T]): List[T] = { def insert(y: T, ys: List[T]): List[T] = ys match { case List() => y :: List() case z :: zs => if (ord.lessThan(y, z)) y :: z :: zs else z :: insert(y, zs) } xs match { case List() => List() case y :: ys => insert(y, insertionSort(ys)) } } //implicit val for insertionSort function implicit val stringOrdering: Ordering[String] = new Ordering[String] { def lessThan(x: String, y: String): Boolean = x.compareTo(y) < 0 } val fruits = List("apple", "pear", "orange", "pineapple") insertionSort(fruits)//這裡沒有ordering引數
insertionSort(fruits)
的呼叫形式看上去不夠優雅(或者說不夠OO),我們希望呼叫的形式是: fruits.insertionSort
。通過之前文章的隱式轉換很容易想到,我們需要將insertionSort方法作為一個implicit class的方法,這樣即可通過隱式轉換得到 fruits.insertionSort
呼叫形式:
//1. 注意原函式的引數改為class引數傳入 //2. 由於原函式用了遞迴,所以這裡加了一層函式 implicit class FruitOps[T](list: List[T])(implicit ord: Ordering[T]) { def insertionSort(): List[T] = { insertSortInternal(list)(ord) } def insertSortInternal[T](xs: List[T])(implicit ord: Ordering[T]): List[T] = { def insert(y: T, ys: List[T]): List[T] = ys match { case List() => y :: List() case z :: zs => if (ord.lessThan(y, z)) y :: z :: zs else z :: insert(y, zs) } xs match { case List() => List() case y :: ys => insert(y, insertSortInternal(ys)) } } } //use case val fruits = List("apple", "pear", "orange", "pineapple") fruits.insertionSort //List(pear, apple, orange, pineapple)
type class
型別引數化和隱式引數結合即scala的type class,實現在不修改原有程式碼的前提下對type(這裡是List[String])增加行為(insertionSort)和屬性。例如,已有型別Person,想要增加一個print方法實現列印個人簡歷。如果通過繼承,那麼Person程式碼會被改變,且print方法不能隨意替換。如果通過Printer工具類同樣Person只能有一種print實現(或者多個Printer工具類)。通過type class和,可以實現 person.print
的呼叫格式,且print的具體實現是由當前scope的implicit type class instance決定,在不同的scope可以通過提供不同的type class instance達到不同的print效果。
type class由3部分元件:
- type class:a trait with type parameter,即Ordering[T]
- type class instance: 即上面的val stringOrdering: Ordering[String],一般為implicit
- interface for user: 即上面的FruitOps,是一個implicit class.
one more example
class Person(val name: String, val address: String) //type class trait HtmlWriter[A] { def write(data: A): String } // a type class instance implicit object PersonWriter extends HtmlWriter[Person] { override def write(data: Person): String = { s"<span>${data.name} and ${data.address} </span> " } } // type class interface for user implicit class HtmlUtil[A](data:A){ def toHtml(implicit writer: HtmlWriter[A]):String={ writer.write(data) } } //use case,如果需要其他print形式, //那麼只要在當前scope提供一個implicit object PersonWriter即可,其他程式碼無需任何改動 val p = new Person("xiongdahu","beijing") p.toHtml