Acidificante em Haskell: como controlar quando aplicar
Você tá lidando com uma função que precisa ser estritamente evaluada, mas o compilador tá te mandando avisos de strictness todo dia. A coisa mais chata é quando você escreve uma fold que deveria preguiçosamente acumular valores, mas de repente o runtime gasta memória demais porque alguma subexpressão foi forçada antes da hora. A pergunta que todo mundo faz é se existe um número mágico de quanto usar o strictness annotation. A resposta honesta é: depende do caso, mas na prática eu costumo colocar `seq` ou annotations de strictness apenas onde o profile mostra gargalo real. Deixa eu te contar como isso funciona no dia a dia. Quando eu estava otimizando um parser de CSV que leria arquivos com milhões de linhas, o problema era uma função recursiva que acumulava thunks na pilha. O compilador Haskell não forza a avaliação automaticamente, então cada chamada criava uma cadeia de expressões pendentes que só eram resolvidas no final. O memory usage subia linearmente com o tamanho do arquivo, e em arquivos grandes o garbage collector entrava em loop.
A solução que funcionou foi adicionar strictness annotations nos parâmetros intermediários da fold. Não foi uma questão de "usar X vezes", mas de identificar onde a evaluación preguiçosa causava retenção desnecessária. Eu usei `seq` nos acumuladores principais e vi o memory footprint cair de 2GB para cerca de 50MB no mesmo arquivo.
Como decidir quando aplicar strictness
O processo começa olhando o profile. O GHC tem opções de profiling que mostram exatamente onde a memória está sendo retida. Você roda com `-prof -fprof-auto` e abre o resultado no hp2ps. Os gráficos mostram picos de memory allocation que correspondem a funções específicas. Depois de identificar o culpado, você adiciona annotations de strictness nos parâmetros. A notação comum é usar `!pattern` em record fields ou `seq` explicitamente. O compilador transforma essas annotations em chamadas de evaluation antecipada. É diferente de colocar strictness em tudo, porque isso pode destruir a lazy evaluation que é útil em outros pontos do código. Um insight contra-intuitivo que aprendi na prática é que às vezes adicionar strictness em um acumulador que parecia inocente causa menos overhead do que expected. O compilador otimiza melhor quando sabe que um valor será usado imediatamente, e em alguns casos isso gera código mais rápido mesmo com a annotation extra. O ganho não é só em memória, mas também em tempo de CPU porque o garbage collector tem menos trabalho.
Get the Full Details

Quando NÃO usar strictness
Tem cenários onde strictness annotations são prejudiciais. Se você tá construindo uma estrutura de dados que precisa ser parcialmente avaliada, forçar avaliação prematura pode quebrar a streaming natural. Por exemplo, processar um arquivo de log gigante linha por linha funciona bem com lazy evaluation, mas se você aplicar strictness no acumulador de linhas, perde a capacidade de descartar dados já processados. Outro caso é quando a função recursiva tem padrão de cabeça-de-pilha (tail recursion) que já é otimizada pelo compilador. Adicionar strictness nesses casos não traz benefício mensurável e só aumenta o tamanho do código. Eu testei isso em benchmarks onde a diferença de performance era menor que a variabilidade natural do sistema.
Workaround para problemas específicos
Quando você encontra um problema de strictness que não pode ser resolvido apenas com annotations, tem alternativas. O primeiro é usar `unsafeForce` do módulo Data.Unsafe quando você tem certeza absoluta de que o valor será usado. É diferente de `seq` porque evita checks de segurança, mas also evita que o compilador otimize mal a avaliação. Outra opção é reestruturar o código para usar monads de estado explicitamente. O State Monad ou ST Monad dão controle fino sobre quando a memória é alocada e quando é liberada. Isso é mais verboso do que adicionar strictness, mas em sistemas complexos o ganho em clareza compensa. Eu prefiro essa abordagem quando o código precisa ser mantido por mais de um desenvolvedor. Para problemas de strictness em folds com acumuladores grandes, a alternativa mais prática é usar `foldl'` do módulo Data.List. Ela é equivalente a adicionar strictness manualmente, mas já vem otimizada no padrão library. A diferença é que você não precisa decorar onde colocar annotations, e o compilador aplica o strictness automaticamente nos parâmetros corretos.
Download e recursos adicionais
Se você quer implementar strictness annotations no seu projeto Haskell, o primeiro passo é ativar profiling no cabal.project. Adicione `profiling: True` e rode com cabal build --enable-profiling. O GHC gera arquivos de perfil que você pode analisar com hp2ps ou ghc-prof. Para entender melhor como a strictness funciona internamente, o manual do GHC tem uma seção sobre "Strictness Analysis". A documentação explica como o compilador decide quando forçar avaliação, e em quais casos as annotations manuais são necessárias. A leitura leva cerca de 30 minutos, mas resolve a maioria das dúvidas sobre when-to-use. Existem packages no Hackage que facilitam o gerenciamento de strictness annotations, como o strict ou strictly. Eles fornecem macros e helpers para adicionar strictness sem escrever annotations manualmente. O uso mais comum é em projetos que precisam de performance crítica em loops, onde cada ciclo de avaliação conta.

Para casos onde strictness annotations não resolvem o problema, a alternativa final é reescrever a função usando pattern matching explícito. O case com `whnf` (weak head normal form) dá controle granular sobre quando a expressão é forçada. É mais verboso do que annotations, mas em edge cases complexos o ganho em previsibilidade compensa o esforço adicional.