Tenho alguma experiência em escrever pequenas ferramentas em Haskell e acho muito intuitivo usar, especialmente para escrever filtros (usando interact
) que processam sua entrada padrão e a canalizam para a saída padrão.
Recentemente, tentei usar um desses filtros em um arquivo que era cerca de 10 vezes maior que o normal e recebi um Stack space overflow
erro.
Depois de fazer algumas leituras (por exemplo, aqui e aqui ), identifiquei duas diretrizes para economizar espaço na pilha (haskellers experientes, corrija-me se escrever algo que não esteja correto):
- Evite chamadas de função recursivas que não são recursivas de cauda (isso é válido para todos os idiomas funcionais que oferecem suporte à otimização de chamada de cauda).
- Introduzir
seq
para forçar a avaliação antecipada das subexpressões para que as expressões não cresçam muito antes de serem reduzidas (isso é específico para Haskell, ou pelo menos para idiomas usando avaliação lenta).
Depois de introduzir cinco ou seis seq
chamadas no meu código, minha ferramenta roda sem problemas novamente (também nos dados maiores). No entanto, acho que o código original era um pouco mais legível.
Como não sou um programador experiente da Haskell, gostaria de perguntar se a introdução seq
dessa maneira é uma prática comum e com que frequência a pessoa verá normalmente seq
no código de produção da Haskell. Ou existem técnicas que permitem evitar o uso com seq
muita frequência e ainda usam pouco espaço na pilha?
Respostas:
Infelizmente, há casos em que é preciso usar
seq
para obter um programa eficiente / que funcione bem para grandes dados. Portanto, em muitos casos, você não pode ficar sem ele no código de produção. Você pode encontrar mais informações no Mundo Real Haskell, Capítulo 25. Criação de perfil e otimização .No entanto, existem possibilidades de como evitar o uso
seq
direto. Isso pode tornar o código mais limpo e mais robusto. Algumas ideias:interact
. O IO preguiçoso é conhecido por ter problemas com o gerenciamento de recursos (não apenas a memória) e os iterados são projetados exatamente para superar isso. (Sugiro evitar E / S preguiçosas no todo, independentemente do tamanho dos seus dados - consulte O problema com E / S preguiçosa .)seq
diretamente (ou criar o seu próprio) combinadores, como foldl ' ou foldr' ou versões estritas de bibliotecas (como Data.Map.Strict ou Control.Monad.State.Strict ) projetadas para cálculos estritos.seq
com uma correspondência estrita de padrões. Declarar campos estritos do construtor também pode ser útil em alguns casos.rseq
) ou NF completo (rdeepseq
) também. Existem muitos métodos utilitários para trabalhar com coleções, combinar estratégias etc.fonte
ByteString
.