4

概括

如果我将文字表达式作为参数传递给函数,那不应该与首先评估相同的文字表达式,然后将变量绑定到从该评估返回的值,然后将变量名作为相同的参数传递到相同的功能?如果该文字表达式为函数的参数返回了错误的类型,那么跳过将值分配给一个中间变量的步骤,该变量的类型 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在其元素类型上是不变的,并且即使 a是 a ,anArray[Sub]也不是 a :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]to转换Array[Super]。尽管如此,这似乎是一个陷阱,因为我仍然认为是否使用中间变量的选择不应该在程序的操作中造成这样的差异,而且这种特殊情况似乎是在执行强制转换而不是引发错误程序员会期望基于泛型Array类型参数的不变性。

问题

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

  • 我怎样才能更好地理解我在这里观察和抱怨的内容?

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

4

2 回答 2

7

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


这是 scala 规范的相关摘录,第6.1 章:表达式类型,第6.6 章:函数应用程序第 6.26.4 章:局部类型推断(强调我的):

表达式的类型通常与某些预期类型 (1) 相关(可能未定义)。当我们写“表达式 e 应符合类型 T”时,我们的意思是: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 是多态方法,则使用局部类型推断来确定 f (3) 的类型参数

...

如果 f 是多态方法,则它适用于局部类型推断可以确定类型参数以便实例化方法适用 (4)* 的情况

...

局部类型推断推断要传递给多态类型表达式的类型参数。假设 e 的类型为 [a1 >: L1 <: U1, ..., an >: Ln <: Un]T 并且没有给出明确的类型参数...如果表达式 e 显示为一个值而没有应用于 value参数,类型参数是通过求解将表达式的类型 T 与预期类型 pt 相关联的约束系统来推断的 (5)

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

但是点 (1)、(2) 和 (5) 一起也说在 中wantsSuperArray(Array(new Sub)),参数的预期类型wantsSuperArray(即Array[Super])被传递给表达式Array(new Sub)(因为它是类型参数不是的多态类型的表达式明确给出)。因此,表达式Array(new Sub)被评估为表达式Array[Super](new Sub)。换句话说,它的类型为Array[Super]

于 2013-04-08T17:08:29.773 回答
0

既然你不能真正做到Array(s),因为类型信息是必需的,即使它需要Any,编译器也会运行它的类型推断代码并检测它可以创建一个Array[Super]并且它适合方法调用。

当您明确地说这是一个Array[Sub]编译器不会运行类型推断时,因为您已经自己给出了类型,并且由于类型与方法调用不匹配,所以这是一个编译错误。

于 2013-04-08T17:09:47.180 回答