Haskellers Meeting 2010 Springで、Simon さんに STM(Software Transaction Memory)の話をして頂くようにお願いしました。参加者の人があらかじめ予習できるように、参考書として Real World Haskell とビューティフルコードを挙げておきました。
Real World Haskell―実戦で学ぶ関数型言語プログラミング
- 作者: Bryan O'Sullivan,John Goerzen,Don Stewart,山下伸夫,伊東勝利,株式会社タイムインターメディア
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/10/26
- メディア: 大型本
- 購入: 8人 クリック: 245回
- この商品を含むブログ (76件) を見る
ビューティフルコード (THEORY/IN/PRACTICE)
- 作者: Brian Kernighan,Jon Bentley,まつもとゆきひろ,Andy Oram,Greg Wilson,久野禎子,久野靖
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/04/23
- メディア: 大型本
- 購入: 30人 クリック: 617回
- この商品を含むブログ (190件) を見る
Real World Haskell の STM の章は読んだことがあったのですが、ビューティフルコードの方はなかったので、よい機会なので読んでみました。Real World HaskellのSTMの説明よりも、ビューティフルコードの方が分かりやすかったです。
ビューティフルコードの方は、例がいかしていて、最終的にSTMでサンタ問題を解きます。
サンタは、休暇から戻った9頭のトナカイたちが全員で起こすか、10人の小人たちのうちの3人がグループになって起こすかするまで、繰り返し眠っています。トナカイたちに起こされたら、9頭全員のトナカイにハーネスをつけてソリの準備をして、おもちゃを配りにいきます。終わったらハーネスをはずし、トナカイたちに休日をやります。小人たちのグループにおこされたら、そのグループの小人たちを書斎に連れて行き、おもちゃの調査や開発の相談をして、方針がまとまったら小人たちは仕事に戻ります。サンタは、小人たちとトナカイたちの両方が同時に起こしに来た場合は、トナカイたちを優先しなければなりません。
#「行く」と「いく」とが揺れているなぁ。。。
理解するために、例題のコードを手で入力してみました。結局、このコードを理解するには、ゲートを2つ用意する必要性が分かるかにかかっています。
コード自体は Simon さんのから入手できるようですが、僕がレイアウトを変えながら入力し、Applicative スタイルに変更したコードを掲載しておきます。
module Main where import Control.Applicative import Control.Concurrent.STM import Control.Concurrent import System.Random ---------------------------------------------------------------- main :: IO () main = do elf_group <- newGroup 3 sequence_ [elf elf_group n | n <- [1..10]] rein_group <- newGroup 9 sequence_ [reindeer rein_group n | n <- [1..9]] forever $ santa elf_group rein_group elf :: Group -> Int -> IO ThreadId elf gp idnt = forkIO . forever $ do elf1 gp idnt randomDelay reindeer :: Group -> Int -> IO ThreadId reindeer gp idnt = forkIO . forever $ do reindeer1 gp idnt randomDelay forever :: IO () -> IO () forever act = do act forever act randomDelay :: IO () randomDelay = do waitTime <- getStdRandom . randomR $ (1, 1000000) threadDelay waitTime santa :: Group -> Group -> IO () santa elf_gp rein_gp = do putStr "----------\n" choose [ (awaitGroup rein_gp, run "deliver toys") , (awaitGroup elf_gp, run "meet in my study") ] where run :: String -> (Gate, Gate) -> IO () run task (in_gate, out_gate) = do putStr $ "Ho! Ho! Ho! let's " ++ task ++ "\n" operateGate in_gate operateGate out_gate choose :: [(STM a, a -> IO ())] -> IO () choose choices = do act <- atomically (foldr1 orElse actions) act where actions :: [STM (IO ())] actions = [ do { val <- guard; return (rhs val) } | (guard, rhs) <- choices ] ---------------------------------------------------------------- data Gate = Gate Int (TVar Int) newGate :: Int -> STM Gate newGate n = Gate n <$> newTVar 0 passGate :: Gate -> IO () passGate (Gate _ tv) = atomically $ do n_left <- readTVar tv check (n_left > 0) writeTVar tv (n_left - 1) operateGate :: Gate -> IO a operateGate (Gate n tv) = do atomically (writeTVar tv n) atomically $ do n_left <- readTVar tv check (n_left == 0) ---------------------------------------------------------------- data Group = Group Int (TVar (Int, Gate, Gate)) newGroup :: Int -> IO Group newGroup n = atomically $ do g1 <- newGate n g2 <- newGate n Group n <$> newTVar (n, g1, g2) joinGroup :: Group -> IO (Gate, Gate) joinGroup (Group _ tv) = atomically $ do (n_left, g1, g2) <- readTVar tv check (n_left > 0) writeTVar tv (n_left - 1, g1, g2) return (g1,g2) awaitGroup :: Group -> STM (Gate, Gate) awaitGroup (Group n tv) = do (n_left, g1, g2) <- readTVar tv check (n_left == 0) new_g1 <- newGate n new_g2 <- newGate n writeTVar tv (n, new_g1, new_g2) return (g1, g2) ---------------------------------------------------------------- elf1 :: Group -> Int -> IO () elf1 gp idnt = helper1 gp (meetInStudy idnt) reindeer1 :: Group -> Int -> IO () reindeer1 gp idnt = helper1 gp (deliverToys idnt) helper1 :: Group -> IO () -> IO () helper1 group do_task = do (in_gate, out_gate) <- joinGroup group passGate in_gate do_task passGate out_gate meetInStudy :: Int -> IO () meetInStudy idnt = putStr $ "Elf " ++ show idnt ++ " meeting in the study\n" deliverToys :: Int -> IO () deliverToys idnt = putStr $ "Reindeer " ++ show idnt ++ " delivering toys\n"