f 是一个非常简单的函数。假设fIter 易于计算,f 也将易于计算。我们不需要记住f。我们唯一的目标是记住fIter。
当开始在 Haskell 中记忆递归函数时,以开放递归形式编写函数很有用。函数的开放递归形式删除了显式递归,而是添加了一个额外的参数来说明当函数需要递归调用自身时要做什么。这将为我们提供一种方法来改变函数递归调用自身的方式。
我们将fIter的签名从第一种类型更改为第二种类型
The type of fIter
v
Int -> Int -> Int
(Int -> Int -> Int) -> Int -> Int -> Int
^ ^
| Gets back something with the type of fIter
What to do when fIter needs to call itself recursively
fIter通过f递归调用自己,所以我们首先将(Int -> Int -> Int)参数添加到fIter到f的调用方式
f' :: (Int -> Int -> Int) -> Int -> Int -> Int
f' r blocksize spaces
| blocksize > spaces = 0
| blocksize == spaces = 1
| otherwise = 1 + r blocksize (spaces - blocksize)
修改后的fIter' 会将如何递归调用自身(它从新参数中获得)作为新参数传递给f'。
fIter' :: (Int -> Int -> Int) -> Int -> Int -> Int
fIter' r blocksize spaces =
sum $ map (f' r blocksize) [1..spaces]
我们可以通过将Data.Function 中定义的fix :: (a -> a) -> a 修复为fix f = let x = f x in x 来恢复原来的慢速fIter。
fIter :: Int -> Int -> Int
fIter = fix fIter'
现在我们可以改变fIter 的自称方式了。在MemoTrie 包中的Data.MemoTrie 中提供了另一种“根据需要填充的惰性数据结构”。它提供了两个我们感兴趣的功能。 trie :: HasTrie a => (a -> b) -> a :->: b 构建从提供的函数构建的惰性数据结构。 untrie :: HasTrie a => (a :->: b) -> a -> b 返回一个从惰性数据结构中查找参数的函数。这些仅适用于可以构建惰性数据结构的某些参数类型HasTrie a。幸运的是,对于我们的问题,已经有 HasTrie Int 和 (HasTrie a, HasTrie b) => HasTrie (a, b) 的实例。
来自Data.MemoTrie 的trie 只接受一个参数,因此我们需要将fIter 的两个参数打包成一个参数。我们通常会使用uncurry 执行此操作,但是,由于fIter' 需要将与结果相同类型的东西作为参数,我们还需要curry 递归调用来解包参数。 uncurryOr 将取消任何打开的递归函数。
uncurryOR :: (( a -> b -> c) -> a -> b -> c) ->
((a, b) -> c) -> (a, b) -> c
uncurryOR or = uncurry . or . curry
将此应用于fIter' 会产生令人满意的结果,uncurryOR fIter' :: ((Int, Int) -> Int) -> (Int, Int) -> Int。
一般来说,为了记忆一个开放递归函数,我们为开放递归函数构建一个 trie,传入 trie 查找作为函数应如何获得其递归结果。
import Data.MemoTrie
memoOR :: HasTrie a => ((a -> b) -> a -> b) ->
a -> b
memoOR or = f
where
f = untrie t
t = trie (or f)
我们可以从Data.MemoTrie 中将fix 和memo = untrie . trie 写成更优雅的memoOR。
memoOR or = fix (memo . or)
我们现在可以定义如何有效地计算fIter
fIter :: Int -> Int -> Int
fIter = curry . memoOR . uncurryOR $ fIter'