scala片段4:使用型別類實現Pimp my library模式

fairjm發表於2015-04-03

原文來自:
Scala snippets 4: Pimp my library pattern with type classes.
Posted by: Jos Dirksen
January 16th, 2015

轉截請註明來自圖靈社群
fairjm


我想寫一篇關於scalaz有趣的部分的文章,但最好還是先看一下scala提供的型別系統。所以在這個片段中,我們將探索一個很小的部分,關於型別類是如何工作並且幫助你寫出更通用的程式碼。

你能找到更多的片段:
Scala snippets 1: Folding
Scala snippets 2: List symbol magic
Scala snippets 3: Lists together with Map, flatmap, zip and reduce
Scala snippets 4: Pimp my library pattern with type classes
注:前三個片段圖靈社群也有相關的翻譯

型別類

看到關於型別類在維基百科的定義可能會馬上嚇跑你:

“In computer science, a type class is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types. Such a constraint typically involves a type class ‘T’ and a type variable ‘a’, and means that ‘a’ can only be instantiated to a type whose members support the overloaded operations associated with ‘T’.”

總的來說型別類允許向現有的類新增新功能但不需要修改現有的類。比如說向String新增標準的"可比較(comparable)"功能但不需要改動現有的類。注意你也可以只用隱式函式來增加自定義的行為(比如"Pimp my library pattern",但是使用型別類更加地安全和靈活。關於這個的一個很好的討論在這裡

介紹以及足夠了,讓我們來看一個非常簡單的型別類的例子。在scala中建立一個型別類需要幾步。

第一步是建立一個特質。這個特質是實際的型別類並且定義了我們想要實現的功能。在這篇文章中,我們將要建立一個很做作的例子,定義一個Duplicate特質。這個特質的功能是重複特定的物件,當傳入"hello"時,預期的返回值是"hellohello"。當引數是個整數時,我們返回value * value,當得到一個字元c時,我們返回"cc"。所有的這些都以型別安全的方式實現。

我們的型別類實際上非常簡單:

trait Duplicate[A,B] {
    def duplicate(value: A): B
  }

注意,它看上去很像scala的mix-in特質,但實際上使用方式完全不一樣。一旦我們得到了型別類的定義,接下去的步驟就是建立一些預設的實現。我們將這些寫在特質的伴生物件內:

object Duplicate {

    // implemented as a singleton object
    implicit object DuplicateString extends Duplicate[String,String] {
      def duplicate(value: String) = value.concat(value)
    }

    // or directly, which I like better.
    implicit val duplicateInt = new Duplicate[Int, Int] {
      def duplicate(value: Int) = value * value
    }

    implicit val duplicateChar = new Duplicate[Char, String] {
      def duplicate(value: Char) = value.toString + value.toString
    }
  }
 }  

通過上面的程式碼可以看到我們能用幾種不同的方式來實現。其中最重要的是implicit這個關鍵字。使用這個關鍵字我們可以讓這些成員在特定的情況下被隱含使用。這些實現都很直接,我們只是用已經定義了的特定的型別(StringIntChar)來實現特質。

現在我們開始使用型別類:

object DuplicateWriter {

  // import the conversions for use within this object
  import conversions.Duplicate

  // Generic method that takes a value, and looks for an implicit
  // conversion of type Duplicate. If no implicit Duplicate is available
  // an error will be thrown. Scala will first look in the local
  // scope before looking for implicits in the companion object
  // of the trait class.
  def write[A,B](value: A)(implicit dup: Duplicate[A, B]) : B = {
    dup.duplicate(value)
  }
}


// simple app that runs our conversions
object Example extends App {
  import snippets.conversions.Duplicate

  implicit val anotherDuplicateInt = new Duplicate[Int, Int] {
    def duplicate(value: Int) = value + value
  }

  println(DuplicateWriter.write("Hello"))
  println(DuplicateWriter.write('c'))
  println(DuplicateWriter.write(0))
  println(DuplicateWriter.write(0)(Duplicate.duplicateInt))

}  

在這個例子中,我們建立了一個DuplicateWriter來呼叫與給定的引數型別匹配的duplicate函式。在給的例子中,我們也用了一個Int型別的其他實現來替代預設的實現(最後一行的程式碼)。 輸出的結果:

20
100
HelloHello
cc

注:這裡有些錯誤 首先程式碼裡的0 應該是10,其次程式碼的列印順序不對(或者是輸出順序不對)..

如果我們用一個不支援的型別(比如Double):

println(DuplicateWriter.write(0d))

我們將得到一個編譯期的錯誤:

Error:(56, 32) could not find implicit value for parameter dup: snippets.conversions.Duplicate[Double,B]
  println(DuplicateWriter.write(0d))
                               ^

Error:(56, 32) not enough arguments for method write: (implicit dup: snippets.conversions.Duplicate[Double,B])B.
Unspecified value parameter dup.
  println(DuplicateWriter.write(0d))
                               ^  

我們可以定製化第一個錯誤資訊在我們的特質上:

@implicitNotFound("No member of type class Duplicate in scope for ${T}")
  trait Duplicate[A,B] {
    def duplicate(value: A): B
  }

這是一個很快的關於型別類的介紹。你可以看到,它們提供了很簡單的方法來對已有的類增加自定義的功能,你甚至不需要能觸及這些類。在下一個片段中,我們將探索一些在scalaz庫中的普遍的,非常有用的型別類。


原文來自:
Scala snippets 4: Pimp my library pattern with type classes.
Posted by: Jos Dirksen
January 16th, 2015

轉截請註明來自圖靈社群
fairjm


相關文章