Haskell 记忆有效的功能

Haskell 记忆有效的功能,haskell,memoization,unsafe-perform-io,Haskell,Memoization,Unsafe Perform Io,我开始从事一个项目,该项目将细胞自动机定义为本地转换功能: newtype Cellular g a = Cellular { delta :: (g -> a) -> a } step :: Monoid g => Cellular g a -> (g -> a) -> (g -> a) step cell init g = delta cell $ init . (g <>) 只要g是Monoid,就可以通过在应用局部变换之前转移焦点

我开始从事一个项目,该项目将细胞自动机定义为本地转换功能:

newtype Cellular g a = Cellular { delta :: (g -> a) -> a }
step :: Monoid g => Cellular g a -> (g -> a) -> (g -> a)
step cell init g = delta cell $ init . (g <>)
只要
g
Monoid
,就可以通过在应用局部变换之前转移焦点来定义全局变换。这为我们提供了以下
步骤
功能:

newtype Cellular g a = Cellular { delta :: (g -> a) -> a }
step :: Monoid g => Cellular g a -> (g -> a) -> (g -> a)
step cell init g = delta cell $ init . (g <>)
我的问题是,我将
Cellular
概括为
CelluarT
,以便能够在本地规则中使用副作用(例如,复制随机邻居):

但是,我只希望效果运行一次,这样,如果您多次询问一个单元格的值,那么答案都是一致的<代码>备忘录使我们在这里失败,因为它保存了有效的计算,而不是结果

我不希望在不使用不安全功能的情况下实现这一点。我尝试使用
unsafePerformIO
、一个
IORef
和一个
Map g a
来存储已经计算的值:

memoM :: (Ord k, Monad m) => (k -> m v) -> (k -> m v)
memoM =
  let ref = unsafePerformIO (newIORef empty) in
  ref `seq` loopM ref

loopM :: (Monad m, Ord k) => IORef (Map k v) -> (k -> m v) -> (k -> m v)
loopM ref f k =
  let m = unsafePerformIO (readIORef ref) in
  case Map.lookup k m of
    Just v  -> return v
    Nothing -> do
      v <- f k
      let upd = unsafePerformIO (writeIORef ref $ insert k v m)
      upd `seq` return v
memoM::(Ord k,Monad m)=>(k->m v)->(k->m v)
备忘录=
让ref=unsafePerformIO(newIORef为空)输入
ref`seq`loopM ref
loopM::(单子m,ordk)=>IORef(映射kv)->(k->mv)->(k->mv)
loopM ref f k=
设m=unsafePerformIO(readIORef ref)in
case-Map.km的查找
只需v->返回v
无事可做

v getLine)
继续获取行,尽管向其传递了相同的参数。

首先,停止尝试使用unsafePerformIO。它有这个名字是有原因的

你要做的不是回忆录,而是控制对内在单子的呼叫。部分线索是Cellular不是单子,所以CellularT不是单子转换器

我认为您需要做的是使用一个纯函数来计算每个单元格所需的效果,然后在单元格上迭代以对效果进行排序。这将你的细胞自动机制(你已经有了,而且看起来不错)与有效的机制分开。目前,您似乎试图在计算效果的同时执行效果,这导致了您的问题


你的效果可能需要分为输入和输出两个阶段,或者类似的东西。或者,您的效果实际上更像一个状态机,其中每个单元格的每次迭代都会产生一个结果,并期望新的输入。在这种情况下,请参阅以了解有关如何执行此操作的一些想法。

如果您给自己一个机会分配参考以保持地图,则可以安全地实现此操作

import Control.Monad.IO.Class

memoM :: (Ord k, MonadIO m) => (k -> m v) -> m (k -> m v)
                 |                           |
                 |        opportunity to allocate the map
                 get to IO correctly
我将使用
MVar
而不是
IORef
来获得大部分正确的并发性。这是为了正确性,以防并发使用,而不是为了性能。为了提高性能,我们可能会更喜欢使用双重检查锁或具有更精细锁粒度的并发映射

import Control.Concurrent    
import Control.Monad.IO.Class    
import qualified Data.Map as Map

memoM :: (Ord k, Monad m, MonadIO m) => (k -> m v) -> m (k -> m v)
memoM once = do 
    mapVar <- liftIO $ newMVar Map.empty    
    return (\k -> inMVar mapVar (lookupInsertM once k))

-- like withMVar, but isn't exception safe   
inMVar :: (MonadIO m) => MVar a -> (a -> m (a, b)) -> m b
inMVar mvar step = do
    (a, b) <- liftIO (takeMVar mvar) >>= step
    liftIO $ putMVar mvar a
    return b

lookupInsertM :: (Ord k, Monad m) => (k -> m v) -> k -> Map.Map k v -> m (Map.Map k v, v)
lookupInsertM once k map = 
    case Map.lookup k map of
        Just v -> return (map, v)
        Nothing -> do
            v <- once k
            return (Map.insert k v map, v)
此转换器增加了从已记忆的有效函数中查找值的功能

lookupMemoT :: (Ord k, Monad m) => k -> MemoT k v m v
lookupMemoT k = MemoT . StateT $ \(once, map) -> do
                                                    (map', v) <- lookupInsertM once k map
                                                    return (v, (once, map'))

我们的
MemoT
为每个函数使用
Map
。有些函数可能以其他方式进行记忆。该软件包有一个用于单子的-风格类,为特定函数提供备忘录,还有一个更复杂的机制用于构建单子,不一定使用
Map
s.

您使用的是哪个备忘录库?您的数据类型相当于
type Cellular ga=Cont a g
type CellularT m g a=ContT a m g
CellularT
CellularT
是一个著名的monad和monad转换器(
ContT
),如果您翻转
a
g
参数的顺序。仅仅因为某些东西不是monad转换器并不意味着它不应该有效;有很多与
(*->*)
类似的东西,它们可能位于单子之上,而它们本身不是单子。你可能会喜欢,尽管它不像你的那样“懒洋洋地”填充查找表。@DanielWagner Yep。我今天上午没有时间写的更好的答案是,在memo-trie中为有限的
a
添加
Traversable
实例到
(:->:)a
lookupMemoT :: (Ord k, Monad m) => k -> MemoT k v m v
lookupMemoT k = MemoT . StateT $ \(once, map) -> do
                                                    (map', v) <- lookupInsertM once k map
                                                    return (v, (once, map'))
runMemoT :: (Monad m) => MemoT k v m a -> (k -> m v) -> m a
runMemoT memo once = evalStateT (getMemoT memo) (once, Map.empty)