Una ragione cruciale per l'uso esplicito di ha rec
a che fare con l'inferenza di tipo Hindley-Milner, che è alla base di tutti i linguaggi di programmazione funzionale di tipo statico (sebbene modificati ed estesi in vari modi).
Se hai una definizione let f x = x
, ti aspetteresti che abbia un tipo 'a -> 'a
e che sia applicabile a 'a
tipi diversi in punti diversi. Ma allo stesso modo, se scrivi let g x = (x + 1) + ...
, ti aspetteresti x
di essere trattato come un int
nel resto del corpo di g
.
Il modo in cui l'inferenza Hindley-Milner affronta questa distinzione è attraverso un passaggio di generalizzazione esplicito . In alcuni punti durante l'elaborazione del programma, il sistema dei tipi si interrompe e dice "ok, i tipi di queste definizioni saranno generalizzati a questo punto, in modo che quando qualcuno li usa, tutte le variabili di tipo libere nel loro tipo saranno appena istanziate, e quindi non interferirà con altri usi di questa definizione. "
Risulta che il posto sensato per fare questa generalizzazione è dopo aver verificato un insieme di funzioni reciprocamente ricorsive. Prima, e generalizzerai troppo, portando a situazioni in cui i tipi potrebbero effettivamente scontrarsi. In seguito, e generalizzerai troppo poco, creando definizioni che non possono essere utilizzate con istanze di più tipi.
Quindi, dato che il controllo di tipo ha bisogno di sapere quali insiemi di definizioni sono reciprocamente ricorsivi, cosa può fare? Una possibilità è semplicemente fare un'analisi delle dipendenze su tutte le definizioni in un ambito e riordinarle nei gruppi più piccoli possibili. Haskell lo fa in realtà, ma in linguaggi come F # (e OCaml e SML) che hanno effetti collaterali illimitati, questa è una cattiva idea perché potrebbe riordinare anche gli effetti collaterali. Quindi, invece, chiede all'utente di contrassegnare esplicitamente quali definizioni sono reciprocamente ricorsive e quindi, per estensione, dove dovrebbe verificarsi la generalizzazione.