Remember effective function

I started working on a project defining a cellular automaton as a local transition function:

newtype Cellular ga = Cellular { delta :: (g -> a) -> a } 

Whenever g is Monoid , you can define a global transition by focusing before applying the local transition. This gives us the following step function:

 step :: Monoid g => Cellular ga -> (g -> a) -> (g -> a) step cell init g = delta cell $ init . (g <>) 

Now we can just start the machine using iterate . And we can save a lot (and I mean a lot: it literally saves hours) of recalculations using memo for each of the steps:

 run :: (Monoid g, Memoizable g) => Cellular ga -> (g -> a) -> [g -> a] run cell = iterate (memo . step cell) 

My problem is that I have generalized Cellular to CelluarT so that I can use side effects in local rules (e.g. copying a random neighbor):

 newtype CellularT mga = Cellular { delta :: (g -> ma) -> ma } 

However, I want the effects to start once so that if you ask the cell several times what its value is, the answers are all consistent. memo fails because it saves efficient calculations, not the result.

I do not expect this to be achieved without using unsafe functions. I tried using it using unsafePerformIO , IORef and Map ga to store the calculated values:

 memoM :: (Ord k, Monad m) => (k -> mv) -> (k -> mv) memoM = let ref = unsafePerformIO (newIORef empty) in ref `seq` loopM ref loopM :: (Monad m, Ord k) => IORef (Map kv) -> (k -> mv) -> (k -> mv) loopM ref fk = let m = unsafePerformIO (readIORef ref) in case Map.lookup km of Just v -> return v Nothing -> do v <- fk let upd = unsafePerformIO (writeIORef ref $ insert kvm) upd `seq` return v 

But it behaves in an unpredictable way: memoM putStrLn correctly remembered, and memoM (\ str -> getLine) continues to extract strings, despite the fact that the same argument is passed to it.

+5
source share
2 answers

This can be done safely if you allow yourself the opportunity to select a link for storing the card.

 import Control.Monad.IO.Class memoM :: (Ord k, MonadIO m) => (k -> mv) -> m (k -> mv) | | | opportunity to allocate the map get to IO correctly 

I am going to use MVar instead of IORef to get the most out of concurrency correctly. This is for correctness if it is used simultaneously, and not for performance. For performance, we can be more attractive than this and use double-check locks or a parallel card with a thinner lock.

 import Control.Concurrent import Control.Monad.IO.Class import qualified Data.Map as Map memoM :: (Ord k, Monad m, MonadIO m) => (k -> mv) -> m (k -> mv) 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)) -> mb inMVar mvar step = do (a, b) <- liftIO (takeMVar mvar) >>= step liftIO $ putMVar mvar a return b lookupInsertM :: (Ord k, Monad m) => (k -> mv) -> k -> Map.Map kv -> m (Map.Map kv, v) lookupInsertM once k map = case Map.lookup k map of Just v -> return (map, v) Nothing -> do v <- once k return (Map.insert kv map, v) 

In fact, we do not use IO , we just bypass the state. Any monad should be able to do this with a transformer applied to it, so why are we kidding in IO ? This is because we want to be able to highlight these cards so memoM can be used for several functions. If we only care about one memoized effective function, we can simply use a state transformer.

 {-# LANGUAGE GeneralizedNewtypeDeriving #-} import Control.Applicative import Control.Monad.Trans.Class import Control.Monad.Trans.State newtype MemoT kvma = MemoT {getMemoT :: StateT (k -> mv, Map.Map kv) ma} deriving (Functor, Applicative, Monad, MonadIO) instance MonadTrans (MemoT kv) where lift = MemoT . lift 

This transformer adds the ability to search for values ​​from a memoized effectful function

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

To run it and get in the main monad, we need to provide the efficient function that we want to memoize.

 runMemoT :: (Monad m) => MemoT kvma -> (k -> mv) -> ma runMemoT memo once = evalStateT (getMemoT memo) (once, Map.empty) 

Our MemoT uses Map for each function. Some features may be seen in a different way. The monad-memo package has an mtl- style class for monads that provide memoization for a specific function, and a more complex mechanism for building them, which does not necessarily use Map s.

+2
source

First, stop trying to use unsafePerformIO. He got that name for some reason.

What you are trying to do is not a memory, but the actual management of the challenges of the inner monad. Part of the clue is that Cellular is not a monad, so CellularT is not a monad transformer.

I think you need to make a pure function that calculates the desired effect for each cell, and then iterates over the cells for a sequence of effects. This separates your cellular autotronic mechanics (which you already have and look good) from your effective mechanics. You are currently trying to execute effects at the same time that they are calculated, which leads to your problems.

Perhaps your effects need to be divided into an input phase and an output phase or something like that. Or perhaps your effects are actually more like a state machine in which each iteration of each cell produces a result and expects a new input. In this case, see my question here for some ideas on how to do this.

0
source

All Articles