在Haskell中实现备忘录是单子吗?

在Haskell中实现备忘录是单子吗?,haskell,monads,memoization,Haskell,Monads,Memoization,我试着用记忆法求解(涉及Collatz序列的长度),我就是这样做的,以保留以前计算的结果。我有一个函数,collatzSequence,我想用它来记忆,我用computeWithMemo来记忆它,它接受一个函数,一个值来计算函数,一个Map,并返回函数在该点的值和一个更新的Map。这就是单子模式吗?谢谢 import Data.Map computeWithMe

我试着用记忆法求解(涉及Collatz序列的长度),我就是这样做的,以保留以前计算的结果。我有一个函数,
collatzSequence
,我想用它来记忆,我用
computeWithMemo
来记忆它,它接受一个函数,一个值来计算函数,一个
Map
,并返回函数在该点的值和一个更新的
Map
。这就是单子模式吗?谢谢

import Data.Map                                                                   

computeWithMemo :: (Int -> Int) -> Int -> Map Int Int -> (Maybe Int, (Map Int Int)
computeWithMemo fun key memo                                                      
  | elem key (Data.Map.keys memo) = (Data.Map.lookup key memo, memo)              
  | otherwise = (Just (fun key), Data.Map.insert key (fun key) memo)              

collatzSequence :: Int -> Int                                                     
collatzSequence x                                                                 
  | x == 1 = 1                                                                    
  | even x = 1 + collatzSequence (x `div` 2)                                      
  | odd x = 1 + collatzSequence (x*3 + 1)                                         

memoize f = computeWithMemo f                                                     

memoizedCollatz = memoize collatzSequence                                         

solve x m                                                                         
  | x > 1 = solve (x-1) (snd (computeWithMemo (collatzSequence) x m))             
  | otherwise = m                                                                 

solution = solve 10000 Data.Map.empty                                             

它是对
状态
monad内部部分的特殊重新实现,即创建和执行函数,以模拟状态的方式获取并返回附加参数

您的代码与
状态
之间的主要区别在于:

  • 您可以在
    solve
    方法中硬编码传递特定类型函数状态的逻辑

    State
    提供一个函数
    >=
    (bind),该函数定义如何组合两个有状态函数,或如何从另一个有状态函数调用一个有状态函数(所有monad都需要这样做)

  • 您可以硬编码从无状态函数创建有状态函数的过程,获取并返回
    Int

    State
    提供了一个函数
    return
    ,可用于使任何无状态函数有状态(所有monad都需要这样做)

  • 您可以硬编码可以在状态上执行的操作,特别是在
    Map Int
    中记忆函数

    State
    提供了一些函数,该状态与
    >=
    一起可用于以各种方式创建有状态函数(这是特定于
    State
    ,而不是一般的monad)

是的,你已经基本上定义了一个非常非常具体的单子的狭义情况


如果你想正式地使它成为一个真正的monad,你可以定义类似于
>=
return
,甚至可以实现typeclass,这样你就可以在它们上面使用Haskell的组合词和语法糖了。

因为记忆是有状态的,使用monadic绑定看起来是一种合理的方式来表示它的顺序性质;同样的技巧也适用于I/O。我想知道
State
monad是否直接适用于您的情况:它是专门为传递状态(内存)的变化而构建的。那么,这是一个monadic计算吗?我真的很难理解教程中的单子,但可能它们只是这个单子有一个方便的函数
>=
(又称“bind”aka“and then”),它使用一个单子值计算一个函数,然后返回另一个:
foo>=\x->retrun bar
,cf.
foo(),然后(JS中的函数(x){return bar;}
。因此,
>=
s很容易链接,并且保证按照指定的顺序运行。这一事实用于实现命令式编程,这在
do
-符号中尤为明显。不过,单子并不限于此;它们只是在许多不同情况下出现的一种模式(并非巧合)。请注意,检查
key`elem`(Data.Map.keys memo)
,然后使用
Data.Map.lookup key memo
是一种反模式。相反您应该进行
查找
,然后对其进行模式匹配——通过
查找
返回
,您可以了解
是否不是
映射
的元素。此外,您总是在第一个坐标中返回
Just
值(或者
备忘录
中,
查找
返回
Just
,或者不返回,然后返回
Just(趣味键)
);因此,
computeMemo
的类型是不必要的松散,应该只重新输入一个
(Int,Map Int Int)
。此外,如果
键不在
备忘录中,您可以计算
趣味键两次。这可能会被一个足够热切的CSE优化器优化掉,但在这里你不应该依赖它;只需执行类似于
let result=fun key in(result,Map.insert key result memo)