scala类型系统:22) 类型约束与特定方法

对于类型限制 =:=<:<

A =:= B  //表示A类型等同于B类型
A <:< B  //表示A类型是B类型的子类型

这个看上去很像操作符的=:=<:<,实际是一个类,它在Predef里定义:

sealed abstract class =:=[From, To] extends (From => To) with Serializable

sealed abstract class <:<[-From, +To] extends (From => To) with Serializable

它定义了两个类型参数,所以可以使用中缀写法:From <:< To

2.10之前还有一个 <%< 类似于view bound,表示 A可以当作B,即A隐式转换成B也满足。但在2.10里已经废弃这种写法。

类型限制用在特定方法(specialized methods)的场景,所谓特定,是指方法只针对特定的类型参数才可以运行:

scala> def test[T](i:T)(implicit ev: T <:< java.io.Serializable) { print("OK") }
test: [T](i: T)(implicit ev: <:<[T,java.io.Serializable])Unit

scala> test("hi")
OK

scala> test(2)
<console>:9: error: Cannot prove that Int <:< java.io.Serializable.

上面定义的test方法,在方法的第二个参数使用了一个隐式参数ev,它的类型是:T <:< java.io.Serializable,表示只有参数类型Tjava.io.Serializable的子类型,才符合类型要求。

或许你会奇怪上面test方法调用”hi”时,隐式参数ev是从哪儿传入的?当前并没有定义这个隐式参数。这个隐式参数也是由Predef里的隐式方法产生的

private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }

implicit def conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

当调用test("hi"),编译器推断出T是String,在寻找 String <:< java.io.Serializable类型的隐式参数时,上下文中找不到,于是通过conforms隐式方法来产生一个,conforms方法只有一个类型参数,它产生的结果是<:<[String,String]类型的对象,但因为<:<[-From,+To]第一个类型参数是逆变的,第二个类型参数是协变的,所以<:<[String,String]符合<:<[String,java.io.Serializable]的子类,满足要求。

而调用test(2)时,因为隐式方法产生的<:<[Int,Int]不符合<:<[Int,java.io.Serializable]子类型,抛出了异常。可见这块编译器是利用函数类型的多态机制来实现类型检测的。

另外,对于Type类型,在判断之间的关系时也有类似的写法,不过这里是Type类型的方法:

scala> typeOf[List[_]] =:= typeOf[List[AnyRef]]
res4: Boolean = false

scala> typeOf[List[Int]] <:< typeOf[Iterable[Int]]
res1: Boolean = true

上面的是方法调用:typ1.=:=(typ2) ,虽然效果都是证明类型关系,但不要混淆。

//2013.11.8 补充
昨天泽彬在csug的旺旺群(94329267/csugcsug)里问<:<:<的差异:

object A{
    def test[T <: java.io.Serializable](i:T) {}
    test(1) // 编译时报错

    def test2[T](i:T)(implicit ev: T <:< java.io.Serializable)  {}
    test2(1) // 同样编译时报错
}

两者的效果似乎一样,应该怎么选择?他在stackoverflow上也问了一下:http://stackoverflow.com/questions/19829770/whats-different-between-and-in-scala

有人给出这样的解释:

def foo[A, B <: A](a: A, b: B) = (a,b)

scala> foo(1, List(1,2,3))
res1: (Any, List[Int]) = (1,List(1, 2, 3))

传入第一个参数是Int类型,第二个参数是List[Int],显然这不符合 B <: A 的约束,编译器在做类型推导的时候,为了满足这个约束,会继续向上寻找父类型来匹配是否满足,于是在第一个参数被推导为Any类型的情况下,List[Int] 符合Any的子类型。

def bar[A,B](a: A, b: B)(implicit ev: B <:< A) = (a,b)

scala> bar(1,List(1,2,3))
<console>:9: error: Cannot prove that List[Int] <:< Int.

通过隐式参数ev来证明类型时,类型推断过程不会像上面那样再向上寻找可能满足的情况,而直接报错。

确实,在用 <: 声明类型约束的时候,不如用<:<更严格,除了上面的类型推导,存在隐式转换的情况下:

scala> def foo[B, A<:B] (a:A,b:B) = print("OK")

scala> class A; class B;

scala> implicit def a2b(a:A) = new B

scala> foo(new A, new B)  //存在A到B的隐式转换,也可满足
OK

scala> def bar[A,B](a:A,b:B)(implicit ev: A<:<B) = print("OK")

scala> bar(new A, new B)  //隐式转换并不管用
<console>:17: error: Cannot prove that A <:< B.

