Haskell 基于密钥的回忆录

Haskell 基于密钥的回忆录,haskell,functional-programming,Haskell,Functional Programming,我最近一直在玩MemoCombinators和MemoTrie软件包,我试图回忆一个函数,它采用了一个树(实际上是一个伪装成DAG的函数,因为有几个节点是共享的)。以以下形式: data Tree k a = Branch (Tree k a) (k, a) (Tree k a) | Leaf (k, a) 所以我想记住一个类型的函数(基于它的键): 现在我有一个模糊的理解,这些记忆组合被用来将你的函数f::a->a转化为a的惰性(未评估)值结构,因此当你取出一个时,它已经被评估过了。所以这对

我最近一直在玩MemoCombinators和MemoTrie软件包,我试图回忆一个函数,它采用了一个树(实际上是一个伪装成DAG的函数,因为有几个节点是共享的)。以以下形式:

data Tree k a = Branch (Tree k a) (k, a) (Tree k a) | Leaf (k, a)
所以我想记住一个类型的函数(基于它的键):

现在我有一个模糊的理解,这些记忆组合被用来将你的函数
f::a->a
转化为
a
的惰性(未评估)值结构,因此当你取出一个时,它已经被评估过了。所以这对我的树来说不是问题——不知何故,我需要将它转换成一个由
k
索引的值结构

我不知道如何使用combinator库来实现它。解决这个问题的一个简单方法是创建一个函数
k->a
,它对一个映射进行索引,这个函数非常适合,但看起来有点笨重

我是在这个目标上被误导了,还是错过了一些显而易见的东西

我可以很容易地看到如何用这种风格写出这个函数,在所有计算中显式地将我的“表”线程化:

f :: Tree Int Int -> Map Int Int -> (Int, Map Int Int)
f (Branch l (k, x) r) m | (Just r) <- lookup k m = r
                        | otherwise = (max rl rr, m'')
     where 
       (rl, m') = (f l m) 
       (rr, m'') = (f r m') 
f::Tree Int->Map Int->(Int,Map Int)

f(分支l(k,x)r)m |(仅r)因此,大多数记忆技术使用状态。函数的记忆版本 保留将输入映射到已记忆输出的集合。当它得到输入时,它会检查集合, 返回已记忆的值(如果可用)。否则,它将使用原始 函数的版本,将输出保存在集合中,并返回新记录的输出。 因此,记忆化的集合在函数的生命周期内不断增长

你提到的Haskell备忘录避免了状态,而是预先计算数据结构 它保存已记忆输出的集合,使用惰性来确保特定输出的值 直到需要时才计算输出。这与有状态方法有很多共同点,除了几个关键点:

  • 因为集合是不可变的,所以它永远不会增长。每次重新计算未移动的输出
  • 因为集合是在函数使用之前创建的,所以它不知道 将使用哪些输入。因此,回忆录器必须提供一组输入,通过这些输入 回忆
这是相当简单的手动实现:

module Temp where
import Prelude hiding (lookup)
import Control.Arrow ((&&&))
import Data.Map (fromList, lookup)

data Tree k a = Branch (Tree k a) (k, a) (Tree k a) | Leaf (k, a)

key :: Tree k a -> k
key (Leaf (k, _)) = k
key (Branch _ (k,_) _) = k

-- memoize a given function over the given trees
memoFor :: Ord k => [Tree k a] -> (Tree k a -> b) -> Tree k a -> b
memoFor ts f = f'
  where f' t = maybe (f t) id $ lookup (key t) m
        m = fromList $ map (key &&& f) ts
MemoCombinators和MemoTrie包试图做的是使输入集合隐式(使用函数) 并分别键入类)。如果可以列举所有可能的输入,那么我们就可以 使用该枚举来构建我们的数据结构

在您的情况下,因为您只想在树的
键上进行记忆,所以最简单的方法可能是
要使用MemoCombinators软件包中的
wrap
功能:

换行:(a->b)->(b->a)->备忘录a->备忘录b

给定a的回忆体和a与b之间的同构,构建b的回忆体

因此,如果您的
值具有相应的
备注
值(例如,
键入key=Int
), 您有一个从
Key
s到
Tree Key Val
的双射,然后您可以使用 为您的
树键Val
函数创建回忆录的双射:

memoize :: (Tree Key Val -> b) -> (Tree Key Val -> b)
memoize = wrap keyToTree treeToKey memoForKey

更新:如果您无法提前创建这样的映射,可能解决方案是使用状态单子,以便您可以在移动中进行记忆:

{-# LANGUAGE FlexibleContexts #-}
-- ... 

import Control.Monad.State (MonadState, gets, modify)
import Data.Map (Map, insert)
-- ... 

memoM :: (Ord k, MonadState (Map k b) m) => (Tree k a -> m b) -> (Tree k a -> m b)
memoM f = f'
  where f' t = do
        let k = key t
        v <- gets $ lookup k
        case v of
          Just b -> return b
          Nothing -> do
            b <- f t
            modify $ insert k b
            return b

-- example of use
sumM :: (Ord k, MonadState (Map k Int) m) => Tree k Int -> m Int
sumM = memoM $ \t -> case t of
        Leaf (_,b) -> return b
        Branch l (_,b) r -> do
          lsum <- sumM l
          rsum <- sumM r
          return $ lsum + b + rsum
{-#语言灵活上下文}
-- ... 
import Control.Monad.State(MonadState、get、modify)
导入数据.Map(映射,插入)
-- ... 
备忘录::(Ord k,MonadState(MAPK b)m)=>(树k a->m b)->(树k a->m b)
备忘录f=f'
其中f't=do
设k=键t
v返回b
无事可做
b树k Int->m Int
sumM=memoM$\t->案例t
叶(u,b)->返回b
分支机构l(u,b)r->do

嗯,看,这就是问题所在。除非我制作一个外部映射(从key->Tree),否则没有简单的方法来编写keytore。看看你是如何编写memorfor的——问题是树的域不容易找到,而且它们不能凭空生成。在构建表结构时,无论是列表还是数组,它都需要是可以构造(或作为列表提供)的函数。嗯。@Oliver:嗯,你觉得更新你的代码使用单子怎么样?然后,您可以在获取内容时使用状态和缓存。我会更新一个例子。太好了,非常感谢。我现在对组合器使用的技术有了更透彻的理解(以及为什么我的问题不起作用)。我无法解释为什么显式传递方法有效,但我无法让类型与自动方法对齐。干杯奥利弗
{-# LANGUAGE FlexibleContexts #-}
-- ... 

import Control.Monad.State (MonadState, gets, modify)
import Data.Map (Map, insert)
-- ... 

memoM :: (Ord k, MonadState (Map k b) m) => (Tree k a -> m b) -> (Tree k a -> m b)
memoM f = f'
  where f' t = do
        let k = key t
        v <- gets $ lookup k
        case v of
          Just b -> return b
          Nothing -> do
            b <- f t
            modify $ insert k b
            return b

-- example of use
sumM :: (Ord k, MonadState (Map k Int) m) => Tree k Int -> m Int
sumM = memoM $ \t -> case t of
        Leaf (_,b) -> return b
        Branch l (_,b) r -> do
          lsum <- sumM l
          rsum <- sumM r
          return $ lsum + b + rsum