bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

乱舞するメッセージ(3)

1.帳簿をつける

担当者間でメッセージを送ることが可能になったので、帳簿をつけることにする。Haskellでのプログラムが、他の言語でのプログラムと異なる点は、変数の値を変えられないということである(STMは例外で、値の変更を安全におこなえる仕組みをもたせた)。
帳簿は、それぞれの勘定科目に現在高を示すので、トランザクションがあるたびに現在高を更新しなければならない。これはHaskellの基本的な考え方と根本的に異なる。値の変更をできるようにしたいと切望するであろうが、Haskellの世界ではかなわぬ夢と考えたほうが良い。
どうすれば解決するのか?トランザクションがあるごとに、現在の帳簿から、そのトランザクションを加えた新しい帳簿を作成することである。従って、会計担当者には、トランザクションがあるたびごとに、現在の帳簿とトランザクションの内容を知らせてあげる必要がある。このため、accountantの関数は、引数に帳簿(プログラムではbookになっている)をもつ。そして、accountantが一つのトランザクション処理を終了すると、再帰的にaccountantを呼び出し、新しい帳簿を渡してあげる。
f:id:bitterharvest:20140731070949p:plain

2.プログラム

プログラムは以下に示すようになる。accountantの関数を簡単に説明しておく。最初にprintBookで現在の帳簿の内容をコンソールプロセスに送る(コンソールプロセスはこれを受けて出力する)。次に、購買担当者あるいは営業担当者から伝票(伝票といっても、購入したあるいは販売した商品の個数だけだが)が来ているかを調べる。もし、いずれかからきていれば、それに対する処理を行う。そうでなければ、伝票が来るのを待つ。
f:id:bitterharvest:20140731065042p:plain
購買担当者から伝票が来ていれば、そのトランザクションの内容をコンソールプロセスに送り、さらに、新たな帳簿を作成し、それとともに、accountantを再帰的に呼び出す。
営業担当者から伝票が来た場合でも同じような処理を行う。
なお、コンソールへの出力を確認できるようにするために、購買担当者の購買間隔は5-10秒のランダムな時間に、営業担当者の販売間隔は2.5-5秒のランダムな時間に、変更した。

import Control.Concurrent.CHP
import qualified Control.Concurrent.CHP.Common as CHP
import Control.Concurrent.CHP.Console
import Control.Monad
import Control.Monad.Trans
import System.Random

main :: IO ()
main = do runCHP_(consoleProcess concurrentAgents)

salesPerson :: Chanout Int -> CHP ()
salesPerson chanout = forever $ 
  do writeC
     randomDelay
  where
    writeC = liftIO_CHP (getStdRandom (randomR (1,50))) >>= writeChannel chanout 
    randomDelay = liftIO_CHP (getStdRandom (randomR (2500000,5000000))) >>= waitFor

buyer :: Chanout Int -> CHP ()
buyer chanout = forever $ 
  do writeC
     randomDelay
  where
    writeC = liftIO_CHP (getStdRandom (randomR (51,100))) >>= writeChannel chanout 
    randomDelay = liftIO_CHP (getStdRandom (randomR (5000000,10000000))) >>= waitFor

data Account = Account {cash :: Int, sales :: Int, purchasing :: Int, stock :: Int, goods :: Int}
initBook = Account {cash = 100000, sales = 0, purchasing = 0, stock = 100000, goods = 0}

acountant :: ConsoleChans -> Chanin Int -> Chanin Int -> Account -> CHP ()
acountant chans chaninS chaninB book =
  printBook book >> 
    (readChannel chaninS >>= \y -> outProcessS book y) <-> (readChannel chaninB >>= \y -> outProcessB book y)
  where
    printBook :: Account -> CHP()
    printBook x = do printString ("Cash: " ++ show (cash x)  ++ "\n")
                     printString ("Sales: " ++ show (sales x)  ++ "\n")
                     printString ("Purchasing: " ++ show (purchasing x)  ++ "\n")
                     printString ("Stock: " ++ show (stock x)  ++ "\n")
                     printString ("Goods: " ++ show (goods x)  ++ "\n")
                     printString "\n"
    outProcessS :: Account -> Int -> CHP ()
    outProcessS x y = printString ("New Sales:" ++ show y  ++ "\n") >> 
      acountant chans chaninS chaninB Account {cash = cash x + 100 * y , sales = sales x + 100 * y, purchasing = purchasing x, stock = stock x, goods = goods x - y}
    outProcessB :: Account -> Int -> CHP ()
    outProcessB x y = printString ("New Purchasing:" ++ show y  ++ "\n") >> 
      acountant chans chaninS chaninB Account {cash = cash x - 80 * y , sales = sales x, purchasing = purchasing x + 80 * y, stock = stock x, goods = goods x + y}
    printString = mapM_(writeChannel (cStdout chans))

concurrentAgents :: ConsoleChans -> CHP ()
concurrentAgents
  chans = do s <- newChannel
             b <- newChannel
             runParallel_
               [ salesPerson (writer s)
               , buyer (writer b)
               , acountant chans (reader s) (reader b) initBook]

3.問題

投資家をエージェントの一人として加えなさい。

4.問題

経理担当者が、一分ごとに、財務諸表、棚卸資産を報告するようにしなさい。