Uma razão crucial para o uso explícito de rec
é fazer com a inferência de tipo de Hindley-Milner, que fundamenta todas as linguagens de programação funcional de tipo estático (embora alterado e estendido de várias maneiras).
Se você tem uma definição let f x = x
, espera que ela tenha um tipo 'a -> 'a
e seja aplicável a diferentes 'a
tipos em diferentes pontos. Mas, da mesma forma, se você escrever let g x = (x + 1) + ...
, esperaria x
ser tratado como um int
no resto do corpo de g
.
A maneira como a inferência de Hindley-Milner lida com essa distinção é por meio de uma etapa de generalização explícita . Em certos pontos ao processar seu programa, o sistema de tipo para e diz "ok, os tipos dessas definições serão generalizados neste ponto, de modo que quando alguém as usar, quaisquer variáveis de tipo livre em seu tipo serão novamente instanciadas, e assim não interferirá com nenhum outro uso desta definição. "
Acontece que o melhor lugar para fazer essa generalização é verificar um conjunto recursivo de funções mutuamente. Qualquer coisa antes, e você generalizará demais, levando a situações em que os tipos podem realmente colidir. Posteriormente, você generalizará muito pouco, fazendo definições que não podem ser usadas com instanciações de vários tipos.
Portanto, visto que o verificador de tipo precisa saber quais conjuntos de definições são recursivos mutuamente, o que ele pode fazer? Uma possibilidade é simplesmente fazer uma análise de dependência em todas as definições em um escopo e reordená-las nos menores grupos possíveis. Haskell realmente faz isso, mas em linguagens como F # (e OCaml e SML), que têm efeitos colaterais irrestritos, é uma má ideia porque pode reordenar os efeitos colaterais também. Em vez disso, ele pede ao usuário para marcar explicitamente quais definições são recursivas mutuamente e, portanto, por extensão, onde a generalização deve ocorrer.