Одна из важнейших причин явного использования rec
- связана с выводом типа Хиндли-Милнера, который лежит в основе всех языков функционального программирования со статической типизацией (хотя и измененных и расширенных различными способами).
Если у вас есть определение let f x = x
, вы ожидаете, что оно будет иметь тип 'a -> 'a
и применимо к разным 'a
типам в разных точках. Но в равной степени, если вы напишете let g x = (x + 1) + ...
, вы ожидаете, что вас x
будут рассматривать как объект int
в остальной части тела g
.
Вывод Хиндли-Милнера справляется с этим различием посредством явного шага обобщения . В определенные моменты при обработке вашей программы система типов останавливается и говорит: «Хорошо, типы этих определений будут обобщены на этом этапе, так что, когда кто-то их использует, любые переменные свободного типа в их типе будут заново созданы, и, таким образом, не будет препятствовать любому другому использованию этого определения ".
Оказывается, разумным местом для этого обобщения является проверка взаимно рекурсивного набора функций. Что-нибудь раньше, и вы будете слишком много обобщать, что приведет к ситуациям, когда типы действительно могут столкнуться. Немного позже, и вы слишком мало обобщите, сделав определения, которые нельзя использовать с экземплярами нескольких типов.
Итак, учитывая, что средство проверки типов должно знать, какие наборы определений являются взаимно рекурсивными, что он может сделать? Одна из возможностей - просто провести анализ зависимостей для всех определений в области видимости и переупорядочить их по минимально возможным группам. На самом деле Haskell делает это, но для таких языков, как F # (и OCaml и SML), которые имеют неограниченные побочные эффекты, это плохая идея, потому что это может также изменить порядок побочных эффектов. Поэтому вместо этого он просит пользователя явно отметить, какие определения являются взаимно рекурсивными, и, следовательно, по расширению, где должно произойти обобщение.