fold/unfoldの練習
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は、リストからリストを作る関数
とも言えるので、それも考慮に入れると良いかもしれない
型はa -> [a]
使用例は
code:hs
型は(a -> a) -> a -> [a]
使用例は、
code:hs
ヒント
repeatとどこが違うかを考えてみる
見た目的には、repeatの解答とめちゃくちゃ似ている
sumの再実装をしよう
型はsum' :: [Int] -> Int
foldを使わない場合の定義
code:hs
sum' [] = 0
sum' (x : xs) = x + sum' xs
productの再実装をしよう
型はproduct' :: [Int] -> Int
foldを使わない場合の定義
code:hs
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' [] ys = ys
append' (x:xs) ys = x: append' xs ys
concatの再実装をしよう
型はconcat' :: [[a]] -> [a]
foldを使わない場合の定義
code:hs
concat' [] = []
concat' (x:xs) = x ++ concat' xs
回答例
repeatの再実装
値からリストを作っているのでunfoldrを使う
code:hs
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' = foldr (+) 0
productの再実装
code:hs
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) [] appendの再実装
code:hs
append' = flip $ foldr (:)
もう少し親切に書いたもの
code:hs
append' xs ys = foldr (:) ys xs
concatの再実装
code:hs
concat' = foldr (++) []
考え方として、foldr f e xsのeとxsは割と自明に決まるので、悩むとしたらfの部分ぐらいだろうか