コーヒーメニューの価格改訂リストの作成で書いた再帰関数をリファクタリングします。
リファクタリング対象の関数は addMergedItems で、 MergedItem のリストを再帰的に Worksheet に追記していく処理です。
addMergedItems :: Worksheet -> [MergedItem] -> RowIndex -> Worksheet
addMergedItems sheet mergedItems rowIndex
| (length mergedItems) == 0 = sheet
| otherwise = addMergedItems sheet' (tail mergedItems) (rowIndex + 1)
where
sheet' =
addCellValues
sheet
[(name mergedItem), (oldPrice mergedItem), (newPrice mergedItem)]
rowIndex
mergedItem = head mergedItems
name :: MergedItem -> CellValue
name (MergedItem v _ _) = v
oldPrice :: MergedItem -> CellValue
oldPrice (MergedItem _ v _) = v
newPrice :: MergedItem -> CellValue
newPrice (MergedItem _ _ v) = v
処理内容は以下です。
再帰するたびに変化するのは sheet と rowIndex です。 そして、MergedItem リストを先頭から順に処理していく、流れです。
foldl とか foldr を使ってこの処理をコードしてみます。
> :type foldl
(a -> b -> a) -> a -> [b] -> a
実装:
addMergedItems :: Worksheet -> [MergedItem] -> RowIndex -> Worksheet
addMergedItems sheet mergedItems rowIndex = fst results
where
results = foldl f (sheet, rowIndex) mergedItems
f :: (Worksheet, RowIndex) -> MergedItem -> (Worksheet, RowIndex)
f sheetAndRowIndex mergedItem = (nextSheet, nextRowIndex)
where
nowSheet = fst sheetAndRowIndex
nowRowIndex = snd sheetAndRowIndex
nextSheet = addCellValues nowSheet cellValues nowRowIndex
nextRowIndex = (snd sheetAndRowIndex) + 1
cellValues =
[(name mergedItem), (oldPrice mergedItem), (newPrice mergedItem)]
name :: MergedItem -> CellValue
name (MergedItem v _ _) = v
oldPrice :: MergedItem -> CellValue
oldPrice (MergedItem _ v _) = v
newPrice :: MergedItem -> CellValue
newPrice (MergedItem _ _ v) = v
ポイント:
results = foldl f (sheet, rowIndex) mergedItems
f :: (Worksheet, RowIndex) -> MergedItem -> (Worksheet, RowIndex)
f sheetAndRowIndex mergedItem = (nextSheet, nextRowIndex)
> type: foldr
(a -> b -> b) -> b -> [a] -> b
実装:
addMergedItems' :: Worksheet -> [MergedItem] -> RowIndex -> Worksheet
addMergedItems' sheet mergedItems rowIndex = fst results
where
results = foldr f (sheet, rowIndex) mergedItems
f :: MergedItem -> (Worksheet, RowIndex) -> (Worksheet, RowIndex)
f mergedItem sheetAndRowIndex = (nextSheet, nextRowIndex)
where
nowSheet = fst sheetAndRowIndex
nowRowIndex = snd sheetAndRowIndex
nextSheet = addCellValues nowSheet cellValues nowRowIndex
nextRowIndex = (snd sheetAndRowIndex) + 1
cellValues =
[(name mergedItem), (oldPrice mergedItem), (newPrice mergedItem)]
name :: MergedItem -> CellValue
name (MergedItem v _ _) = v
oldPrice :: MergedItem -> CellValue
oldPrice (MergedItem _ v _) = v
newPrice :: MergedItem -> CellValue
newPrice (MergedItem _ _ v) = v
ポイント:
results = foldr f (sheet, rowIndex) mergedItems
f :: MergedItem -> (Worksheet, RowIndex) -> (Worksheet, RowIndex)
f mergedItem sheetAndRowIndex = (nextSheet, nextRowIndex)
リストを右から処理するので、foldl と出力される順が上下逆になります。 コーヒーメニューアイテムの処理では出力順は問題になりませんが、もし foldl と同じにしたいのであれば、 [MergedItem] を reverse してから foldr すればよい。
foldl や foldr を使うことで、独自に再帰関数を書かなくても済む。 foldl や foldr で繰り返しがおきるときに何が変化して、何を蓄積していけばよいのかを見極める必要がある。