現実的かどうかはさておき、税込み価格の商品と税抜き価格の商品が混在している店を考えてみよう。
会計の都合上、購入金額の合計を計算するときは、一旦すべての単価を税抜きに揃えてから集計し、まとめて税額を計算する必要があるものとする。
合計を計算するコードは下記のようになりそうだ。
このコードを眺めていると、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