简体   繁体   English

你如何在Haskell中创建一个通用的memoize函数?

[英]How do you make a generic memoize function in Haskell?

I've seen the other post about this , but is there a clean way of doing this in Haskell? 我已经看过关于这个的另一篇文章了 ,但是在Haskell有一个干净的方法吗?

As a 2nd part, can it also be done without making the function monadic? 作为第二部分,还可以在不使功能monadic的情况下完成吗?

The package data-memocombinators on hackage provides lots of reusable memoization routines. hackage上的数据包memocombinators提供了大量可重用的memoization例程。 The basic idea is: 基本思路是:

type Memo a = forall r. (a -> r) -> (a -> r)

Ie it can memoize any function from a. 即它可以记忆任何功能。 The module then provides some primitives (like unit :: Memo () and integral :: Memo Int ), and combinators for building more complex memo tables (like pair :: Memo a -> Memo b -> Memo (a,b) and list :: Memo a -> Memo [a] ). 然后该模块提供一些原语(如unit :: Memo ()integral :: Memo Int ),以及用于构建更复杂的备忘录表的组合器(如pair :: Memo a -> Memo b -> Memo (a,b)list :: Memo a -> Memo [a] )。

You can modify Jonathan´s solution with unsafePerformIO to create a "pure" memoizing version of your function. 您可以使用unsafePerformIO修改Jonathan的解决方案,以创建函数的“纯”记忆版本。

import qualified Data.Map as Map
import Data.IORef
import System.IO.Unsafe

memoize :: Ord a => (a -> b) -> (a -> b)
memoize f = unsafePerformIO $ do 
    r <- newIORef Map.empty
    return $ \ x -> unsafePerformIO $ do 
        m <- readIORef r
        case Map.lookup x m of
            Just y  -> return y
            Nothing -> do 
                    let y = f x
                    writeIORef r (Map.insert x y m)
                    return y

This will work with recursive functions: 这将适用于递归函数:

fib :: Int -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib_memo (n-1) + fib_memo (n-2)

fib_memo :: Int -> Integer
fib_memo = memoize fib

Altough this example is a function with one integer parameter, the type of memoize tells us that it can be used with any function that takes a comparable type. 尽管这个例子是一个带有一个整数参数的函数,memoize的类型告诉我们它可以用于任何具有可比类型的函数。 If you have a function with more than one parameter just group them in a tuple before applying memoize. 如果你有一个带有多个参数的函数,只需在应用memoize之前将它们组合在一个元组中。 Fi: 网络连接:

f :: String -> [Int] -> Float
f ...

f_memo = curry (memoize (uncurry f))

This largely follows http://www.haskell.org/haskellwiki/Memoization . 这主要遵循http://www.haskell.org/haskellwiki/Memoization

You want a function of type (a -> b). 你想要一个类型(a - > b)的函数。 If it doesn't call itself, then you can just write a simple wrapper that caches the return values. 如果它不调用自身,那么你可以编写一个缓存返回值的简单包装器。 The best way to store this mapping depends on what properties of a you can exploit. 存储此映射的最佳方法取决于您可以利用的属性。 Ordering is pretty much a minimum. 订购几乎是最低限度。 With integers you can construct an infinite lazy list or tree holding the values. 使用整数,您可以构造一个包含值的无限惰性列表或树。

type Cacher a b = (a -> b) -> a -> b

positive_list_cacher :: Cacher Int b
positive_list_cacher f n = (map f [0..]) !! n

or 要么

integer_list_cacher :: Cacher Int b
integer_list_cacher f n = (map f (interleave [0..] [-1, -2, ..]) !!
    index n where
        index n | n < 0  = 2*abs(n) - 1
        index n | n >= 0 = 2 * n

So, suppose it is recursive. 所以,假设它是递归的。 Then you need it to call not itself, but the memoized version, so you pass that in instead: 然后你需要它不是自己调用,而是记忆版本,所以你传递它:

f_with_memo :: (a -> b) -> a -> b
f_with_memo memoed base = base_answer
f_with_memo memoed arg  = calc (memoed (simpler arg))

The memoized version is, of course, what we're trying to define. 当然,备忘版本是我们要定义的内容。

But we can start by creating a function that caches its inputs: 但我们可以从创建一个缓存其输入的函数开始:

We could construct one level by passing in a function that creates a structure that caches values. 我们可以通过传入一个创建一个缓存值的结构的函数来构造一个级别。 Except we need to create the version of f that already has the cached function passed in. 除了我们需要创建已经传入缓存函数的f版本。

Thanks to laziness, this is no problem: 由于懒惰,这是没有问题的:

memoize cacher f = cached where
         cached = cacher (f cached)

then all we need is to use it: 那么我们所需要的就是使用它:

exposed_f = memoize cacher_for_f f

The article gives hints as to how to use a type class selecting on the input to the function to do the above, rather than choosing an explicit caching function. 本文提供了关于如何使用类型类选择函数的输入来执行上述操作的提示,而不是选择显式缓存函数。 This can be really nice -- rather than explicitly constructing a cache for each combination of input types, we can implicitly combine caches for types a and b into a cache for a function taking a and b. 这可能非常好 - 我们可以隐式地将类型a和b的高速缓存组合到用于获取a和b的函数的高速缓存中,而不是为每种输入类型组合显式构建高速缓存。

One final caveat: using this lazy technique means the cache never shrinks, it only grows. 最后一点需要注意:使用这种懒惰的技术意味着缓存永远不会缩小,只会增长。 If you instead use the IO monad, you can manage this, but doing it wisely depends on usage patterns. 如果你改为使用IO monad,你可以管理它,但明智地做它取决于使用模式。

Doing a direct translation from the more imperative languages, I came up with this. 从更多命令式语言进行直接翻译,我想出了这个。

memoize :: Ord a => (a -> IO b) -> IO (a -> IO b)
memoize f =
  do r <- newIORef Map.empty
     return $ \x -> do m <- readIORef r
                       case Map.lookup x m of
                            Just y  -> return y
                            Nothing -> do y <- f x
                                          writeIORef r (Map.insert x y m)
                                          return y

But this is somehow unsatisfactory. 但这在某种程度上令人不满意。 Also, Data.Map constrains the parameter to be an instance of Ord . 此外, Data.Map将参数约束为Ord的实例。

If your arguments are going to be natural numbers, you can do simply: 如果你的论点是自然数,你可以简单地做:

memo f = let values = map f [0..]
     in \n -> values !! n

However, that doesn't really help you with the stack overflowing, and it doesn't work with recursive calls. 但是,这并不能真正帮助您解决堆栈溢出问题,并且它不适用于递归调用。 You can see some fancier solutions at http://www.haskell.org/haskellwiki/Memoization . 您可以在http://www.haskell.org/haskellwiki/Memoization上看到一些更高级的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM