scala类型系统:26) type classes模式

在进入主题之前,先了解一下通过泛型实现参数化多态的形式:

trait Comparable[T] { 
    def comp(o:T) = this.hashCode - o.hashCode 
}

class A extends Comparable[A] { 
    override def comp(o:A) = super.comp(o) 
}

class B(val i:Int) extends Comparable[B] { 
    override def comp(o:B) = this.i - o.i 
}

上面定义了一个Comparable的泛型特质,对于A,B等实现者可以设定不同的参数类型。

在scala里因为有隐式转换的功能,也可以在定义时不显式的继承Comparable特质,而在伴生对象里定义一个隐式转换,在需要用comp方法的时候,编译器会自动转换为目标类型:

scala> class A; 

scala> object A { 
            implicit def convert(a:A) : Comparable[A] = {
                new a.type with Comparable[A] { 
                    override def comp(o:A) = a.hashCode - o.hashCode 
                } 
            }
        }   

scala> a.comp(new A)
res2: Int = -1633174887

不过scala已经不鼓励使用隐式转换,必要的话,可以使用隐式参数:

// 需要定义一个新的comparable2特质,comp方法接受2个参数
scala> trait Comparable2[T] { def comp(x:A, y:A) = x.hashCode - y.hashCode } 

scala> class A; 

scala> object A { 
            implicit val compA = 
                new Comparable2[A] { override def comp(o:A) = super.comp(o) }
        }

上面定义了一个隐式参数compA它是Comparable[A]类型,不过这样如果要用到A的comp行为,需要在调用的方法中声明隐式参数

// 柯里化,最后一个参数是隐式的,编译器会自动在上下文找

scala> def call[T](x:T, y:T) (implicit c : Comparable2[T]) { 
            c.comp(x,y) 
        }

scala> val x = new A; val y = new A;

scala> call(x,y)

这样做相当于把A的结构与行为(方法)给分离开了(解耦);或者说对某个类型,在不改动其本身的情况下,具备了扩展其行为的能力。

这个模式被称为type classes,源于haskell,我对haskell不熟悉,为什么用这么奇怪的一个名字?从这儿看到:在haskell里没有类似于java/scala里的class的概念,所以class这个术语可以随意使用,它的意思相当于”class of types”,可以理解为对某一系列类型的抽象(即高阶类型)。

scala里的type classes模式主要通过隐式参数来实现,但需要注意的是,并不是所有的隐式参数都可以理解为type classes模式,隐式参数的类型必须是泛型的(高阶),它表示某种抽象行为,这种行为的具体实现要由它的具体类型参数决定。

// 隐式参数c在这里是多态的
scala> def call[T](x:T, y:T) (implicit c : Comparable2[T])

// 针对A实现了一个具体的comp行为,它的参数类型是A 
scala> implicit object CompA extends Comparable2[A] { 
            override def comp(x:A, y:A) = super.comp(x, y) 
        }

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

scala类型系统:26) type classes模式》上有13个想法

  1. Pingback引用通告: shapeless(1): 从方法与函数的多态谈起 | 在路上

  2. Pingback引用通告: spray中的Magnet模式: typeclass的一种特定方式 | 在路上

  3. new a.type with Comparable[A] { 这句有问题,example:

    class Hello

    object Hello {
    implicit def convert(hello: Hello) : Compareable[Hello] = {
    new Hello with Compareable[Hello] {
    override def cmp(o: Hello) = hello.hashCode() – o.hashCode()
    }
    }

    def main(args: Array[String]) {
    val hello: Hello = new Hello
    val result:Int = hello.cmp(new Hello)
    println(result)
    }
    }

    • 谢谢指出,我在scala2.10.4下验证,那个隐式转换的地方:new a.type with Comparable[A]
      应该写为 new A with Comparable[A] 或者 new Comparable 才能编译通过。

      我贴出来的代码应该都是当时在repl下验证过的,当时所用的版本我现在不确定了。
      按照我的推测, a.type with Comparable[A] 这种路径依赖类型,在 new 的时候,可能在某个版本的编译器里做了修改。

      scala> import scala.reflect.runtime.universe._
      scala> trait C[T]
      scala> class A
      scala> val a = new A

      scala> typeOf[a.type] <:< typeOf[A] res2: Boolean = true scala> typeOf[a.type with C[A]] <:< typeOf[C[A]] res3: Boolean = true 在当前的2.10.4下,使用 new 的时候,编译器报错: scala> new a.type with C[A] { }
      :14: error: class type required but a.type found
      new a.type with C[A] { }
      ^
      可能我在写这篇blog的时候这里这种写法在当时的编译器下可以编译通过(或者是我当时真的没有验证,一般不太会)

  4. 第一个例子中`comp`方法的实现没有变化,在定义隐式转换器的时候没有必要重载这个方法了吧。

    implicit def convert(a:A) : Comparable[A] = new A with Comparable[A]

    就好了。


  5. 这样做相当于把A的结构与行为(方法)给分离开了(解耦);或者说对某个类型,在不改动其本身的情况下,具备了扩展其行为的能力。

    不太理解这句。例子中A并没有增加新的行为(方法),感觉只是把Comparable2作为一个可重用的逻辑

    • 不使用typeclass的重用/多态必须依赖继承,这里通过typeclass实现的重用/多态没有改变A。

  6. 请问

    trait Comparable2[T] { def comp(x:A, y:A) = x.hashCode – y.hashCode }

    这句是否应该是

    trait Comparable2[T] { def comp(x:T, y:T) = x.hashCode – y.hashCode }

    谢谢!

    • 另外下面的object A定义我觉得可能也有一点笔误,个人猜测这样定义是否会更合适:

      object A {
      implicit val compA = new Comparable2[A] {}
      }

      谢谢!

发表评论

电子邮件地址不会被公开。 必填项已用*标注