Como outros já observaram, Haskell requer automático , dinâmico gerenciamento memória: o gerenciamento automático de memória é necessário porque o gerenciamento manual de memória não é seguro; o gerenciamento de memória dinâmica é necessário porque, para alguns programas, o tempo de vida de um objeto só pode ser determinado em tempo de execução.
Por exemplo, considere o seguinte programa:
main = loop (Just [1..1000]) where
loop :: Maybe [Int] -> IO ()
loop obj = do
print obj
resp <- getLine
if resp == "clear"
then loop Nothing
else loop obj
Neste programa, a lista [1..1000]
deve ser mantida na memória até que o usuário digite "limpar"; portanto, o tempo de vida disso deve ser determinado dinamicamente e é por isso que o gerenciamento de memória dinâmico é necessário.
Portanto, nesse sentido, a alocação de memória dinâmica automatizada é necessária e, na prática, isso significa: sim , Haskell requer um coletor de lixo, já que a coleta de lixo é o gerenciador automático de memória dinâmica de mais alto desempenho.
Contudo...
Embora um coletor de lixo seja necessário, podemos tentar encontrar alguns casos especiais em que o compilador pode usar um esquema de gerenciamento de memória mais barato do que a coleta de lixo. Por exemplo, dado
f :: Integer -> Integer
f x = let x2 = x*x in x2*x2
podemos esperar que o compilador detecte o que x2
pode ser desalocado com segurança quando f
retornar (em vez de esperar que o coletor de lixo seja desalocado x2
). Essencialmente, estamos pedindo que o compilador execute uma análise de escape para converter as alocações em heap coletado pelo lixo para alocações na pilha sempre que possível.
Não é muito razoável perguntar: o compilador haskell jhc faz isso, embora o GHC não. Simon Marlow diz que o coletor de lixo geracional do GHC torna a análise de fuga quase desnecessária.
A jhc realmente usa uma forma sofisticada de análise de escape conhecida como inferência de região . Considerar
f :: Integer -> (Integer, Integer)
f x = let x2 = x * x in (x2, x2+1)
g :: Integer -> Integer
g x = case f x of (y, z) -> y + z
Nesse caso, uma análise de escape simplista concluiria que x2
escapa de f
(porque é retornado na tupla) e, portanto, x2
deve ser alocado no heap coletado pelo lixo. A inferência de região, por outro lado, é capaz de detectar que x2
pode ser desalocada quando g
retorna; a ideia aqui é que x2
deva ser alocado na g
região de e não f
na região de.
Além de Haskell
Embora a inferência de região seja útil em certos casos, conforme discutido acima, parece ser difícil conciliar efetivamente com a avaliação preguiçosa (veja os comentários de Edward Kmett e Simon Peyton Jones ). Por exemplo, considere
f :: Integer -> Integer
f n = product [1..n]
Alguém pode ficar tentado a alocar a lista [1..n]
na pilha e desalocá-la após o f
retorno, mas isso seria catastrófico: mudariaf
de usar memória O (1) (sob coleta de lixo) para memória O (n).
Um extenso trabalho foi feito na década de 1990 e no início de 2000 na inferência de região para a linguagem funcional estrita ML. Mads Tofte, Lars Birkedal, Martin Elsman e Niels Hallenberg escreveram uma retrospectiva bastante legível sobre seu trabalho em inferência de região, grande parte da qual eles integraram ao compilador MLKit . Eles experimentaram com gerenciamento de memória puramente baseado em região (ou seja, sem coletor de lixo), bem como gerenciamento de memória híbrido baseado em região / coletado por lixo, e relataram que seus programas de teste rodaram "entre 10 vezes mais rápido e 4 vezes mais lento" do que o lixo puro- versões coletadas.