【问题标题】:How do you make a generic memoize function in Haskell?你如何在 Haskell 中创建一个通用的 memoize 函数?
【发布时间】:2010-09-13 14:07:03
【问题描述】:

我见过the other post about this,但是在 Haskell 中是否有一种干净的方法来做到这一点?

作为第二部分,是否也可以在不使函数单子化的情况下完成?

【问题讨论】:

  • 我几乎不会拼写 Haskell :),但我不这么认为。据我了解,记忆完全涉及纯函数式语言所不允许的那种静态状态。当然,我认为使用 monad 可以做到这一点。但是你的第二部分表明你已经知道了。
  • @Mike:我可能也有同样的想法,但实际上函数式语言可以很好地进行记忆,正如答案所示。他们只需要通过函数参数传递状态。

标签: haskell monads memoization


【解决方案1】:

hackage 上的包 data-memocombinators 提供了许多可重用的 memoization 例程。基本思路是:

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

即它可以记忆a中的任何功能。然后该模块提供一些原语(如unit :: Memo ()integral :: Memo Int),以及用于构建更复杂的备忘录表的组合器(如pair :: Memo a -> Memo b -> Memo (a,b)list :: Memo a -> Memo [a])。

【讨论】:

    【解决方案2】:

    您可以使用 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
    

    这将适用于递归函数:

    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
    

    虽然这个例子是一个带有一个整数参数的函数,但 memoize 的类型告诉我们它可以与任何具有可比较类型的函数一起使用。如果您有一个具有多个参数的函数,只需在应用 memoize 之前将它们分组到一个元组中。菲:

    f :: String -> [Int] -> Float
    f ...
    
    f_memo = curry (memoize (uncurry f))
    

    【讨论】:

    • 这很好。如果您还可以显示所需的导入语句,这可能对新手有帮助。
    • 不需要unsafePerformIO。在 ST monad 中执行相同的代码。
    • @uhbif19 是这样吗?我挑战你写它(最后的纯(a -&gt; b)完好无损)
    【解决方案3】:

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

    你想要一个 (a -> b) 类型的函数。如果它不调用自己,那么 您可以编写一个简单的包装器来缓存返回值。这 存储此映射的最佳方式取决于您可以使用的 a 的哪些属性 开发。订购几乎是最低限度的。带整数 您可以构造一个无限的惰性列表或保存这些值的树。

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

    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
    

    所以,假设它是递归的。然后你需要它不调用自己,而是调用 记忆的版本,所以你把它传入:

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

    记忆化的版本当然是我们想要定义的。

    但我们可以从创建一个缓存其输入的函数开始:

    我们可以通过传入一个创建一个 缓存值的结构。除了我们需要创建 f 的版本 已经有传入的缓存函数。

    多亏了懒惰,这没问题:

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

    那么我们只需要使用它:

    exposed_f = memoize cacher_for_f f
    

    文章提供了有关如何使用类型类选择的提示 输入到函数来执行上述操作,而不是选择显式 缓存功能。这真的很好——而不是明确地 为输入类型的每个组合构造一个缓存,我们可以隐式地 将类型 a 和 b 的缓存组合成一个用于获取 a 和 b 的函数的缓存。

    最后一个警告:使用这种惰性技术意味着缓存永远不会缩小, 它只会增长。如果你改用 IO monad,你可以管理这个,但是 明智的做法取决于使用模式。

    【讨论】:

    • 我阅读了链接。我猜你必须创建一个新的类型类并为你想要记忆的任何类型实现它的接口。有什么方法可以在 Memoize 模块中编写一次代码来为该模块的用户节省工作?
    • 您可以编写要缓存的常用类型,以及一些组合它们的规则。如果他们使用您未定义的类型,他们将需要自己创建实例。
    • 您也可以基于类型类(例如 Ord 或 Bound)创建实例,但每个实例都应该放在单独的模块中——它们可能需要不同的缓存方案,因此需要选择不使用这些.
    【解决方案4】:

    从更命令的语言直接翻译,我想出了这个。

    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
    

    但这在某种程度上并不令人满意。此外,Data.Map 将参数限制为 Ord 的实例。

    【讨论】:

    • 当然,没有办法避免某种约束,无论是隐式的还是显式的。例如,你将如何记忆类型为 (Integer -> Bool) -> Bool 的函数?
    【解决方案5】:

    如果你的论点是自然数,你可以这样做:

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

    但是,这并不能真正帮助您解决堆栈溢出问题,并且它不适用于递归调用。你可以在http://www.haskell.org/haskellwiki/Memoization看到一些更高级的解决方案。

    【讨论】:

    • 这很有帮助,但我仍然觉得可能有更笼统的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-12
    • 2021-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-23
    相关资源
    最近更新 更多