scala类型系统:22) 类型约束与特定方法》上有10条评论

  1. Pingback引用通告: scala类型系统:柯里-霍华德同构 | 在路上

  2. hary

    scala> typeOf[List[Int]] <: import scala.reflect.runtime.universe._
    import scala.reflect.runtime.universe._

    scala> typeOf[List[Int]] <: typeOf[List[Int]]
    res24: reflect.runtime.universe.Type = scala.List[Int]

    scala> typeOf[Iterable[Int]]
    res25: reflect.runtime.universe.Type = scala.collection.mutable.Iterable[Int]

    回复
  3. hary

    typeOf[A]是在哪里定义的, 找不到啊。 typeOf的语意没搞明白。

    typeOf[List[_]] =:= typeOf[List[AnyRef]] 这里的意思也没搞明白
    typeOf[A]是返回一个值??? 显然不是。
    因为=:=[X,Y]要求 X, Y必须是类型。
    这么说typeOf[A]是个类型构造器?typeOf[List[_]]返回的是类型。
    可是typeOf[T]的类型构造器, 与List[T]的类型构造有什么区别呢?, 看下面的例子。
    scala> typeOf[Int]
    res34: reflect.runtime.universe.Type = Int

    scala> List[Int]
    :16: error: missing arguments for method apply in object List;
    follow this method with `_’ if you want to treat it as a partially applied function
    List[Int]

    回复
  4. hongjiang 文章作者

    import scala.reflect.runtime.universe.typeOf;

    这里 universe 是 runtime 这个 package object 里的一个值:
    lazy val universe: api.JavaUniverse = new runtime.JavaUniverse

    而 JavaUniverse继承自 Universe,去看Universe类就清楚了,它混入了一堆trait,包含TypeTags,
    typeOf 方法就是 TypeTags 里面定义的:

    /**
    * Shortcut for `implicitly[TypeTag[T]].tpe`
    * @group TypeTags
    */
    def typeOf[T](implicit ttag: TypeTag[T]): Type = ttag.tpe

    回复
    1. hary

      这里还是没理解 typeOf[List[_]] =:= typeOf[List[AnyRef]]

      这里作为中缀类型, 是否等价与 =:=[typeOf[List[_]], typeOf[List[AnyRef]]]
      这么理解肯定是错误对的, 从你提供的情况来看, typeOf是个泛型函数, 返回类型为Type

      scala> val a = typeOf[String];
      a: reflect.runtime.universe.Type = String

      scala> val b = typeOf[String];
      b: reflect.runtime.universe.Type = String

      scala> a =:= b
      res3: Boolean = true

      看来存在一个 =:= 的中缀函数。
      不过我在Predef里没找到=:=这个函数。
      能解释下我这里的 a =:= b的语意么?

      回复
      1. hary

        Predef中=:=相关的内容如下
        @implicitNotFound(msg = “Cannot prove that ${From} =:= ${To}.”)
        sealed abstract class =:=[From, To] extends (From => To) with Serializable
        private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x }
        object =:= {
        implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A]
        }

        我的理解是:
        1. 有个=:=的泛型类
        2. 有个类型为=:=[Any, Any]{ Refinement }的final对象 singleton_=:=
        3. 有个半生类对象=:=, 里面有个隐式函数tpEquals[A], 返回类型为 =:=[A, A]
        实际返回值为: singleton_=:=.asInstanceOf[A =:= A]

        —————————————————————————–
        —————————————————————————–
        a. 由于 =:=是seal的类, 所以不能继承
        b. 由于 =:=是abstract, 所以不能构造=:=实例
        c. =:=只存在唯一的实例singleton_=:=

        试图创建=:=的其他实例的错误如下:
        scala> new =:=[String, String]{ def apply( x: String) : String = x }
        :13: error: illegal inheritance from sealed class =:=
        new =:=[String, String]{ def apply( x: String) : String = x }
        ^

        这里伴生对象=:=的tpEquals干什么用的??? 能分析下么?

        回复
  5. zz

    不明白为什么”This uses the Scala feature that a generic type op[T1, T2] can be written T1 op T2″?
    就是A <:< A . 能不能解释一下?谢谢

    回复
  6. Lifu Huang

    老师您好,想向您请教,在

    sealed abstract class =:=[From, To] extends (From => To) with Serializable
    sealed abstract class <: To) with Serializable

    中,=:=和<:<为什么需要继承Function2和Serializable?按照我的理解,=:=和<:<的实例主要用来作为evidence以达到类型限制的作用,那让其实现函数接口有什么意义呢?

    谢谢老师!

    回复

发表评论

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