Perché la funzione "non fare nulla" di Haskell, id, consuma tonnellate di memoria?


112

Haskell ha una funzione di identità che restituisce l'input invariato. La definizione è semplice:

id :: a -> a
id x = x

Quindi, per divertimento, questo dovrebbe produrre 8:

f = id id id id id id id id id id id id id id id id id id id id id id id id id id id
main = print $ f 8

Dopo pochi secondi (e circa 2 GB di memoria secondo Task Manager), la compilazione non riesce con ghc: out of memory. Allo stesso modo, dice l'interprete ghci: out of memory.

Poiché idè una funzione piuttosto semplice, non mi aspetto che sia un carico di memoria in fase di esecuzione o in fase di compilazione. A cosa serve tutta la memoria?


11
Vuoi comporre quelle ids. In VIM, con il cursore sulla definizione di f, fare questo: :s/id id/id . id ./g.
Tobias Brandt

Risposte:


135

Conosciamo il tipo di id,

id :: a -> a

E quando siamo specializzati in questo id id, la copia a sinistra di idha il tipo:

id :: (a -> a) -> (a -> a)

E poi quando ti specializzi di nuovo per il più ida sinistra in id id id, ottieni:

id :: ((a -> a) -> (a -> a)) -> ((a -> a) -> (a -> a))

Quindi vedi ogni idcosa che aggiungi, la firma del tipo dell'estrema sinistra idè due volte più grande.

Nota che i tipi vengono eliminati durante la compilazione, quindi questo occuperà solo memoria in GHC. Non occuperà memoria nel tuo programma.


Ricordo che Okasaki ebbe problemi simili quando scrisse un calcolatore RPN incorporato in Haskell.
dfeuer

3
La domanda, forse, è se GHC debba trovare un modo per gestire questo genere di cose con più grazia. In particolare, il tipo è molto grande se scritto per intero, ma c'è un'enorme quantità di duplicazioni: la condivisione potrebbe essere usata per comprimere cose del genere? Esiste un modo efficiente per elaborarli?
dfeuer

5
@dfeuer Prova a chiedere il tipo in ghci. Vedrai dalla velocità della risposta che deve essere eseguita la condivisione appropriata. Sospetto che questa condivisione vada persa - per ovvie ragioni - una volta tradotta in qualche altra rappresentazione intermedia (ad esempio core).
Daniel Wagner

4
Ciò significa che se idviene ripetuto più nvolte, lo spazio del suo tipo è proporzionale a 2^n. Il tipo dedotto nel codice di Ryan avrebbe bisogno di 2^27riferimenti alla sua variabile di tipo oltre all'altra struttura necessaria per rappresentare il tipo, che è probabilmente molto più grande di quanto ci si aspetterebbe dalla maggior parte dei tipi.
David

58
Fare l'inferenza del tipo in modo ingenuo è doppio esponenziale, utilizzando abilmente la condivisione nelle espressioni di tipo è possibile ridurlo a esponenziale. Ma non importa quello che fai, ci saranno alcune espressioni piuttosto semplici che faranno esplodere il controllo del tipo. Fortunatamente, questi non si verificano nella programmazione pratica.
agosto
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.