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"