方法参数类型忽略Scala不变泛型类型参数,具体取决于参数是文字表达式还是变量

时间:2013-04-08 16:59:12

标签: scala generics casting type-conversion covariance

摘要

如果我将一个文字表达式作为参数传递给函数,那么它不应该与首先计算相同的文字表达式相同,然后将变量绑定到从该评估返回的值,然后将变量名称作为同一个函数的相同参数?如果该文字表达式返回函数参数的错误类型,则跳过将值赋值给中间变量的步骤,该中间变量的类型Scala从表达式的返回值推断,不应该将该不兼容类型传递给之前的函数它引发了类型不匹配错误,应该吗?然而,这不是以下示例所示的内容吗?

实施例

在这里,我尝试获取类型为Array[Super]的函数参数,以接受Array[Sub]的值。在Scala repl中声明以下内容。请注意函数的单个参数的类型:

class Super
class Sub extends Super
def wantsSuperArray(a: Array[Super]) { println(a.size) }

然后构造Sub

的实例
scala> val s = new Sub
s: Sub = Sub@2c9fa2fb

创建Array[Sub]

scala> val subArray = Array(s)
subArray: Array[Sub] = Array(Sub@2c9fa2fb)

以下表示通用Array is invariant in its element typeArray[Sub]不是Array[Super],即使SubSuper

scala> wantsSuperArray(subArray)
<console>:13: error: type mismatch;
 found   : Array[Sub]
 required: Array[Super]
Note: Sub <: Super, but class Array is invariant in type T.
You may wish to investigate a wildcard type such as `_ <: Super`. (SLS 3.2.10)
              wantsSuperArray(subArray)
                              ^

到目前为止没有惊喜。

令人惊讶的观察

我们刚刚看到wantsSuperArray()不会将Array[Sub]作为参数类型。那么为什么以下不会产生与上面相同的类型不匹配错误消息呢?

scala> wantsSuperArray(Array(new Sub))
1

同样,为什么提出这个没有错误?

scala> wantsSuperArray(Array(s))
1

考虑编译器使用repl一致地处理这三个变体,即拒绝编译并为第一个提供相同的类型不匹配错误,并编译第二个和第三个。

附加详细信息

如果我们按如下方式明确参数化Array,则会再次出现错误消息:

scala> wantsSuperArray(Array[Sub](new Sub))
<console>:11: error: type mismatch;
 found   : Array[Sub]
 required: Array[Super]
Note: Sub <: Super, but class Array is invariant in type T.
You may wish to investigate a wildcard type such as `_ <: Super`. (SLS 3.2.10)
              wantsSuperArray(Array[Sub](new Sub))
                                        ^

所以,显然当没有涉及中间变量时,Scala可以看到wantsSuperArray想要什么类型并且正在进行某种转换,可能是从Array[Sub]转换为Array[Super]。不过,这似乎是一个陷阱,因为我仍然认为选择是否使用中间变量不应该导致程序操作中的这种差异,并且这种特殊情况似乎是执行转换而不是引发错误程序员期望基于通用Array类型参数的不变性。

问题

如果我相信在调用上面定义的wantsSuperArray()时,传递一个文字表达式应该与传递一个变量的名称相同,该变量包含评估相同表达式所产生的值,如上所示,那么我误会了吗?

  • 如何才能提高对我观察和抱怨的理解?

  • 在Scala文档中我可以阅读我在这里观察到的现象,以便能够理解它并且再也不会对它感到惊讶吗?

2 个答案:

答案 0 :(得分:7)

这是因为scala根据预期的wantsSuperArray参数类型执行类型推断。 因此,即使单独使用Array(new Sub)将被视为类型Array[Sub]的表达式,编译器也会看到您处于期望类型为Array[Super]的值的上下文中,因此在调用时Array.apply(通用)它尝试使用Super作为类型参数(而不是Sub),这正确地键入(Array.apply采用类型参数的vararg列表T,此处为T = Super,您传递Sub的实例,这是Super的子类型,这是合理的。)


以下是scala规范的相关摘录, 6.1:表达式类型,章节 6.6:函数应用程序章节6.26.4:本地类型推断(强调我的):

  

表达式的输入通常相对于某些预期类型(1)(可能是   未定义)。在写“表达e被期待符合T型”的时候,我们   mean:e的预期类型是T,表达式e的类型必须符合   到T。

     

<强> ...

     

应用程序f(e1,...,em)将函数f应用于参数表达式   e1,...,em。如果f有一个方法类型(p1:T1,...,pn:Tn)U,每个参数的类型   表达式ei使用相应的参数类型Ti作为预期类型(2)键入。   设Si是参数ei的类型类型(i in 1,...,m)。 如果f是多态方法,则为local   类型推断用于确定f(3)的类型参数。

     

<强> ...

     

如果f是多态方法,则可以使用本地类型推断   确定类型参数,以便实例化方法适用(4)*

     

<强> ...

     

本地类型推断推断要传递给多态类型表达式的类型参数。假设e的类型为[a1&gt;:L1&lt;:U1,...,&gt;:Ln&lt ;:Un] T且没有显式类型   给出参数...如果表达式e显示为未应用的值   值参数,通过求解约束系统推断出类型参数   它将表达式的类型T与期望的类型pt(5)

联系起来

点(3)和(4)解释了如何在表达式Array(new Sub)中,类型推断从new Sub流向Array.apply以产生类型Array[Sub]。这是一个“简单”的案例,你显然没有问题。如果您只是采用此规则,Array(new Sub)应输入为Array[Sub]。事实上,当它被孤立地输入时会发生这种情况(例如val subArray = Array(new Sub)subArray确实有类型Array[Sub])。

但是第(1),(2)和(5)点同时也表示在wantsSuperArray(Array(new Sub))中,wantsSuperArrayArray[Super]的参数的预期类型被传递表达式Array(new Sub)(因为它是多态类型的表达式,其中未明确给出类型参数)。因此,表达式Array(new Sub)被评估为表达式Array[Super](new Sub)。换句话说,它被输入为Array[Super]

答案 1 :(得分:0)

由于你不能真正做Array(s),因为类型信息是必需的,即使它是Any,编译器也会运行它的类型推理代码并检测它可以创建Array[Super]。 1}}它将适合方法调用。

当你明确地说它是Array[Sub]时,编译器不会运行类型推断,因为你已经自己提供了类型,并且因为类型与方法调用不匹配,所以它是编译错误。