Почему Scala разрешает вложенные структуры данных, такие как List или 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 позволяет компилировать?

Ответ 1

TL;DR

Короче говоря, OCaml и Scala используют два разных класса систем типов: первый структурный ввод, последний имеет номинальная типизация, поэтому они ведут себя по-разному, когда речь идет о алгоритмах ввода типа.


Полная дискуссия

Если вы разрешаете номинальный подтипирование в вашей системе типов, это в значительной степени то, что вы получаете.

При анализе List компилятор Scala вычисляет этот тип как LUB (наименьшая верхняя граница) всех типов, содержащихся в списке. В этом случае LUB Int и List равен Any. Другие случаи имели бы более разумный результат:

@ List(Some(1), None)
res0: List[Option[Int]] = List(Some(1), None)

LUB Some[Int] и None составляет Option[Int], что обычно является ожидаемым. Было бы "странно" для пользователя, если это не удалось:

expected List[Some[Int]] but got List[Option[Int]]

OCaml использует структурный подтипирование, поэтому его система типов работает по-разному, когда дело доходит до вывода типа. Как отметил @gsg в комментариях, OCaml специально не объединяет типы типа Scala, но требует явного upcast.

В 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))

Ответ 2

Так как Scala допускает неявный подтипирование, он может вывести "правильный" тип для таких выражений со смешанным содержимым. Scala правильно указывает, что ваш список имеет тип List[Any], что означает, что внутри него может произойти что-либо.

Так как Ocaml не поддерживает неявный подтипирование без явного downcast; он не может автоматически расширять тип для смешанных списков.

Чаще всего, если вы закончите с типом Any или AnyRef, вы что-то испортили, но в некоторых ситуациях это тоже может быть правильным. Программист должен решить, нужен ли более строгий тип.