bitterharvest’s diary

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

Haskellを利用して簡単な会計システムを実現する(3)

1.勘定科目

勘定科目は、資産に関係する科目、負債(資本金を含む)に関係する科目、費用に関係する科目、売上に関係する科目に分けることができる。総勘定元帳などの帳簿をつけるときは、資産に関する科目と費用に関係する科目の現在高は借方に記述し、負債に関する科目と売上に関する科目の現在高は貸方側に記載する(損益計算書は、売上に関する科目を左側(あるいは上側)に、費用に関する科目を右側(あるいは下側)に記載する。このため、混同しやすいので、注意)。
勘定科目の現在高は取引によって増減するが、取引は仕訳伝票に記載される。仕訳伝票で、資産的な科目が借方に記載されているときは、資産の増加を示すために、現在高は取引高分だけ増えることになる。逆に、貸方に記載されているときは、資産の減少を示すため、現在高は取引分だけ減ることになる。
今回対象としている小さな会社での勘定科目の中で、資産的な科目は現金である。このため、現金が借方に記載されているときは現金の増加に、貸方に記載されているときは、現金の減少になる。
負債的な科目は、これとは逆になる。小さな会社の例では、資本金が負債的な科目である。
同様に、(営業上の)費用的な科目が、仕訳伝票の借方に記載されているときは増加し、貸方に記載されているときは減少する。小さな会社での勘定科目の中で費用に関するものは仕入高である。
売上げにかかわる科目が、仕訳伝票で借方に記載されているときは減少を示し、貸方にある時は増加する。小さな会社での売り上げにかかわる科目は売上高である。
小さな会社の勘定科目の中で、仕訳伝票の借方にも貸方にもよく表れるのは現金である。その他の科目が減少することは取引の取消しを除いては起こらないので、減少させるようなことが仕訳伝票に記載されている場合には要注意である。
それでは、Haskellで小さな会社の会計システムに関する関数を定義することを考える。その前に、STMと乱数発生のモジュールをロードしておく。

import System.Random
import Control.Monad
import Control.Concurrent
import Control.Concurrent.STM

2.現金

まず初めに、勘定科目のうち現金をHaskellの関数としてあらわすことを考える。現金の現在高の増減はトランザクション処理である。このため、現金はトランザクションメモリ型ではあるが、勘定科目としての性格を明確にするために、Cashという型シノニムを用意する。これは次のようになる。

type Cash = TVar Int

現金の増加をaddC、減少をsubCという関数であらわすこととする。現金はマイナスになることはないということを前提にすると、関数subCでは、現在高よりも減少させる数の方が大きい時には、処理を中断し、現金が増えるのを待つことにする。現金の値が変わったところで、再試行する。

subC :: Cash -> Int -> STM ()
subC cash withdraw = do amount <- readTVar cash
                        if amount < withdraw
                          then retry
                          else writeTVar cash (amount - withdraw)
                            
addC :: Cash -> Int -> STM ()
addC cash deposit = do amount <- readTVar cash
                       writeTVar cash (amount + deposit)

他の勘定科目は、マイナスになることも許すとすると、各勘定科目の増加、減少については次のようにあらわすことができる。ここで、Salesが売上高、Purchasingが仕入高、Stockが資本金を表すシノニムである

type Sales = TVar Int

subS :: Sales -> Int -> STM ()
subS sales withdraw = do amount <- readTVar sales
                         writeTVar sales (amount - withdraw)
                            
addS :: Sales -> Int -> STM ()
addS sales deposit = do amount <- readTVar sales
                        writeTVar sales (amount + deposit)

newSales :: Int -> IO Sales
newSales init = newTVarIO init

type Purchasing = TVar Int

subP :: Purchasing -> Int -> STM ()
subP purchasing withdraw = do amount <- readTVar purchasing
                              writeTVar purchasing (amount - withdraw)
                            
addP :: Purchasing -> Int -> STM ()
addP purchasing deposit = do amount <- readTVar purchasing
                             writeTVar purchasing (amount + deposit)

newPurchasing :: Int -> IO Purchasing
newPurchasing init = newTVarIO init

type Stock = TVar Int

subT :: Stock -> Int -> STM ()
subT stock withdraw = do amount <- readTVar stock
                         writeTVar stock (amount - withdraw)
                            
addT :: Stock -> Int -> STM ()
addT stock deposit = do amount <- readTVar stock
                        writeTVar stock (amount + deposit)

newStock :: Int -> IO Stock
newStock init = newTVarIO init

3.棚卸

小さな会社は、商品を購入しそれを売っているので、商品の在庫を把握しておく必要がある。商品については、sellとbuyの二つの関数を用意する。在庫が少ないと商品を売ることができない。そこで、sellの関数では、売ろうとする個数が在庫より少ないときは、新たな入荷を待つようにする。それぞれの関数は以下のようになる。

type Product = TVar Int

sell :: Product -> Int -> STM ()
sell product decrease = do amount <- readTVar product
                           if amount < decrease
                             then retry
                             else writeTVar product (amount - decrease)

buy :: Product -> Int -> STM ()
buy product increase = do amount <- readTVar product
                          writeTVar product (amount + increase)