本文由 Jilen 發表在 ScalaCool 團隊博客。html
shapeless 是一個類型相關的庫,提供了不少有趣的功能。
本文介紹其中一個重要功能:自動派生 typeclass 實例。git
Shapeless 實現了 HList
,不一樣於 Scala 標準庫的 Tuple 的扁平結構,HList 是遞歸定義的,和標準庫 List 相似。
HList 能夠簡單理解成每一個元素類型能夠不一樣 Listgithub
簡化後的 HListbash
sealed trait HList
case object HNil extends HNil
case class ::[+H, +T <: HList](head : H, tail : T) extends HList複製代碼
很容易看出 HList 能夠對應到任意 case class,例如less
case class Foo(a: Int, b: String, c: Boolean)
Int :: String :: Boolean :: HNil複製代碼
而 shapeless 也提供 Generic 對象實現任意 case class 實例和對應的 HList 之間的轉換。post
trait Generic[T] extends Serializable {
def to(t : T) : Repr
def from(r : Repr) : T
}
object Generic {
type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }
...
}複製代碼
Miles 設計這個對象不侷限於 case class
,只是很鬆散的定義 T
和 Repr
之間互相轉換方法。
不少人可能疑惑這個方法爲何不設計成兩個類型參數 Generic[A, B]
,這其實是爲了使用 Generic.Aux
繞過編譯器限制。
具體能夠查看此處ui
因爲 HList 和 case class 能夠一一對應,因此咱們很容易想到spa
Generic.Aux[Foo, Int :: String :: Boolean :: HNil]複製代碼
這樣的 Generic
對象就能夠實現 Foo
和 Int :: String :: Boolean :: HNil
之間的相互轉換。
並且 shapeless 會自動使用 macro 生成這樣的 Generic
對象scala
scala> case class Foo(a: Int, b: String, c: Boolean)
defined class Foo
scala> Generic[Foo]
res0: shapeless.Generic[Foo]{type Repr = shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.HNil]]]} = anon$macro$4$1@42db6e8e複製代碼
如今假設咱們要設計一個 typeclass設計
trait Show[A] {
def show(a: A): String
}複製代碼
其功能是能夠將任意 case class 實例顯示成字符串。爲了簡化問題,咱們定義如下顯示規則。
Int
類型直接顯示爲數值Boolean
類型直接顯示爲 true
或 false
String
類型用引號包圍,例如 "str"
case class
顯示爲 []
包圍的屬性列表,屬性之間逗號隔開 [field1, field2, field3...]
咱們很容易實現基本類型的 Show
實例
基本類型 Show 實例
implicit val intShow: Show[Int] = new Show[Int] {
def show(a: Int) = a.toString
}
implicit val stringShow: Show[String] = new Show[String] {
def show(a: String) = "\"" + a + "\""
}
implicit val booleanShow: Show[Boolean] = new Show[Boolean] {
def show(a: Boolean) = if(a) "true" else "false"
}複製代碼
如今來看看如何派生任意 case class 的 Show 實例。固然咱們能夠經過反射或者 macro 實現,這裏咱們展現 shapeless 如何利用 scala 編譯器自動推導出須要實例
任意
case class
的Show
實例
implicit val hnilShow: Show[HNil] = new Show[HNil] {
def show(a: HNil) = ""
}
implicit def hlistShow[H, T <: HList](
implicit hs: Show[H],
ts: Show[T]
): Show[H :: T] = new Show[H :: T]{
def show(a: H :: T) = hs.show(a.head) + "," + ts.show(a.tail)
}
implicit def caseClassShow[A, R <: HList](
implicit val gen: Generic.Aux[A, R],
hlistShow: Show[R]
): Show[A] = {
def show(a: A) = hlistShow(gen.to(a))
}複製代碼
咱們可視化如下編譯器自動推導出 Show[Foo]
的過程
Show
實例。
Generic
對象和對應
HList
的 typeclass 派生。
固然,現實應用過程當中,咱們常常須要屬性名和遞歸以及嵌套定義狀況,本文中的實現不支持這些場景,後續文章中,我會介紹這些狀況處理。