我发现this answer和this wiki page是Haskell中记忆化的绝佳介绍。但是,他们确实给我留下了一个我希望得到回答的问题:
在我看来,所使用的技术要求您“打开”(如“访问内部”)用于存储备忘录的数据结构。例如,1实现了表结构,2实现了section 3中的树。是否可以使用预先制作的数据结构进行类似的操作?例如,假设您认为Data.Map
非常棒,并希望将您的memoized值存储在Map
中。可以用一个预制的数据结构来实现memoization,其中一个不实现结构本身,而是使用预先制作的结构?
希望有人会给我一个关于如何思考,或者更有可能纠正我对功能性记忆的误解的暗示。
编辑:我可以想到一种方法,但它并不优雅:如果f :: a -> b
,那么可以轻松制作一个memoized version f' :: Map a b -> a -> (Map a b, b)
,其中第一个参数是memoization存储,输出对包含可能更新的存储和计算值。这种状态传递肯定不是我想要的(尽管我猜它可能包含在monad中,但它比1和2中的方法更加丑陋。
编辑2:也许尝试表达我当前的(错误的)思维方式会有所帮助。目前,我似乎反复将我自己违背自己的意愿拉入非解决方案
import qualified Data.Map as Map
memo :: (Ord a) => [a] -> (a -> b) -> (a -> b)
memo domain f = (Map.!) storage
where
storage = Map.fromList (zip domain (map f domain))
我越是盯着这一点,我越发现我误解了一些基本的东西。你知道,我觉得我的memo [True, False]
等同于1的bool
个回忆录。
答案 0 :(得分:3)
如果您注意到,Data.Memocombinators实际上依赖于“预先制作的”Data.IntTrie。我确信您可以使用相同的代码并将IntTrie的使用替换为另一种数据结构,尽管它可能效率不高。
记忆的一般想法是保存计算结果。在Haskell中,最简单的方法是将函数映射到一个表,其中表每个参数有一个维度。由于Haskell是懒惰的(好吧,Haskell中的大多数数据结构都是),它只会在您特别要求时评估给定单元格的值。 “table”基本上是指“map”,因为它会将你从key(s)带到value。
[edit]关于示例2的其他想法
如果我没有弄错,那么第一次(Map.!) storage
被迫为给定的密钥进行评估时,storage
结构将被完全拧干(尽管计算f
赢了除了给定的密钥之外的任何事情都会发生。因此,第一次查找将导致额外的O(n)操作,n为length domain
。后续查找不会产生此费用。
类似于典型的int-indexed列表或IntTrie的Lazier结构同样需要在调用查找时显示其结构,但与Map不同,它们不需要一次完成。列表被拧干,直到访问索引键。 IntTries只拧出所需键的“前缀”(或后缀?不确定。可能以任何方式实现)的整数键。索引11,(1011
)会拧出1(1
),2(10
),5(101
)和11(1011
)。 Data.Memocombinators
只是将所有密钥转换为Ints(或“位”),以便可以使用IntTrie
。
P.S。是否有一个比“拧干”更好的短语?脑海中浮现出“武力”,“脊椎”和“表现”等字样,但我无法想到正确的词/短语。