Shapeless入門指南(一):自動派生 typeclass 實例

本文由 Jilen 發表在 ScalaCool 團隊博客。html

shapeless 是一個類型相關的庫,提供了不少有趣的功能。
本文介紹其中一個重要功能:自動派生 typeclass 實例。git

Hlist

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

Generic 對象

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,只是很鬆散的定義 TRepr 之間互相轉換方法。
不少人可能疑惑這個方法爲何不設計成兩個類型參數 Generic[A, B],這其實是爲了使用 Generic.Aux 繞過編譯器限制。
具體能夠查看此處ui

case class 和 HList 互相轉換

因爲 HList 和 case class 能夠一一對應,因此咱們很容易想到spa

Generic.Aux[Foo, Int :: String :: Boolean :: HNil]複製代碼

這樣的 Generic 對象就能夠實現 FooInt :: 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 實例

如今假設咱們要設計一個 typeclass設計

trait Show[A] {
  def show(a: A): String
}複製代碼

其功能是能夠將任意 case class 實例顯示成字符串。爲了簡化問題,咱們定義如下顯示規則。

  • Int 類型直接顯示爲數值
  • Boolean 類型直接顯示爲 truefalse
  • 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 classShow 實例

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] 的過程


編譯器自動推導過程
編譯器自動推導過程


Shapeless 巧妙的利用編譯器自動推導功能,推導出了任意 case class 對象的 Show 實例。
整個過程雖然理解起來很複雜,但規則卻意外的簡單:編譯器自動推導。
這樣實例派生過程就轉化成了 Generic 對象和對應 HList 的 typeclass 派生。

固然,現實應用過程當中,咱們常常須要屬性名和遞歸以及嵌套定義狀況,本文中的實現不支持這些場景,後續文章中,我會介紹這些狀況處理。

相關文章
相關標籤/搜索