Hi Chris,<br><br>Thanks a bunch for the new angle.<br><br>Question & comments:<br><br>* I like the simplicity of using a single TVar whose state reflects the not-computed/computed state of the IVal.<br><br>* I also like the public interface of taking an STM argument (newTIVal(IO)) over returning a sink (newEmptyTIVal(IO)), which came from some non-STM thinking. In fact, maybe 'cached' is a better public interface yet. I'm going to try it out, renaming "cached" to "ival". (Oh yeah, I'm shortening "TIVal" to "IVal".)<br>
<br>* Why tryPutTMVar in place of putTMVar? Perhaps to encourage checking that var hasn't been written?<br><br>* A perhaps prettier version of force:<br><br> force (TIVal tv) = readTVar tv >>= either compute return<br>
where<br> compute wait = do a <- wait<br> writeTVar tv (Right a)<br> return a<br><br>* The Applicative STM instance can be simplified:<br><br> instance Applicative STM where { pure = return; (<*>) = ap }<br>
<br>Cheers, - Conal<br><br><div class="gmail_quote">On Mon, Apr 28, 2008 at 7:40 AM, ChrisK <<a href="mailto:haskell@list.mightyreason.com">haskell@list.mightyreason.com</a>> wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
The garbage collector never gets to collect either the action used to populate the cached value, or the private TMVar used to hold the cached value.<br>
<br>
A better type for TIVal is given below. It is a newtype of a TVal. The contents are either a delayed computation or the previously forced value.<br>
<br>
Thew newTIVal(IO) functions immediately specify the delayed action.<br>
<br>
The newEmptyTIVal(IO) functions create a private TMVar that allows the delayed action to be specified once later. Note the use of tryPutTMVar to return a Bool instead of failing, in the event that the user tries to store more that one action.<br>
<br>
When force is called, the previous action (and any private TMVar) are forgotten. The garbage collector might then be free to collect them.<br>
<br>
-- <br>
Chris<br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); padding-left: 1ex;">
-- By Chris Kuklewicz (April 2008), public domain<br>
module TIVal(TIVal,newTIVal,newTIValIO,force,cached) where<br>
<br>
import Control.Applicative(Applicative(..))<br>
import Control.Concurrent.STM(STM,TVar,newTVar,newTVarIO,readTVar,writeTVar<br>
,TMVar,newEmptyTMVar,newEmptyTMVarIO,tryPutTMVar,readTMVar)<br>
import Control.Monad(Monad(..),join,liftM2)<br>
import System.IO.Unsafe(unsafePerformIO)<br>
<br>
newtype TIVal a = TIVal (TVar (Either (STM a) a))<br>
<br>
-- the non-empty versions take a computation to delay<br>
<br>
newTIVal :: STM a -> STM (TIVal a)<br>
newTIVal = fmap TIVal . newTVar . Left<br>
<br>
newTIValIO :: STM a -> IO (TIVal a)<br>
newTIValIO = fmap TIVal . newTVarIO . Left<br>
<br>
-- The empty versions stage things with a TMVar, note the use of join<br>
-- Plain values 'a' can be stored with (return a)<br>
<br>
newEmptyTIVal :: STM ( TIVal a, STM a -> STM Bool)<br>
newEmptyTIVal = do<br>
private <- newEmptyTMVar<br>
tv <- newTVar (Left (join $ readTMVar private))<br>
return (TIVal tv, tryPutTMVar private)<br>
<br>
newEmptyTIValIO :: IO ( TIVal a, STM a -> STM Bool )<br>
newEmptyTIValIO = do<br>
private <- newEmptyTMVarIO<br>
tv <- newTVarIO (Left (join $ readTMVar private))<br>
return (TIVal tv, tryPutTMVar private)<br>
<br>
-- force will clearly let go of the computation (and any private TMVar)<div class="Ih2E3d"><br>
<br>
force :: TIVal a -> STM a<br></div>
force (TIVal tv) = do<br>
v <- readTVar tv<br>
case v of<br>
Right a -> return a<br>
Left wait -> do a <- wait<br>
writeTVar tv (Right a)<br>
return a<br>
<br>
-- Conal's "cached" function. This is actually safe.<div class="Ih2E3d"><br>
<br>
cached :: STM a -> TIVal a<br></div>
cached = unsafePerformIO . newTIValIO<br>
<br>
-- The instances<div class="Ih2E3d"><br>
<br>
instance Functor TIVal where<br>
f `fmap` tiv = cached (f `fmap` force tiv)<br>
<br>
instance Applicative TIVal where<br>
pure x = cached (pure x)<br>
ivf <*> ivx = cached (force ivf <*> force ivx)<br>
<br>
instance Monad TIVal where<br>
return x = cached (return x)<br>
tiv >>= k = cached (force tiv >>= force . k)<br>
<br></div>
instance Applicative STM where<br>
pure x = return x<br>
ivf <*> ivx = liftM2 ($) ivf ivx <br>
</blockquote>
</blockquote></div><br>