Una razón crucial para el uso explícito de rec
tiene que ver con la inferencia de tipo Hindley-Milner, que subyace a todos los lenguajes de programación funcional de tipado estático (aunque cambiado y extendido de varias formas).
Si tiene una definición let f x = x
, esperaría que tenga un tipo 'a -> 'a
y sea aplicable en diferentes 'a
tipos en diferentes puntos. Pero igualmente, si escribe let g x = (x + 1) + ...
, esperaría x
ser tratado como int
en el resto del cuerpo de g
.
La forma en que la inferencia de Hindley-Milner trata esta distinción es mediante un paso de generalización explícito . En ciertos puntos al procesar su programa, el sistema de tipos se detiene y dice "ok, los tipos de estas definiciones se generalizarán en este punto, de modo que cuando alguien las use, cualquier variable de tipo libre en su tipo será recién instanciada y, por lo tanto, no interferirá con ningún otro uso de esta definición ".
Resulta que el lugar sensato para hacer esta generalización es después de verificar un conjunto de funciones recursivas entre sí. Si lo hace antes, generalizará demasiado, lo que conducirá a situaciones en las que los tipos podrían colisionar. Más adelante, generalizará muy poco, haciendo definiciones que no se pueden usar con instancias de múltiples tipos.
Entonces, dado que el verificador de tipos necesita saber qué conjuntos de definiciones son recursivas entre sí, ¿qué puede hacer? Una posibilidad es simplemente hacer un análisis de dependencia de todas las definiciones en un alcance y reordenarlas en los grupos más pequeños posibles. Haskell en realidad hace esto, pero en lenguajes como F # (y OCaml y SML) que tienen efectos secundarios sin restricciones, esta es una mala idea porque también podría reordenar los efectos secundarios. Entonces, en cambio, le pide al usuario que marque explícitamente qué definiciones son recursivas entre sí y, por lo tanto, por extensión, dónde debería ocurrir la generalización.