fold/unfoldの練習
from 畳み込みと展開
fold/unfoldに慣れる
Haskellで書く
Foldableなら何でもいいが、ここでは特殊化してListを用いることにする
以下の3つの関数の内のいずれかを使う
foldr
code:hs
foldr :: (a -> b -> b) -> b -> a -> b
foldr f e [] = e
foldr f e (x : xs) = f x (foldr f e xs)
foldl
code:hs
foldl :: (a -> b -> a) -> a -> b -> a
foldl f e [] = e
foldl f e (x:xs) = foldl f (f e x) xs
unfoldr
code:hs
unfoldr :: (a -> Maybe (b,a)) -> a -> b
unfoldr f x = case f x of
Nothing -> []
Just (z,x') -> z : unfoldr f x'
考え方としては、
foldは、リストから値を作る関数
unfoldは、値からリストを作る関数
であるので、再実装したいモノの引数と返り値の型を確認してから、どれを使うのが良いかなというのを考えると良い
また、リストも値なので、上の2つは、
foldは、リストからリストを作る関数
unfoldは、リストからリストを作る関数
とも言えるので、それも考慮に入れると良いかもしれない
問題 (回答はfold/unfoldの練習#609cd3591982700000ccd2f6)
repeat関数の再実装をしよう
型はa -> [a]
使用例は
code:hs
take 10 $ repeat 5 -- 5,5,5,5,5,5,5,5,5,5
iterate関数の再実装をしよう
型は(a -> a) -> a -> [a]
使用例は、
code:hs
take 5 $ iterate (+2) 1 -- 1,3,5,7,9
ヒント
repeatとどこが違うかを考えてみる
見た目的には、repeatの解答とめちゃくちゃ似ている
sumの再実装をしよう
型はsum' :: [Int] -> Int
foldを使わない場合の定義
code:hs
sum' :: Int -> Int
sum' [] = 0
sum' (x : xs) = x + sum' xs
productの再実装をしよう
型はproduct' :: [Int] -> Int
foldを使わない場合の定義
code:hs
product' :: Int -> Int
product' [] = 1
product' (x : xs) = x * product' xs
mapの再実装をしよう
型はmap' :: (a -> b) -> [a] -> [b]
foldを使わない場合の定義
code:hs
map' :: (a -> b) -> a -> b
map' f [] = []
map' f (x:xs) = f x: map' f xs
foldでもunfoldでも定義できるので両方やってみよう
ヒント
foldrを使う
こういうのを見ながらやるとイメージしやすい
code:hs
1 : (2 : (3 : []))) -- 通常のリスト
1 + (2 + (3 + 0))) -- foldr (+) 0 1,2,3
filterの再実装をしよう
型はfilter' :: (a -> Bool) -> [a] -> [a]
foldを使わない場合の定義
code:hs
filter' :: (a -> Bool) -> a -> a
filter' _ [] = []
filter' p (x:xs) = if p x then x : filter p xs else filter p xs
ヒント
foldl, foldrのどちらを使ってもほぼほぼ同じだが、どちらかのほうがややきれいになる
(++)の再実装をしよう
append'という名前で実装するmrsekut.icon
型はappend' :: [a] -> [a] -> [a]
foldを使わない場合の定義
code:hs
append' :: a -> a -> a
append' [] ys = ys
append' (x:xs) ys = x: append' xs ys
concatの再実装をしよう
型はconcat' :: [[a]] -> [a]
foldを使わない場合の定義
code:hs
concat' :: a -> a
concat' [] = []
concat' (x:xs) = x ++ concat' xs
回答例
repeatの再実装
値からリストを作っているのでunfoldrを使う
code:hs
repeat' :: a -> a
repeat' = unfoldr $ \x -> Just (x, x)
iterateの再実装
値からリストを作っているのでunfoldrを使う
code:hs
iterate' :: (a -> a) -> a -> a
iterate' f = unfoldr $ \x -> Just (x, f x)
sumの再実装
code:hs
sum' :: Int -> Int
sum' = foldr (+) 0
productの再実装
code:hs
product' :: Int -> Int
product' = foldr (*) 1
mapの再実装
foldrを使った場合
code:hs
map' :: (a -> b) -> a -> b
map' f = foldr ((:) . f) []
もう少し親切に書いたもの
code:hs
map' :: (a -> b) -> a -> b
map' f xs = foldr' (\x acc -> (f x) : acc) [] xs
逆にわかりにくいか?
昔のmrsekut.iconにとってはこっちの方がわかりやすかったらしい
今のmrsekut.iconにとっては上の方がわかりやすい
unfoldrを使った場合
code:hs
{-# LANGUAGE LambdaCase #-}
map :: (a -> b) -> a -> b
map f = unfoldr $ \case
[] -> Nothing
x:xs -> Just (f x, xs)
filterの再実装
code:hs
filter' :: (a -> Bool) -> a -> a
filter' p = foldr (\x acc -> if p x then x:acc else acc) []
foldlを使った場合
code:hs
filter' p = foldl (\acc x -> if p x then acc else acc++x) []
fold/unfoldの練習#609cd5b61982700000ccd33dからもわかるように、foldrを使ったほうが、consが自然に書けてキレイになる
appendの再実装
code:hs
append' :: a -> a -> a
append' = flip $ foldr (:)
もう少し親切に書いたもの
code:hs
append' :: a -> a -> a
append' xs ys = foldr (:) ys xs
concatの再実装
code:hs
concat' :: a -> a
concat' = foldr (++) []
考え方として、foldr f e xsのeとxsは割と自明に決まるので、悩むとしたらfの部分ぐらいだろうか