foldr のすごさを体験してみた

Real World Haskell にはこんなコードが載ってます。

myFoldl :: (a -> b -> a) -> a -> [b] -> a
myFoldl f z xs = foldr step id xs z
  where step x g a = g (f a x)
Real World Haskell p100 4.6.8 右からの畳み込み

foldl は foldr を使って書けるという例です。
こんな風にほとんどのリスト処理は再帰を使わなくても foldr を使えば書けるらしい*1ので、どこまでできるのか確認するために Prelude にある Data.List の関数を再定義してみました。


ルールは、

  • 再定義した関数は直接的または間接的に再帰していてはならない(foldr は除く)
  • 再帰しないで作った関数を他の関数で使うのは OK
  • 再定義した関数は元の関数と同じ計算量で同じ結果を返さなければならない

という感じです。
こんな風になりました。

import Prelude hiding (
  -- List operations
  map,(++),filter,head,last,tail,init,null,length,(!!),reverse,
  ---- Reducing lists (folds)
  foldl,foldl1,{-foldr,-}foldr1,
  ------ Special folds
  and,or,any,all,sum,product,concat,concatMap,maximum,minimum,
  ---- Building lists
  ------ Scans
  scanl,scanl1,scanr,scanr1,
  ---- Infinite lists
  {-iterate,-}repeat,replicate,cycle,
  ---- Sublists
  take,drop,splitAt,takeWhile,dropWhile,span,break,
  ---- Searching lists
  elem,notElem,lookup,
  ---- Zipping and unzipping lists
  zip,zip3,zipWith,zipWith3,unzip,unzip3,
  ---- Functions on strings
  lines,words,unlines,unwords)
import Data.Maybe (fromJust)

-- List operations
map :: (a -> b) -> [a] -> [b]
map f = foldr ((:).f) []

(++) :: [a] -> [a] -> [a]
(++) = flip (foldr (:))

filter :: (a -> Bool) -> [a] -> [a]
filter p = foldr f []
  where f x xs | p x       = x:xs
               | otherwise = xs

head :: [a] -> a
head (x:_) = x

last :: [a] -> a
last = foldr1 (flip const)

tail :: [a] -> [a]
tail (_:xs) = xs

init :: [a] -> [a]
init (x:xs) = foldr f (const []) xs x
  where f x g = (:g x)

null :: [a] -> Bool
null [] = True
null _  = False

length :: [a] -> Int
length = foldr (curry ((+1).snd)) 0

(!!) :: [a] -> Int -> a
(!!) xs n | n < 0     = error "out of index"
          | otherwise = foldr f (error "out of index") xs n
            where f x g 0 = x
                  f x g n = g (n-1)

reverse :: [a] -> [a]
reverse = foldl (flip (:)) []

---- Reducing lists (folds)
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f init xs = foldr step id xs init
  where step x g a = g (f a x)

foldl1 :: (a -> a -> a) -> [a] -> a
foldl1 f (x:xs) = foldl f x xs

--foldr :: (a -> b -> b) -> b -> [a] -> b

foldr1 :: (a -> a -> a) -> [a] -> a
foldr1 f (x:xs) = foldr step id xs x
  where step x g = flip f (g x)

------ Special folds
and :: [Bool] -> Bool
and = foldr (&&) False

or :: [Bool] -> Bool
or = foldr (||) False

any :: (a -> Bool) -> [a] -> Bool
any p = foldr ((||).p) False

all :: (a -> Bool) -> [a] -> Bool
all p = foldr ((&&).p) True

sum :: Num a => [a] -> a
sum = foldr (+) 0

product :: Num a => [a] -> a
product = foldr (*) 1

concat :: [[a]] -> [a]
concat = foldr (++) []

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f = foldr ((++).f) []

maximum :: Ord a => [a] -> a
maximum = foldr1 max

minimum :: Ord a => [a] -> a
minimum = foldr1 min

---- Building lists
------ Scans
scanl :: (a -> b -> a) -> a -> [b] -> [a]
scanl f x xs = foldr step (:[]) xs x
  where step x g a = a:g (f a x)

scanl1 :: (a -> a -> a) -> [a] -> [a]
scanl1 f (x:xs) = scanl f x xs

scanr :: (a -> b -> b) -> b -> [a] -> [b]
scanr f x xs = foldr step (:[]) xs x
  where step x g a = f x a:g a

