为什么像Scala这样具有非常强大的静态类型系统的语言允许以下结构:
scala> List(1, List(1,2))
res0: List[Any] = List(1, List(1, 2))
如果您将List
替换为Array
,则同样适用。我在OCaml中学习了函数式编程,它会在编译时拒绝相同的代码:
# [1; [1;2]; 3];;
Characters 4-9:
[1; [1;2]; 3];;
^^^^^
Error: This expression has type 'a list
but an expression was expected of type int
那么为什么Scala允许这个编译?
答案 0 :(得分:19)
长话短说,OCaml和Scala使用两种不同类型的系统:前者有structural typing,后者有nominal typing,因此在类型推理算法时它们的行为不同。
如果你在类型系统中允许nominal subtyping,那就是你得到的。
在分析List
时,Scala编译器将类型计算为列表包含的所有类型的LUB(最小上限)。在这种情况下,Int
和List
的LUB为Any
。其他案例会有更明智的结果:
@ List(Some(1), None)
res0: List[Option[Int]] = List(Some(1), None)
Some[Int]
和None
的LUB是Option[Int]
,这通常是您所期望的。这将是一个奇怪的"如果失败的用户:
expected List[Some[Int]] but got List[Option[Int]]
OCaml使用structural subtyping,因此其类型系统在类型推断方面的工作方式不同。正如@gsg在评论中指出的那样,OCaml并没有统一像Scala这样的类型,但需要明确的向上推广。
在Scala中,编译器在执行类型推断时统一类型(由于名义上的子类型)。
当然,使用显式类型注释可以获得更好的错误:
@ val x: List[Int] = List(1, List(1, 2))
Compilation Failed
Main.scala:53: type mismatch;
found : List[Any]
required: List[Int]
}.apply
^
只要编译器使用Any
标志推断-Ywarn-infer-any
- 这通常是一个坏兆头 - 就会收到警告。这是scala REPL的一个例子:
scala -Ywarn-infer-any
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_51).
Type in expressions to have them evaluated.
Type :help for more information.
scala> List(1, List(1, 2))
<console>:11: warning: a type was inferred to be `Any`; this may indicate a programming error.
List(1, List(1, 2))
^
res0: List[Any] = List(1, List(1, 2))
答案 1 :(得分:8)
由于Scala允许隐式子类型,因此能够推断出具有混合内容的此类表达式的“正确”类型。 Scala正确地推断出您的列表类型为List[Any]
,这意味着其中可能出现任何内容。
由于Ocaml不支持隐式子类型而没有明确的向下转换;它无法自动加宽混合列表的类型。
大多数情况下,如果您最终使用Any
或AnyRef
类型,那么您已经搞砸了某些东西,但在某些情况下它也可能是正确的。由程序员决定是否需要更严格的类型。