简体   繁体   English

scala的类型检查器无法识别抽象路径依赖类场景中的类型

[英]scala's type checker doesn't recognize types in abstract path-dependent classes scenario

Let's define a trait with an abstract class 让我们用抽象类定义一个特征

object Outer {

  trait X {
    type T
    val empty: T
  }

Now we can make an instance of it: 现在我们可以创建它的一个实例:

  val x = new X {
    type T = Int
    val empty = 42
  }

Scala now recognizes, that x.empty is an Int : Scala现在认识到, x.empty是一个Int

  def xEmptyIsInt = x.empty: Int

Now, let's define an other class 现在,让我们定义另一个类

  case class Y(x: X) extends X {
    type T = x.T
    val empty = x.empty
  }

and make an instance of it 并制作一个实例

  val y = Y(x)

But now Scala, isn't able to infer that y.empty is of type Int . 但是现在Scala无法推断y.empty属于Int类型。 The following 下列

  def yEmptyIsInt = y.empty: Int

now produces an error message: 现在生成错误消息:

error: type mismatch;
found   : y.x.T
required: Int
       y.empty : Int

Why is this the case? 为什么会这样? Is this a scala bug? 这是一个scala bug吗?

We can mitigate this issue by introducing a parameters to the case class: 我们可以通过向case类引入参数来缓解此问题:

  case class Z[U](x: X { type T = U }) extends X {
    type T = U
    val empty = x.empty
  }

Then it works again 然后再次运作

  val z = Z(x)
  def zEmptyIsInt: Int = z.empty

But we always have to mention all the types inside X at call-site. 但我们总是要在呼叫现场提到X内的所有类型。 This ideally should be an implementation detail which leads to the following approach: 理想情况下,这应该是一个实现细节,导致以下方法:

  case class A[U <: X](z: U) extends X {
    type T = z.T
    val empty = z.empty
  }

This also mitigates the issue 这也减轻了这个问题

  val a = A(x)
  def aEmptyIsInt: Int = a.empty

}

So, to summarize, my questions are the following: Why does the simple case doesn't work? 总而言之,我的问题如下:为什么简单的情况不起作用? Is this a valid workaround? 这是一个有效的解决方法吗? What other problems might come up when we follow one of the two workaround approaches? 当我们遵循两种解决方法之一时,可能会出现哪些其他问题? Is there a better approach? 有更好的方法吗?

You've re-used x for different things, so from here on I'll call the object instantiated by val x "instance x" and the x: X used in class Y "parameter x". 你已经为不同的东西重新使用了x ,所以从这里开始我将调用val x “instance x”实例化的对象和类Y“parameter x”中使用的x: X .

"Instance x" is an anonymous subclass of trait X with concrete members overriding trait X 's abstract members. “实例x”是trait X匿名子类 ,具体成员覆盖trait X的抽象成员。 As a subclass of X you can pass it to the constructor for case class Y and it will be accepted happily, since as a subclass it is an X . 作为X的子类,您可以将它传递给case class Y的构造函数,并且它将被愉快地接受,因为作为子类,它 X

It seems to me you expect that case class Y will then check at runtime to see if the instance of X it is passed has overridden X 's members, and generate an instance of Y whose members have different types depending on what was passed in. 在我看来,你希望case class Y在运行时检查它是否传递的X实例是否已覆盖X的成员,并生成Y的实例,其成员具有不同的类型,具体取决于传入的内容。

This is emphatically not how Scala works, and would pretty much defeat the purpose of its (static) type system. 这显然不是Scala如何工作,并且几乎会破坏其(静态)类型系统的目的。 For example, you wouldn't be able to do anything useful with Y.empty without runtime reflection since it could have any type at all, and at that point you're better off just using a dynamic type system. 例如,如果没有运行时反射,你就无法对Y.empty做任何有用的事情,因为它可以有任何类型,那时你最好只使用动态类型系统。 If you want the benefits of parametricity, free theorems, not needing reflection, etc. then you have to stick to statically determined types (with a small exception for pattern matching). 如果你想要参数,自由定理,不需要反射等的好处,那么你必须坚持静态确定的类型(模式匹配的一个小例外)。 And that's what Scala does here. 这就是Scala在这里所做的。

What actually happens is that you've told Y 's constructor that parameter x is an X , and so the compiler statically determines that x.empty , has the type of X.empty , which is abstract type T . 实际发生的是你告诉Y的构造函数参数xX ,因此编译器静态地确定x.empty的类型为X.empty ,它是抽象类型T Scala's inference isn't failing; 斯卡拉的推断并没有失败; your types are actually mismatched. 你的类型实际上是不匹配的。

Separately, this doesn't really have anything to do with path-dependent types. 另外,这与路径依赖类型没有任何关系。 Here is a good walkthrough , but in short, path-dependent types are bound to their parent's instance, not determined dynamically at runtime. 这是一个很好的演练 ,但简而言之,路径依赖类型绑定到其父实例,而不是在运行时动态确定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM