haskell GHC是否重用递归函数计算?

w6lpcovy  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(89)

假设我有以下函数

myFunction :: Integer->Integer->Integer->Rational
myFunction n m l
    | m>l  = 0
    | m==1 = 1 / n ** (l-1)
    | m==l = (p n l 1) * ( factorial (n-1) ) / factorial (n-l)
    | otherwise = (m/n) * (p n (l-1) m) + ((n-m+1)/n) p n (l-1) (m-1)

细节并不重要,关键是为了计算n=100,l=200和m=100的函数,代码需要解决(l=199,m=100)和(l=199,m=100)的问题,等等。问题是,为了返回任何需要达到l==m或m==1的值,我的问题是:GHC是否重用它已经计算过的值?
除了解决递归(在这种情况下是可能的),有没有一种方法可以改进代码,使其不重新计算值?

fwzugrvs

fwzugrvs1#

GHC并不试图自动记忆函数调用的结果,因此在递归中遇到的两个单独的p 100 199 100调用通常需要两个单独的计算。
如果你想记住电话,有两种方法。
你可以使用一个记忆库。有相当多的可用,所以最好访问Hackage package list并搜索页面“memoi”,如果你想看看有什么可用的。例如,使用memoize包,您的函数可以使用以下内容进行memoize:

import Data.Function.Memoize
import Data.Ratio

myFunction :: Integer -> Integer -> Integer -> Rational
myFunction = memoFix3 f
  where f p n l m
          | m > l  = 0
          | m==1 = 1 % (n ^ (l-1))
          | m==l = (p n l 1) * ( factorial (n-1) % factorial (n-l) )
          | otherwise = (m % n) * (p n (l-1) m) + ((n-m+1) % n) * p n (l-1) (m-1)

        factorial n = product [1..n]

main = print $ myFunction 100 200 100

这将在几秒钟内生成一个答案。它还在调用之间进行记忆,因此main中的其他myFunction调用将使用以前调用的记忆结果。
还有一种Haskell特有的技术,你可以构建一个数据结构来表示所有可能的(或至少是所有需要的)函数求值的结果,利用惰性来自动计算所需的结果,按照依赖顺序并且没有重复,即使数据结构在概念上是无限的。
下面是你的函数的一个版本,它从所有需要的对(l, m)到结果中使用了一个(有限的)Map

import Data.Map.Lazy ((!))
import qualified Data.Map.Lazy as Map
import Data.Ratio

myFunction :: Integer -> Integer -> Integer -> Rational
myFunction n l m
  = p' ! (l, m)
  where p l m
          | m > l  = 0
          | m==1 = 1 % (n ^ (l-1))
          | m==l = (p' ! (l, 1)) * ( factorial (n-1) % factorial (n-l) )
          | otherwise = (m % n) * (p' ! (l-1, m)) + ((n-m+1) % n) * p' ! (l-1, m-1)
        p' = Map.fromList [ ((l',m'), p l' m')
                 | l' <- [1..l]
                 , m' <- [1..l'] ]

        factorial n = product [1..n]

main = do
  print $ myFunction 100 200 100

这个方法在每次调用时重新生成Map,所以“记忆化”不会在调用之间共享。

相关问题