現実的かどうかはさておき、税込み価格の商品と税抜き価格の商品が混在している店を考えてみよう。
会計の都合上、購入金額の合計を計算するときは、一旦すべての単価を税抜きに揃えてから集計し、まとめて税額を計算する必要があるものとする。
合計を計算するコードは下記のようになりそうだ。
このコードを眺めていると、toTaxIncluded と fromTaxIncluded の 引数 taxRate を消したくなってくるだろう。
ほぼ定数のようなものであり、合計の計算とは本質的に関係のない要素であるためだ。
main = do
print $ total 0.08 [(108,True,2),(200,False,1),(324,True,2),(400,False,1)]
toTaxIncluded :: Float -> Float -> Float
toTaxIncluded taxRate price = price * (1.00 + taxRate)
fromTaxIncluded :: Float -> Float -> Float
fromTaxIncluded taxRate tiPrice = tiPrice / (1.00 + taxRate)
total :: Float -> [(Float,Bool,Int)] -> Float
total taxRate xs =
let subTotal = foldl' step 0.0 xs
in toTaxIncluded taxRate subTotal
where
step acc (price,taxin,quan) = acc + (unitPrice taxin price) * (fromIntegral quan)
unitPrice taxin = if taxin
then fromTaxIncluded taxRate
else id
そこでコードを下記のように改善してみよう。taxRate が引数から消えてすっきりした。
Haskell のコードをスクリプトとして使用している場合はこれで十分だろう。
ただ、一旦コンパイルされてしまうと、toTaxIncludedG と fromTaxIncludedG が使用する税率は固定されてしまう。
main = do
print $ totalG [(108,True,2),(200,False,1),(324,True,2),(400,False,1)]
taxRateG :: Float
taxRateG = 0.08
toTaxIncludedG :: Float -> Float
toTaxIncludedG price = price * (1.00 + taxRateG)
fromTaxIncludedG :: Float -> Float
fromTaxIncludedG tiPrice = tiPrice / (1.00 + taxRateG)
totalG :: [(Float,Bool,Int)] -> Float
totalG xs =
let subTotal = foldl' step 0.0 xs
in toTaxIncludedG subTotal
where
step acc (price,taxin,quan) = acc + (unitPrice taxin price) * (fromIntegral quan)
unitPrice taxin = if taxin
then fromTaxIncludedG
else id
そこでReaderモナド登場。Readerモナドを使用すると、手続き型プログラミングでグローバル変数として保持したいような要素を、自然に保持することができる。
runReader関数 の 第2引数で指定した値を、モナド内の任意の関数内で ask 関数を使用して取り出すことができる。
main = do
print $ totalR 0.08 [(108,True,2),(200,False,1),(324,True,2),(400,False,1)]
toTaxIncludedR :: Float -> Reader Float Float
toTaxIncludedR price = do
taxRate <- ask
return $ price * (1.00 + taxRate)
fromTaxIncludedR :: Float -> Reader Float Float
fromTaxIncludedR tiPrice = do
taxRate <- ask
return $ tiPrice / (1.00 + taxRate)
totalR :: Float -> [(Float,Bool,Int)] -> Float
totalR taxRate xs = (`runReader` taxRate) $ do
subTotal <- foldM step 0.0 xs
toTaxIncludedR subTotal
where
step :: Float -> (Float,Bool,Int) -> Reader Float Float
step acc (price,taxin,quan) = do
up <- unitPrice taxin price
return $ acc + up * (fromIntegral quan)
unitPrice taxin = if taxin
then fromTaxIncludedR
else return
全部のせておく。
import Data.List (foldl')
import Control.Monad.Reader (Reader,runReader,ask)
import Control.Monad (foldM)
main = do
print $ total 0.08 [(108,True,2),(200,False,1),(324,True,2),(400,False,1)]
print $ totalG [(108,True,2),(200,False,1),(324,True,2),(400,False,1)]
print $ totalR 0.08 [(108,True,2),(200,False,1),(324,True,2),(400,False,1)]
toTaxIncluded :: Float -> Float -> Float
toTaxIncluded taxRate price = price * (1.00 + taxRate)
fromTaxIncluded :: Float -> Float -> Float
fromTaxIncluded taxRate tiPrice = tiPrice / (1.00 + taxRate)
total :: Float -> [(Float,Bool,Int)] -> Float
total taxRate xs =
let subTotal = foldl' step 0.0 xs
in toTaxIncluded taxRate subTotal
where
step acc (price,taxin,quan) = acc + (unitPrice taxin price) * (fromIntegral quan)
unitPrice taxin = if taxin
then fromTaxIncluded taxRate
else id
taxRateG :: Float
taxRateG = 0.08
toTaxIncludedG :: Float -> Float
toTaxIncludedG price = price * (1.00 + taxRateG)
fromTaxIncludedG :: Float -> Float
fromTaxIncludedG tiPrice = tiPrice / (1.00 + taxRateG)
totalG :: [(Float,Bool,Int)] -> Float
totalG xs =
let subTotal = foldl' step 0.0 xs
in toTaxIncludedG subTotal
where
step acc (price,taxin,quan) = acc + (unitPrice taxin price) * (fromIntegral quan)
unitPrice taxin = if taxin
then fromTaxIncludedG
else id
toTaxIncludedR :: Float -> Reader Float Float
toTaxIncludedR price = do
taxRate <- ask
return $ price * (1.00 + taxRate)
fromTaxIncludedR :: Float -> Reader Float Float
fromTaxIncludedR tiPrice = do
taxRate <- ask
return $ tiPrice / (1.00 + taxRate)
totalR :: Float -> [(Float,Bool,Int)] -> Float
totalR taxRate xs = (`runReader` taxRate) $ do
subTotal <- foldM step 0.0 xs
toTaxIncludedR subTotal
where
step :: Float -> (Float,Bool,Int) -> Reader Float Float
step acc (price,taxin,quan) = do
up <- unitPrice taxin price
return $ acc + up * (fromIntegral quan)
unitPrice taxin = if taxin
then fromTaxIncludedR
else return