GHC non memorizza le funzioni.
Tuttavia, calcola ogni espressione data nel codice al massimo una volta per volta in cui viene immessa l'espressione lambda circostante, o al massimo una volta mai se è al livello superiore. Determinare dove le lambda-espressioni sono può essere un po 'difficile quando si utilizzano zucchero sintattico, come nel tuo esempio, quindi cerchiamo di convertire questi per equivalente sintassi Dezuccherato:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Nota: Il rapporto Haskell 98 descrive in realtà un operatore di sinistra sezione come (a %)
equivalenti a \b -> (%) a b
, ma GHC desugars a (%) a
. Queste sono tecnicamente differenti perché possono essere distinti da penso che potrei aver presentato un biglietto GHC Trac su questo seq
..)
Detto questo, è possibile vedere che in m1'
, l'espressione filter odd [1..]
non è contenuta n qualsiasi espressione lambda, quindi verrà calcolata una sola volta per ogni esecuzione del programma, mentre in m2'
, filter odd [1..]
verrà calcolata ogni volta che viene immessa l'espressione lambda, ad esempio su ogni chiamata di m2'
. Questo spiega la differenza di tempo che stai vedendo.
In realtà, alcune versioni di GHC, con determinate opzioni di ottimizzazione, condivideranno più valori di quanto indicato nella descrizione precedente. Questo può essere problematico in alcune situazioni. Ad esempio, si consideri la funzione
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC potrebbe notare che y
non dipende da x
e riscrivere la funzione di
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
In questo caso, la nuova versione è molto meno efficiente perché avrà leggere circa 1 GB dalla memoria in cui è memorizzato , mentre la versione originale dovrebbe essere eseguita nello spazio costante e adattarsi alla cache del processore. In effetti, in GHC 6.12.1, la funzione f
è quasi due volte più veloce quando è compilata senza le ottimizzazioni rispetto a quando è compilata con -O2
.
Il costo per valutare (filtro dispari [1]] è comunque vicino allo zero - è una lista lenta dopo tutto, quindi il costo reale è in (x !! 10000000) l'applicazione quando l'elenco è effettivamente valutata. Inoltre, sia m1 che m2 sembrano essere valutati una sola volta con -O2 e -O1 (sul mio ghc 6.12.3) almeno all'interno del seguente test: (test = m1 10000000 'seq' m1 10000000). C'è una differenza anche se non viene specificato alcun flag di ottimizzazione. Ed entrambe le varianti della tua "f" hanno una residenza massima di 5356 byte indipendentemente dall'ottimizzazione, a proposito (con meno allocazione totale quando viene utilizzato -O2). –
@ Ed'ka: prova questo programma di test, con la precedente definizione di 'f':' main = interact $ unlines. (mostra la mappa per leggere). linee'; compilare con o senza '-O2'; quindi 'echo 1 | ./Main'. Se scrivi un test come 'main = print (f 5)', allora 'y' può essere garbage collection mentre viene usato e non c'è differenza tra i due' f's. –
er, che dovrebbe essere 'map (show. F. Read)', naturalmente. E ora che ho scaricato GHC 6.12.3, vedo gli stessi risultati di GHC 6.12.1. E sì, hai ragione riguardo a 'm1' e' m2': le versioni di GHC che eseguono questo tipo di sollevamento con le ottimizzazioni attivate trasformeranno 'm2' in' m1'. –