类型级编程简介

如果我们考虑异类列表,其中列表的元素具有变化但已知类型,则可能希望能够共同地对列表的元素执行操作而不丢弃元素的类型信息。以下示例在简单的异构列表上实现映射操作。

因为元素类型不同,我们可以执行的操作类别限于某种形式的类型投影,所以我们定义了一个特征 Projection,其中抽象 type Apply[A] 计算投影的结果类型def apply[A](a: A): Apply[A] 计算投影的结果

trait Projection {
  type Apply[A] // <: Any
  def apply[A](a: A): Apply[A]
}

在实现 type Apply[A] 时,我们在类型级别编程(而不是值级别)。

我们的异构列表类型定义了由所需投影和投影类型参数化的 map 操作。地图操作的结果是抽象的,将通过实现类和投影而变化,并且自然必须仍然是一个 HList

sealed trait HList {
  type Map[P <: Projection] <: HList
  def map[P <: Projection](p: P): Map[P]
}

HNil 的情况下,空的异类列表,任何投影的结果将始终是它自己。这里我们声明 trait HNil 为方便,以便我们可以将 HNil 作为一种类型来代替 HNil.type

sealed trait HNil extends HList
case object HNil extends HNil {
  type Map[P <: Projection] = HNil
  def map[P <: Projection](p: P): Map[P] = HNil
}

HCons 是非空的异类列表。在这里我们断言,当应用地图操作时,产生的头部类型是由投影到头部值(P#Apply[H])的应用产生的,并且得到的尾部类型是通过将投影映射到尾部而产生的类型( T#Map[P]),这被称为 HList

case class HCons[H, T <: HList](head: H, tail: T) extends HList {
  type Map[P <: Projection] = HCons[P#Apply[H], T#Map[P]]
  def map[P <: Projection](p: P): Map[P] = HCons(p.apply(head), tail.map(p))
}

最明显的此类投影是执行某种形式的包装操作 - 以下示例生成 HCons[Option[String], HCons[Option[Int], HNil]] 的实例:

HCons("1", HCons(2, HNil)).map(new Projection {
  type Apply[A] = Option[A]
  def apply[A](a: A): Apply[A] = Some(a)
})