Re: [Haskell-cafe] Re: Caching the Result of a Transaction?

2008-04-29 Thread Jake Mcarthur
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?

2008-04-29 Thread Jake Mcarthur
*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?

2008-04-29 Thread Jake Mcarthur

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?

2008-04-28 Thread Conal Elliott
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?

2008-04-28 Thread Jake Mcarthur

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?

2008-04-28 Thread Ryan Ingram
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