scala雾中风景(15): class A { type T }与class A[T] {}

回答一个新的问题:为什么class A { type T } 里面的T未定义,表示某种抽象类型,而A却不需要声明为abstract,这是为何?

scala> class A { type T }
defined class A

scala> val a =new A
a: A = A@69284047

确实,尽管T类型未声明,却并不影响A的实例化。其实,这种写法,跟 class A[T] {} 效果上是相似的,都是定义了一个泛型参数。不过我们看看它们在类型推断上的差异:

scala>  class A[T] { def foo(p:T) {println(p)} }
defined class A

scala> new A
res3: A[Nothing] = A@49bb2f1c

scala> (new A).foo("hi")
hi

上面把T声明为泛型参数(type parameter)的话,构造的时候不给出具体参数,编译器会推导为A[Nothing]。

scala> class B { type T; def foo(p:T) {println(p)} }
defined class B

scala> new B
res4: B = B@72f1b266

scala> b.foo("hi")
<console>:9: error: type mismatch;
 found   : String("hi")
 required: b.T 
          (new B).foo("hi")
                      ^

而在class内部定义一个抽象类型T,创建实例时不指定具体类型时,这个类型是一个抽象的路径依赖类型”b.T”,要想满足的话,需要显式的声明一个这样的参数传入:

scala> val p: b.T = "hi".asInstanceOf[b.T]
p: b.T = hi

scala> b.foo(p)
hi

scala雾中风景(15): class A { type T }与class A[T] {}》上有15条评论

  1. hary

    val p: b.T = “hi”.asInstanceOf[b.T]
    就这句来说, scala真是有点神奇, b.T还是个未”实现“类型(抽象类型), 居然可以:
    1、赋值
    2、”hi”可以被cast为b.T这一个抽象类型。

    哪里可以看到asInstanceOf的源代码? 应该是在Any类实现的把, scala源码丽没找到Any类的源码。
    asIsntanceOf我想主要用法是:
    scala> class A; class B extends A
    defined class A
    defined class B

    scala> val a: A = new B
    a: A = B@58ab0523

    scala>

    scala> val b = a.asInstanceOf[B]
    b: B = B@58ab0523

    但是下面又是可以的。
    scala> val t = 2.0d.asInstanceOf[Int]
    t: Int = 2

    看来asInstanceOf与java的下列用法与语意还是不同的。
    public class T {
    public static void main(String[] args) {
    Object o = new String(“hello world”);
    String s = (String)o;
    System.out.println(s);
    }
    }

    回复
    1. lcn

      2.0d.asInstanceOf[Int]实际是使用了implicit的,参看这个:http://stackoverflow.com/a/12359801/2073130

      回复
      1. hary

        你的结论是?
        你转的stack我看了, 这里面并没有解答这个问题吧。

        第一位说: 对于Numerical类型, x.asInstanceOf[T] 都作为conversion来处理
        第二位不认同这个观点: 他认为conversion casting是两个概念。 asInstanceOf是casting. 他认为casting只能在一个类型树下进行, 如果不能casting就应该抛出exception.

        所以你的观点与结论是?

        回复
        1. lcn

          前面我说的武断了,这里的conversion不是通过implicit来做的,而是在运行时调用toT方法。asInstanceOf的意义在于支会编译器,告诉它一条额外的类型信息。至于具体是属于casting还是conversion都无所谓,真正要求的是参与的两个类型之间有明确的变换路径。有人认为asInstanceOf应该只服务于casting,但这样一来数值类型的转换又会需要一个额外的util来处理,相比共用asInstanceOf这么做明显多余。

          回复
  2. hongjiang 文章作者

    asInstanceOf 方法是 Any.scala里定义的,它是运行时才能确定的。

    /** Cast the receiver object to be of type `T0`.
    *
    * Note that the success of a cast at runtime is modulo Scala’s erasure semantics.
    * Therefore the expression `1.asInstanceOf[String]` will throw a `ClassCastException` at
    * runtime, while the expression `List(1).asInstanceOf[List[String]]` will not.
    * In the latter example, because the type argument is erased as part of compilation it is
    * not possible to check whether the contents of the list are of the requested type.
    *
    * @throws ClassCastException if the receiver object is not an instance of the erasure of type `T0`.
    * @return the receiver object.
    */
    def asInstanceOf[T0]: T0 = sys.error(“asInstanceOf”)

    回复
  3. hary

    val p: b.T = “hi”.asInstanceOf[b.T]
    这句能够成功的依据是什么。 总觉得怪怪得,b.T还是个未实现的类型啊?

    对于变量v, 和类型T.
    v.asInstanceOf[T] 能够否成功的判断依据是么?

    回复
    1. hongjiang 文章作者

      你想多了。
      相对于具体的对象实例来说,类型本来就是抽象的,路径依赖类型只是做了一下限定,跟java里的内部类(非静态)的限定类似,只是可以更细粒度,比如限制这个类型必须依赖某一个特定对象实例(参考路径依赖类型)。

      v.asInstanceOf[T] 就是判断v实例在构造时的类型X,是否是 T 的子类型(或T本身)而已

      回复
      1. hary

        如果:
        v.asInstanceOf[T] 就是判断v实例在构造时的类型X,是否是 T 的子类型(或T本身)而已

        那么, 就这个例子而言:
        val p: b.T = “hi”.asInstanceOf[b.T]
        v = “hi”
        T = b.T
        X = String
        也就是说 String是b.T的子类型 或String类型就是b.T类型
        但是b.T类型并没有定义啊?

        再者:
        scala> 1.0.asInstanceOf[Int]
        res195: Int = 1

        Float也并非Int的子类型。

        回复
        1. hongjiang 文章作者

          对于某些基础类型,是可以相互造型的,Int, Long, Double, Float 之间可以相互造型,只是可能存在精度丢失。
          对于引用类型,判断的依据仍是我说的那样。就这个问题而言,这里 b.T 是一个抽象类型,在类型推导的时候,被推导成了一个非常泛的类型,最终在字节码层面是一个Object类型,所以把String造型过去也没有问题。

          % cat A.scala
          class B { type T; def foo(p:T) {println(p)} }

          object Main{
          val b = new B
          val p: b.T = “hi”.asInstanceOf[b.T]
          }

          % scalac -Xprint:jvm A.scala

          object Main extends Object {
          private[this] val b: B = _;
          def b(): B = Main.this.b;
          private[this] val p: Object = _;
          def p(): Object = Main.this.p;
          def (): Main.type = {
          Main.super.
          ();
          Main.this.b = new B();
          Main.this.p = “hi”;
          ()
          }
          }

          回复
          1. hongjiang 文章作者

            你可能弄混了“运行时”,与“编译时”,b.T 在编译时,被编译器推导它最终的类型,之后在运行时是明确的。至于这个类型在编译阶段的推导过程,可以通过
            scalac -Y:debug-typer A.scala 来看一下,这个路径依赖类型的推导过程非常复杂(里面还有adapte的尝试),最终被适配为了Any,这涉及到scala背后类型推导的实现机制,就不去细究了。

  4. hongjiang 文章作者

    补充一篇好文,martin对泛型与抽象类型的对比和解释:
    https://github.com/wecite/papers/blob/master/An-Overview-of-the-Scala-Programming-Language/5.Abstraction.md#53–%E7%94%A8%E6%8A%BD%E8%B1%A1%E7%B1%BB%E5%9E%8B%E5%BB%BA%E7%AB%8B%E6%B3%9B%E5%9E%8B%E6%A8%A1%E5%9E%8Bmodeling-generics-with-abstract-types

    摘自里面的内容:

    两种抽象模式之间可以转换,对于一种语言还是有价值的,因为可以降低其内在的概念复杂性。例如,Scala 的泛型,实际上就是一种语法糖,完全可以被抽象类型替代掉。既然如此,也许会有人问,这种语法糖有没有必要性?或者说为什么不只用抽象类型呢,这样可以使语法本身简化很多。实际上,Scala 中引入泛型有两重意义:首先,手工把泛型转化为成为抽象类型表达形式并不那么简单,不仅会丧失语法的简洁性,而且还可能带来前述的命名冲突等问题。其次,泛型和抽象类型在 Scala 中一般扮演不同的角色,泛型一般用于类型的实例化,而抽象类型主要用于在调用者代码中对相应的抽象类型进行引用。后者主要来自于两个场合:一个是有人需要在客户代码中隐藏相关类型信息,用于构造类似于SML模式的模块系统。另一个是在子类中协变地继承父类的类型,从而获得族多态。

    可能有人会问,那么是否可以反过来用泛型来替代抽象类型呢?一些对于两种抽象方式都支持的系统进行的研究 [27] 证实,这样做要困难得多,至少整个程序都需要重写。不仅如此,如果系统要实现受限多态的话,重写类型上/下界的部分会呈平方级增长 [8]。实际上这一点也不奇怪,因为这两种类型体系的理论基础就不同,泛型(不带 F-界的)可以用 F<: 系统来表达 [11],而抽象类型则建立在类型依赖的基础之上。后者比前者的表现力更强,例如,带路径依赖类型的 νObj 演算是可以涵盖 F<: 的。

    回复

发表评论

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