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 派生。

固然,现实应用过程当中,咱们常常须要属性名和递归以及嵌套定义状况,本文中的实现不支持这些场景,后续文章中,我会介绍这些状况处理。

相关文章
相关标签/搜索