Re: [Haskell-cafe] Re: Caching the Result of a Transaction?
Alright, I have tested it now. I still feel funny about most of the names I chose for the types and functions, and it's still very ugly, but the code appears to work correctly. In this version I have also added "retry" and "orElse" functions so that it can feel more like the STM monad. I think the biggest downside to this monad is the potential confusion about whether to use "could" or "must," but I have a feeling that better naming choices would reduce the ambiguity. Thoughts? module CachedSTM where import Control.Applicative import Control.Concurrent.STM as S import Control.Monad data CachedSTM a = CSTM { getMust :: STM (), getCould :: STM a } instance Functor CachedSTM where f `fmap` (CSTM m s) = CSTM m $ f <$> s joinCSTM :: CachedSTM (CachedSTM a) -> CachedSTM a joinCSTM cstm = CSTM m s where m = do cstm' <- getCould cstm getMust cstm' `S.orElse` return () getMust cstm `S.orElse` return () s = getCould =<< getCould cstm instance Applicative CachedSTM where pure = return (<*>) = ap instance Monad CachedSTM where return = CSTM (return ()) . return x >>= f = joinCSTM $ f <$> x maybeAtomicallyC :: CachedSTM a -> IO (Maybe a) maybeAtomicallyC cstm = atomically $ do getMust cstm liftM Just (getCould cstm) `S.orElse` return Nothing could :: STM a -> CachedSTM a could stm = CSTM (return ()) stm must :: STM () -> CachedSTM () must stm = CSTM (stm `S.orElse` return ()) $ return () retry :: CachedSTM a retry = could S.retry orElse :: CachedSTM a -> CachedSTM a -> CachedSTM a orElse a b = do must $ getMust a temp <- could newEmptyTMVar must $ (getCould a >>= putTMVar temp) `S.orElse` getMust b could $ takeTMVar temp `S.orElse` getCould b I don't think the IVar code has changed (no version control for this), but here it is again for quick reference: module IVal where import CachedSTM import Control.Applicative import Control.Concurrent.STM import Control.Monad import System.IO.Unsafe newtype IVal a = IVal (TVar (Either (CachedSTM a) a)) newIVal :: CachedSTM a -> CachedSTM (IVal a) newIVal = fmap IVal . could . newTVar . Left newIValIO :: CachedSTM a -> IO (IVal a) newIValIO = fmap IVal . newTVarIO . Left cached :: CachedSTM a -> IVal a cached = unsafePerformIO . newIValIO force :: IVal a -> CachedSTM a force (IVal tv) = could (readTVar tv) >>= either compute return where compute wait = do x <- wait must . writeTVar tv $ Right x return x instance Functor IVal where f `fmap` x = cached $ f <$> force x instance Applicative IVal where pure = return (<*>) = ap instance Monad IVal where return = cached . return x >>= f = cached (force x >>= force . f) - Jake___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Re: Caching the Result of a Transaction?
*sigh* As is usual with my untested code, the code I just sent was wrong. I will be able to actually test, correct, and refine it tonight. If nobody else has picked it up by then I will do so. - Jake ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Re: Caching the Result of a Transaction?
On Apr 28, 2008, at 10:01 PM, Ryan Ingram wrote: The problem I have with all of these STM-based solutions to this problem is that they don't actually cache until the action fully executes successfully. I just hacked together a new monad that I think might solve this, at least with a little extra work. I haven't tested it yet though because I have to do some studying now. I just want to go ahead and put it up for review and see if you guys think this is a good approach. To use it you use the "could" and "must" functions to specify which STM actions may be rolled back and which ones must be permanent. When you apply maybeAtomicallyC to a CachedSTM action, all the "must" actions are performed individually, where any that fail do not affect any of the others. Once the "must" actions are done, the "could" actions are performed, returning Just the result. If that fails then the whole thing simply returns Nothing, but the "must" actions are still committed. At least, I _hope_ the above is what it actually does! module CachedSTM where import Control.Applicative import Control.Concurrent.STM import Control.Monad data CachedSTM a = CSTM { getMust :: STM (), getShould :: STM a } instance Functor CachedSTM where f `fmap` (CSTM m s) = CSTM m $ f <$> s joinCSTM :: CachedSTM (CachedSTM a) -> CachedSTM a joinCSTM cstm = CSTM m s where m = do cstm' <- getShould cstm getMust cstm' `orElse` return () getMust cstm `orElse` return () s = getShould =<< getShould cstm instance Applicative CachedSTM where pure = return (<*>) = ap instance Monad CachedSTM where return = CSTM (return ()) . return x >>= f = joinCSTM $ f <$> x maybeAtomicallyC :: CachedSTM a -> IO (Maybe a) maybeAtomicallyC cstm = atomically $ do getMust cstm liftM Just (getShould cstm) `orElse` return Nothing could :: STM a -> CachedSTM a could stm = CSTM (return ()) stm must :: STM () -> CachedSTM () must stm = CSTM stm $ return () Now the IVal stuff might look something like: module IVal where import CachedSTM import Control.Applicative import Control.Concurrent.STM import Control.Monad import System.IO.Unsafe newtype IVal a = IVal (TVar (Either (CachedSTM a) a)) newIVal :: CachedSTM a -> CachedSTM (IVal a) newIVal = fmap IVal . could . newTVar . Left newIValIO :: CachedSTM a -> IO (IVal a) newIValIO = fmap IVal . newTVarIO . Left cached :: CachedSTM a -> IVal a cached = unsafePerformIO . newIValIO force :: IVal a -> CachedSTM a force (IVal tv) = could (readTVar tv) >>= either compute return where compute wait = do x <- wait must . writeTVar tv $ Right x return x instance Functor IVal where f `fmap` x = cached $ f <$> force x instance Applicative IVal where pure = return (<*>) = ap instance Monad IVal where return = cached . return x >>= f = cached (force x >>= force . f) - Jake ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Re: Caching the Result of a Transaction?
Thanks, Ryan, for the reminder and explanation of this problem. - Conal On Mon, Apr 28, 2008 at 8:01 PM, Ryan Ingram <[EMAIL PROTECTED]> wrote: > The problem I have with all of these STM-based solutions to this > problem is that they don't actually cache until the action fully > executes successfully. > > For example, if you have a :: TIVal a, and f :: a -> TIVal b, and you > execute > force (a >>= f) > > and the action returned by f executes retry for whatever reason, then > the caching done in "a" gets undone. Ideally I want to be able to > provide some proof that the result of a is pure and have it committed > immediately when it finishes. > > Every attempt I've had so far to solve this problem ends up being some > type of the form > newtype X a = IO (STM (Either a (X a))) > which has its own problems. > > -- ryan > ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Re: Caching the Result of a Transaction?
On Apr 28, 2008, at 10:01 PM, Ryan Ingram wrote: [...] if you have a :: TIVal a, and f :: a -> TIVal b, and you execute force (a >>= f) and the action returned by f executes retry for whatever reason, then the caching done in "a" gets undone. Dangit, you're right. You just rained on the parade! Hmm... Perhaps we could use the data structure Chris proposed and mix it with my first approach involving forkIO. Another thread can perform the caching in a separate transaction _or_ the main thread can perform the caching itself in case the extra thread doesn't get a chance first. This would ensure that it manages to get cached _sometime_ even when the main transaction itself retries, but it loses some of the elegance we had gained by not forking a new thread and still doesn't _guarantee_ that the computations are run only once. This is not ideal. :( - Jake ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Re: Caching the Result of a Transaction?
The problem I have with all of these STM-based solutions to this problem is that they don't actually cache until the action fully executes successfully. For example, if you have a :: TIVal a, and f :: a -> TIVal b, and you execute force (a >>= f) and the action returned by f executes retry for whatever reason, then the caching done in "a" gets undone. Ideally I want to be able to provide some proof that the result of a is pure and have it committed immediately when it finishes. Every attempt I've had so far to solve this problem ends up being some type of the form newtype X a = IO (STM (Either a (X a))) which has its own problems. -- ryan ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe