Почему Scala требует, чтобы функции имели явный тип возвращаемого значения?

11

Недавно я начал учиться программировать в Scala, и до сих пор было весело. Мне действительно нравится возможность объявлять функции в другой функции, что кажется интуитивно понятным.

У меня есть одна любимая мозоль о Scala - тот факт, что Scala требует явного возвращаемого типа в своих функциях . И я чувствую, что это мешает выразительности языка. Кроме того, просто сложно программировать с этим требованием. Может быть, это потому, что я из зоны комфорта Javascript и Ruby. Но для такого языка, как Scala, у которого в приложении будет множество связанных функций, я не могу представить, как мне в мозгу надо точно определить, какой тип конкретной функции, которую я пишу, должен возвращаться с рекурсиями после рекурсий.

Это требование явного объявления возвращаемого типа для функций не беспокоит меня для таких языков, как Java и C ++. Рекурсии в Java и C ++, когда они случались, часто имели дело с максимум 2-3 функциями. Никогда не объединяйте несколько функций, таких как Scala.

Поэтому я думаю, мне интересно, есть ли веская причина, по которой в Scala должно быть требование наличия функций, имеющих явный тип возврата?

вывоз мусора
источник
5
Это не так - и я не понимаю, почему это будет проблемой, если это произойдет.
Кит Томпсон
1
Я думал, что это сделал. Есть ли в Scala случаи, когда тип возвращаемого значения для функции на самом деле неоднозначен?
сборка мусора

Ответы:

15

Scala не требует явного возвращаемого типа для всех функций, только для рекурсивных. Причиной этого является то, что алгоритм вывода типов в Scala (что-то близкое к нему) является простым сканированием от начала до конца, которое неспособно к прогнозированию.

Это означает, что такая функция:

def fortuneCookieJoke(message: String) = message + " in bed."

возвращаемый тип не требуется, так как компилятор Scala может четко видеть, не используя логические переменные или просматривая что-либо, кроме параметров метода, что тип возвращаемого значения должен быть String.

С другой стороны, такая функция:

def mapInts(f: (Int) => Int, l: List[Int]) = l match {
  case Nil => Nil
  case x :: xs => f(x) :: mapInts(f, xs)
}

вызовет ошибку во время компиляции, потому что компилятор Scala не может увидеть, без использования заглядывания или логических переменных, именно то, что тип mapInts. Максимум, что можно было бы сказать, если бы он был достаточно умным, это то, что возвращаемый тип - это супертип List[Nothing], поскольку Nilон того же типа. Это не дает достаточно информации, чтобы точно определить тип возвращаемого значения mapInts.

Обратите внимание, что это характерно для Scala, и что существуют другие статически типизированные языки (большая часть семейства Miranda / Haskell / Clean, большая часть семейства ML и несколько других), которые используют гораздо более полные и функциональные алгоритмы вывода типов чем использует Scala. Кроме того, имейте в виду, что это не совсем ошибка Скалы; номинальный подтип и вывод целого модуля принципиально расходятся друг с другом, и разработчики Scala решили отдать предпочтение первому над вторым ради совместимости с Java, в то время как «более чистые» статически типизированные функциональные языки были в основном разработаны с противоположный выбор в виду.

Пламя Птариена
источник
4
На самом деле, проблема не столько в том, чтобы найти более полный алгоритм вывода типа. Он выясняет более полный алгоритм вывода типов, сохраняя при этом высокое качество сообщений об ошибках в текущем компиляторе Scala.
Йорг Миттаг
1
Не будет ли верный возврат на case Nilсамом деле пустым List[Int]()? В этом случае достаточно умный компилятор может понять это. Думаю, все это играет в «Адвоката дьявола».
KChaloux