Tenho tentado explorar as possibilidades de recursão em Haskell, mas não sei muito sobre como melhorar a eficiência ao enfrentar grandes recursões. Hoje, eu queria fazer uma função que calculasse números de Stirling de segundo tipo:
factorial :: Integer -> Integer
factorial n = helper n 1
where
helper 1 a = a
helper n a = helper (n-1) (a*n)
stirling :: Integer -> Integer -> Integer
stirling n 1 = 1
stirling n 2 = ((2 ^n)- 2) `div` 2
stirling n k
| k > n = 0
| k == n = 1
| k == (n-1) = (factorial n) `div` (factorial 2 * (factorial(n-2)))
| otherwise = stirling (n-1) (k-1) + k *(stirling (n-1) k)
Tenho tentado criar uma função auxiliar, assim como na definição fatorial, que pudesse evitar a inconveniência da avaliação preguiçosa de Haskell consumir muitos recursos de memória. Quero fazer o mesmo, calculando o produto em cada chamada do
k *(stirling (n-1) k)
recursão, mas não peguei o jeito. Alguma ideia?
PD: O código já funciona bem, mas sou novo em Haskell e ainda não sei quais são as possibilidades. Qualquer melhoria é bem-vinda (embora eu não use nenhuma biblioteca; quero aprender o básico antes de entrar nisso).
Não posso escrever uma explicação muito detalhada agora, mas você pode torná-la um pouco mais rápida:
Int
para a entrada em vez deInteger
Então você entende isso:
A próxima coisa que eu tentaria é a programação dinâmica para evitar computação redundante.
E você usa o estilo acumulador para fazer com que uma das chamadas recursivas não finais seja recursiva, como fazem nesta postagem do blog: https://www.haskell.org/ghc/blog/20190728-free-variable-traversals.html
Sua implementação já parece ser bastante eficiente em termos de memória, pelo menos em meus testes. Por exemplo:
Alterar os parâmetros (para cima ou para baixo) não parece alterar muito a residência de 8 MB, então espero que seja o custo mínimo de inicialização de um programa Haskell. Por outro lado, parece bastante lento. Parâmetros ainda um pouco maiores do que os que usei aqui - digamos, o dobro de ambos - demoraram mais do que eu gostaria de esperar. A simples implementação direta da fórmula da Wikipedia parece ser muito mais rápida, mantendo um bom perfil de memória:
(Eu não recompilei entre essas execuções e as anteriores, então você pode ter certeza de que ambos obtivemos o mesmo nível de otimização.)
Por outro lado, recebo uma resposta um pouco diferente da sua. Talvez você consiga detectar um bug em um ou outro de nosso código. De qualquer forma, aqui está a fórmula da Wikipedia * e aqui está minha opinião sobre sua tradução mais direta:
Não se esqueça de compilar
-O2
e deixar o GHC fazer todo o trabalho sujo de pensar em como torná-lo rápido e com uso eficiente de memória.* StackOverflow possui um mecanismo para incluir a imagem diretamente, em vez de vinculá-la, mas não parece estar funcionando para mim. Não sei por que!