scanr1 :: (a -> a -> a) -> [a] -> [a]
scanr1 f (x:xs) = foldr step (:[]) xs x
  where step x g a = f a x:g x

---- Infinite lists
--iterate :: (a -> a) -> a -> [a]

repeat :: a -> [a]
repeat = iterate id

replicate :: Int -> a -> [a]
replicate n = take n . repeat

cycle :: [a] -> [a]
cycle = concat . repeat

---- Sublists
take :: Int -> [a] -> [a]
take n xs = foldr f (const []) xs n
  where f x g 0 = []
        f x g n = x:g (n-1)

drop :: Int -> [a] -> [a]
drop n xs = foldr f (const []) xs n
  where f x g 0 = x:g 0
        f x g n = g (n-1)

splitAt :: Int -> [a] -> ([a],[a])
splitAt n xs = (take n xs,drop n xs)

takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p = foldr f []
  where f x xs | p x       = x:xs
               | otherwise = []

dropWhile :: (a -> Bool) -> [a] -> [a]
dropWhile p xs = foldr f (const []) xs True
  where f x g True  | p x       = g True
                    | otherwise = x:g False
        f x g False             = x:g False

span :: (a -> Bool) -> [a] -> ([a],[a])
span p xs = (takeWhile p xs,dropWhile p xs)

break :: (a -> Bool) -> [a] -> ([a],[a])
break p = span (not . p)

---- Searching lists
elem :: Eq a => a -> [a] -> Bool
elem x = any (==x)

notElem :: Eq a => a -> [a] -> Bool
notElem x = not . elem x

lookup :: Eq a => a -> [(a, b)] -> Maybe b
lookup x = foldr f Nothing
  where f (a,b) mb | x==a      = Just b
                   | otherwise = mb

---- Zipping and unzipping lists
zip :: [a] -> [b] -> [(a,b)]
zip = zipWith (,)

zip3 :: [a] -> [b] -> [c] -> [(a,b,c)]
zip3 = zipWith3 (,,)

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f = foldr g (const [])
  where g x h []     = []
        g x h (y:ys) = f x y:h ys

zipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]
zipWith3 f = foldr g (\_ _->[])
  where g x h []     _      = []
        g x h _      []     = []
        g x h (y:ys) (z:zs) = f x y z:h ys zs

unzip :: [(a, b)] -> ([a], [b])
unzip = foldr f ([],[])
  where f (x,y) ~(xs,ys) = (x:xs,y:ys)

unzip3 :: [(a, b, c)] -> ([a], [b], [c])
unzip3 = foldr f ([],[],[])
  where f (x,y,z) ~(xs,ys,zs) = (x:xs,y:ys,z:zs)

---- Functions on strings
lines :: String -> [String]
lines str = snd $ foldr f first str '\n'
  where f c g = uncurry second (g c)
        first '\n' = ("",[])
        first c    = ([c],[])
        second str strs '\n' = ("",str:strs)
        second str strs c    = (c:str,strs)

words :: String -> [String]
words str = snd $ foldr f (g "" []) str ' '
  where f c g' = uncurry g (g' c)
        g str strs ' ' | null str  = ("",strs)
                       | otherwise = ("",str:strs)
        g str strs c               = (c:str,strs)

unlines :: [String] -> String
unlines = concatMap (++"\n")

unwords :: [String] -> String
unwords [] = []
unwords xs = foldr1 f xs
  where f x y = x++(' ':y)

foldr 以外は再帰しないとか言ってましたが、 iterate 関数は無限の入力が無いと無理なので諦めました。
あと計算量についても、微妙に無駄があったり*2とかしてうぬぬという感じですが、とりあえず無限リストに対しても無駄なサンクを作ったりすることなく同じ動作をするようには心がけました。


iterate 関数と、それを使って実装している repeat, replicate, cycle 関数、それから head といった元々リスト処理が不要な関数は foldr では書けません(書きません)でしたが、それ以外は全部 foldr あるいは foldr を間接的に利用している関数を使って書くことができました。
zip あたりとか無理じゃね?と思っていたのですが、id:uskz 先生に教えてもらいつつ何とか実装できました。foldr すごいです。


個人的には scan の実装がいい感じにできたかなと思います。
逆に words や lines はもっと改善できそうな気がするのですが、うまい方法が思いつかなかったです。

*1:こういう関数を原始帰納的関数とか言うらしい

*2:例えば dropWhile とか p x が False になった後も毎回パターンマッチしてて無駄だとか