[关闭]
@MiloXia 2015-09-15T09:54:53.000000Z 字数 2994 阅读 2108

Type class 模式

函数式编程


概念

来自于haskell,意思相 当于”class of types”,可以理解为对某一系列类型的抽象(即高阶类型)

Scala的实现方式

scala里的type classes模式主要通过隐式参数来实现

但需要注意的是,并不是所有的隐式参数都可以理解为type classes模式,隐式参数的类型必须是泛型的(高阶)

它表示某种抽象行为,这种行为的具体实现 要由它的具体类型参数决定

论文

[ http://ropas.snu.ac.kr/~bruno/papers/TypeClasses.pdf ]

例子

  1. //已有类
  2. class A
  3. class B(val i: Int)
  4. //type class的一般使用范式
  5. def cmp[T: Comparable2](x:T, y:T) = {
  6. implicitly[Comparable2[T]].comp(x,y)
  7. }
  8. //or
  9. //def cmp[T](x: T, y: T) (implicit c : Comparable2[T]) {
  10. // c.comp(x, y)
  11. //}
  12. //type class
  13. trait Comparable2[T] { def comp(x: T, y: T) = x.hashCode - y.hashCode }
  14. //定义扩展实现
  15. object Comparable2 {
  16. implicit object ACmp extends Comparable2[A] {
  17. override def comp(x: A, y: A) = super.comp(x, y)
  18. }
  19. implicit object BCmp extends Comparable2[B] {
  20. override def comp(x:B, y:B) = x.i - y.i
  21. }
  22. }
  23. //or
  24. //object A {
  25. // implicit val cmpA = new Comparable2[A] {
  26. // override def comp(x: A, y: A) = super.comp(x, y)
  27. // }
  28. //}
  29. //object B {
  30. // implicit val cmpB = new Comparable2[B] {
  31. // override def comp(x:B, y:B) = x.i - y.i
  32. // }
  33. //}
  34. //* 推荐上面一种,因为有可能修改不到A和B的伴生对象
  35. //test
  36. val x = new A
  37. val y = new A
  38. println(cmp(x, y) > 0)
  39. val x2 = new B(1)
  40. val y2 = new B(2)
  41. println(cmp(x2, y2) > 0)

意义

把A的结构与行为(方法)给分离开了(解耦)

或者说对某个类型,在不改动其本身的情况下,具备了扩展其行为的能力

Scala in depth 总结的优点

简单总结

type classes模式通过泛型来描述某种通用行为

对每个想要用到这种行为的具体类型,在实现时行为部分并不放在类型自身中,而是通过实现一个type class实例(对泛型具化),最后在调用时(通过隐 式参数)让编译器自动寻找行为的实现

实现世界的例子

依赖类型

依赖类型是用在type class模式中的一种场景,表达的是type class优点第二条:可组合性,也是源自haskell的一种扩展

由shapeless作者提出

例子

假设矩阵类型与矢量类型以及Int类型在做笛卡尔乘积运算时,结果类型取决于这两者的组合:

  1. Matrix * Matrix ->Matrix //矩阵 *矩阵 得到 矩阵
  2. Matrix * Vector ->Vector //矩阵 *矢量 得到 矢量
  3. Matrix * Int ->Matrix //矩阵 *Int得到 矩阵
  4. Int * Matrix ->Matrix //Int*矩阵 得到 矩阵

因为结果类型受入参类型的约束,我们在设计函数时,可以把结果类型约束为满足这种组合的情况,用 scala来实现

  1. trait Matrix
  2. trait Vector
  3. trait MultDep[A, B, C] //定义一个笛卡尔运算时需要依赖类型,用于约束结果类型

我们通过隐式对象提供各种类型的组合时的依赖类型:

  1. implicit object mmm extends MultDep[Matrix, Matrix, Matrix]
  2. implicit object mvv extends MultDep[Matrix, Vector, Vector]
  3. implicit object mim extends MultDep[Matrix, Int, Matrix]
  4. implicit object imm extends MultDep[Int, Matrix, Matrix]

检验一下隐式对象:

  1. scala> implicitly[MultDep[Matrix, Matrix, Matrix]] // OK: Matrix * Matrix -> Ma trix
  2. scala> implicitly[MultDep[Matrix, Vector, Vector]] // OK: Matrix * Vector -> Ve ctor
  3. scala> implicitly[MultDep[Matrix, Vector, Matrix]] // Error 没有定义过这种组合

现在在笛卡尔积的方法上增加这个隐式参数,来限制参数类型和结果类型必须符合我们的组合要求:

  1. def mult[A, B, C](a : A, b : B)(implicit instance : MultDep[A, B, C]) : C = {
  2. null.asInstanceOf[ C ]
  3. }

这样我们在运行mult方法时,当传入的参数类型不符合我们用隐式对象提供的组合时,编译器直接会帮我 们检查出来:

  1. val r1 : Matrix = mult(new Matrix {}, new Matrix{}) // Compiles
  2. val r2 : Vector = mult(new Matrix {}, new Vector{}) // Compiles
  3. val r3 : Matrix = mult(new Matrix {}, 2) // Compiles
  4. val r4 : Matrix = mult(2, new Matrix {}) // Compiles
  5. val r5 : Matrix = mult(new Matrix {}, new Vector{}) // 错误,结果类型应该是Vector

现实世界中的应用

在scala的集合库中,当从一种类型的集合转换到另一种类型的集合时,比如执行map或to方法时会调 用CanBuildFrom这个隐式参数来判断当前类型是否可转换为目标类型:

  1. def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
  2. val b = bf(repr)
  3. b.sizeHint(this)
  4. for (x <- this) b += f(x)
  5. b.result
  6. }

type class 与 clojure 多重方法

type class有点像clojure的基于class分类的多重方法

  1. (defmulti comp class :default :base)
  2. (defmethod comp A [x, y]
  3. (:base x y))
  4. (defmethod comp B [x, y]
  5. (- x.i y.i))
  6. (defmethod :base [x, y]
  7. (- (.hashCode x) (.hashCode y))))
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注