Почему в полиморфных методах нужно явно указывать типы?
Например, есть такой код:
def div10[T](value: T): T = {
value / 10 // Compile error (value / is not a member of type parameter T)
}
И очевидно, что этот код не cкомпилируется.
Мне непонятно почему ошибка выдается на уровне реализации функции, а не на уровне ее использования при попытке подсунуть тип, у которого нет метода деления?
И главный вопрос - чем оправдываются такие ограничения, из-за которых, на мой взгляд, приходится писать более сложный код?
Ответы (2 шт):
Есть языки со статической типизацией (та же scala), есть языки с динамической типизацией (к примеру javascript).
Тут можно посмотреть про то и другое https://habr.com/ru/post/308484/
Тут за пользу динамической типизации https://ru.stackoverflow.com/a/904210/417043
Тут за вред динамической типизации https://habr.com/ru/post/500926/
В scala, соответственно, у каждой переменной при компилировании можно определить тип, который ограничивает операторы и методы которые можно применять к / вызывать у переменной. В приведенном примере мы не задаем никаких ограничений на тип. Т.е. T эквивалентно Any у которого нет оператора деления.
Дело не в динамической типизации. При достаточно умном (и сложном) компиляторе это реализуется и при статической типизации. Например, есть ML-языки (Meta Language, а не Machine Learning), где компилятор умеет это делать. Но у них подходящая система типов. Scala же приходится жить в Java-среде, где система типов для такой задачи не совсем подходит.
Упрощенно, есть два подхода к проблеме.
"Шаблонизация". Метод считается шаблоном, который превращается в реальный метод только в точке вызова. Вариант простой, но практически неудобный. Либо шаблоны становятся очень сложными и громоздкими, превращаясь с метаязык (привет, C++), либо они никак не проверяются до момента вызова, что неудобно и даже небезопасно в больших проектах или библиотеках - придется покрывать тестами такие шаблоны для их проверки.
"Обратный вывод типа". Компилятор сам угадывает, какие требования к
Tдолжны быть на моменте компиляции. Это уже более безопасно, но не всегда возможно. Даже в этом примере под деление попадают как минимум целочисленные типы и типы с плавающей запятой. А ведь может быть где-то и оператор деления перегружен. Аналогично, если есть вызов какого-то метода, то подходят все типы, для которых есть определение метода с подходящими именем и сигнатурой. А это означает, что компилятор должен знать о всех типах программы - это сложно, а то и невозможно в случае использования class loader в динамике.
Резюме: идея здравая даже в статической типизации, в Scala могла быть реализована, но не в Java-окружении. И компилятор стал бы сложнее (и медленнее, конечно).