为什么 Scala 允许嵌套数据结构,如 List 或 Array
Why does Scala allow nested data structures like List or Array
为什么像 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 允许编译呢?
由于 Scala 允许隐式子类型化,因此它能够为此类具有混合内容的表达式推断 "correct" 类型。 Scala 正确地推断出您的列表是 List[Any]
类型,这意味着它可以发生任何事情。
由于 Ocaml 不支持没有显式向下转换的隐式子类型;它无法自动加宽混合列表的类型。
大多数情况下,如果您最终输入的是 Any
或 AnyRef
,那么您搞砸了一些事情,但在某些情况下它也可能是正确的。是否需要更严格的类型由程序员决定。
tl;博士
长话短说,OCaml 和 Scala 使用两种不同的 类 类型系统:前者具有 structural typing, the latter has 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]
,这通常是你所期望的。如果失败,用户将是 "weird":
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
^
只要编译器使用 -Ywarn-infer-any
标志推断 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))
为什么像 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 允许编译呢?
由于 Scala 允许隐式子类型化,因此它能够为此类具有混合内容的表达式推断 "correct" 类型。 Scala 正确地推断出您的列表是 List[Any]
类型,这意味着它可以发生任何事情。
由于 Ocaml 不支持没有显式向下转换的隐式子类型;它无法自动加宽混合列表的类型。
大多数情况下,如果您最终输入的是 Any
或 AnyRef
,那么您搞砸了一些事情,但在某些情况下它也可能是正确的。是否需要更严格的类型由程序员决定。
tl;博士
长话短说,OCaml 和 Scala 使用两种不同的 类 类型系统:前者具有 structural typing, the latter has 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]
,这通常是你所期望的。如果失败,用户将是 "weird":
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
^
只要编译器使用 -Ywarn-infer-any
标志推断 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))