There was a fellow on #haskell the other day who was apprehensive about learning STM. Should he learn it after learning category theory? We assured him that Haskell's STM was fairly simple, whereas category theory is a dense liberal art that you study to enrich your mind. Someone pointed him to the wiki and he went on his way.
Afterwards, I perused the wiki (again). What struck me was that there was no example there that show you how to convert a plain old bunch of IO routines into STM routines. When I went to make one myself, I realized how brain-dead easy it is, but that there's something missing...
Anyway, here's a chalked up example where someone has to perform multiple time-consuming tasks (preferably in parallel), with the interesting routine in bold:
import Control.Applicative
import Control.Monad
import Control.Concurrent
import Control.Concurrent.STM
import Control.Concurrent.STM.TMVar
import Data.DateTime
main :: IO ()
main =
do putStrLn "Without STM"
t1s <- getCurrentTime
stuff <- withoutSTM
t1e <- getCurrentTime
putStrLn $ show stuff
putStrLn $ "That took " ++ show (diffSeconds t1e t1s) ++ " seconds"
-- this is the routine that actually does stuff
withoutSTM :: IO GroceryStore
withoutSTM =
do a <- getTomatoesCountFromDB
b <- haveFreshBerries
c <- getNameOfCurrentStore
return $ GroceryStore a b c
getTomatoesCountFromDB :: IO Int
getTomatoesCountFromDB =
do milliSleep 1000 -- simulate slow DB read
return 5
haveFreshBerries :: IO Bool
haveFreshBerries =
do milliSleep 1000
return True
getNameOfCurrentStore :: IO String
getNameOfCurrentStore =
do milliSleep 1000
return "Tom's Produce"
data GroceryStore
= GroceryStore
{ numTomatoes :: Int
, freshBerriesInStock :: Bool
, nameOfStore :: String
}
deriving (Eq, Show, Ord)
milliSleep = threadDelay . (*) 1000
So there you have the plain old imperative code.
And here is the same code modified to use STM.
-- getTomatoesCountFromDB, haveFreshBerries, getNameOfCurrentStore are unchanged from before
-- this is the modified routine
withSTM :: IO GroceryStore
withSTM =
do a <- stmFork getTomatoesCountFromDB
b <- stmFork haveFreshBerries
c <- stmFork getNameOfCurrentStore
GroceryStore <$> (stmWait a)
<*> (stmWait b)
<*> (stmWait c)
main :: IO ()
main =
do putStrLn "With STM"
t2s <- getCurrentTime
stuffWithSTM <- withSTM
t2e <- getCurrentTime
putStrLn $ show stuffWithSTM
putStrLn $ "That took " ++ show (diffSeconds t2e t2s) ++ " seconds"
return ()
-- more helpers
stmWait = atomically
stmFork :: IO a -> IO (STM a)
stmFork m =
do tmv <- newEmptyTMVarIO
let m' = m >>= (atomically . putTMVar tmv)
forkIO m'
return $ readTMVar tmv
Now the folks who've been playing with this for a while won't find this particularly remarkable, but I wouldn't have used it a year ago when I was first learning Haskell, so I thought someone should make note of it. There are also two important things to take away from this.
1. You could only do this if imperative routines are treated as values. The pons asinorum that Eric S. Raymond was referring to is not without its benefits.
2. Would an applicative instance for STM be nicer? At the least, we might be able to do code like this instead.
stmWait $ GroceryStore <$> a <*> b <*> c
What would be wrong with the naive implementation of this? e.g.:
(<*>) :: (Functor m, Monad m) => m (a -> b) -> m a -> m b
(<*>) mf ma =
do a <- ma
f <- mf
return $ f a
Again, I'm probably way out of my depth here, so I apologize profusely if this is asinine.
Posted via email from lambdasquirrel's posterous
No comments:
Post a Comment