Почему в полиморфных методах нужно явно указывать типы?

Например, есть такой код:

def div10[T](value: T): T = {
  value / 10  // Compile error (value / is not a member of type parameter T)
}

И очевидно, что этот код не cкомпилируется.

Мне непонятно почему ошибка выдается на уровне реализации функции, а не на уровне ее использования при попытке подсунуть тип, у которого нет метода деления?

И главный вопрос - чем оправдываются такие ограничения, из-за которых, на мой взгляд, приходится писать более сложный код?


Ответы (2 шт):

Автор решения: Evgeny

Есть языки со статической типизацией (та же scala), есть языки с динамической типизацией (к примеру javascript).

Тут можно посмотреть про то и другое https://habr.com/ru/post/308484/

Тут за пользу динамической типизации https://ru.stackoverflow.com/a/904210/417043

Тут за вред динамической типизации https://habr.com/ru/post/500926/

В scala, соответственно, у каждой переменной при компилировании можно определить тип, который ограничивает операторы и методы которые можно применять к / вызывать у переменной. В приведенном примере мы не задаем никаких ограничений на тип. Т.е. T эквивалентно Any у которого нет оператора деления.

→ Ссылка
Автор решения: BccHnw

Дело не в динамической типизации. При достаточно умном (и сложном) компиляторе это реализуется и при статической типизации. Например, есть ML-языки (Meta Language, а не Machine Learning), где компилятор умеет это делать. Но у них подходящая система типов. Scala же приходится жить в Java-среде, где система типов для такой задачи не совсем подходит.

Упрощенно, есть два подхода к проблеме.

  1. "Шаблонизация". Метод считается шаблоном, который превращается в реальный метод только в точке вызова. Вариант простой, но практически неудобный. Либо шаблоны становятся очень сложными и громоздкими, превращаясь с метаязык (привет, C++), либо они никак не проверяются до момента вызова, что неудобно и даже небезопасно в больших проектах или библиотеках - придется покрывать тестами такие шаблоны для их проверки.

  2. "Обратный вывод типа". Компилятор сам угадывает, какие требования к T должны быть на моменте компиляции. Это уже более безопасно, но не всегда возможно. Даже в этом примере под деление попадают как минимум целочисленные типы и типы с плавающей запятой. А ведь может быть где-то и оператор деления перегружен. Аналогично, если есть вызов какого-то метода, то подходят все типы, для которых есть определение метода с подходящими именем и сигнатурой. А это означает, что компилятор должен знать о всех типах программы - это сложно, а то и невозможно в случае использования class loader в динамике.

Резюме: идея здравая даже в статической типизации, в Scala могла быть реализована, но не в Java-окружении. И компилятор стал бы сложнее (и медленнее, конечно).

→ Ссылка