foldr で dropWhile を再定義する
Haskell の Prelude に定義されている関数を、高階関数をつかって再定義してみようという「プログラミング Haskell」第 7 章の問題について。
それはともかく、こんなことを考えてみたり、
dropWhile' = \p -> foldl (\xs x -> let xs' = xs ++ [x] in if and . map p $ xs' then xs else xs') []
(これは間違い。 述語 p が成立する要素を取り除く逆 filter になってしまっている)
あるいは、次のように drop に渡す n を計算したりと、いろいろ苦労したんだけれど、どうにもきれいにまとまらなくて悶々としていた。
dropWhile' p xs = drop n xs where n = foldr f 0 (map p xs) f = \b -> if b then succ else const 0(術後は述語の間違い)
しかーし今日! 頭上に電球ピコーン!級の回答を同僚から入手して感動さめやらない状態。
リファインした定義はこんな感じ。
dropWhile' p xs = foldr (\x ys -> if p x then tail ys else xs) [] xs
ためしに p に (<3) を、 xs に [1,2,3,1] を与えて簡約してみよう。
dropWhile' (<3) [1,2,3,1] = { dropWhile' の定義を展開 } (\p xs -> foldr (\x ys -> if p x then tail ys else xs) [] xs) (<3) [1,2,3,1] = { 引数を適用 } foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [1,2,3,1] = { foldr を一回適用 } (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) 1 -- x (foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [2,3,1]) -- ys = { 最初のλ式を引数に適用 } if (<3) 1 then tail (foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [2,3,1]) else [1,2,3,1] = { if (<3) 1 ... を評価 } tail (foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [2,3,1]) = { foldr を再適用 } tail ((\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) 2 -- x (foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [3,1]) -- ys ) = { λ式を引数に適用 } tail (if (<3) 2 -> then tail (foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [3,1]) else [1,2,3,4])) = { if 文を評価 } tail (tail (foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [3,1])) = { foldr を再適用} tail (tail ((\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) 3 -- x (foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [1]) -- ys )) = { λ式を適用 } tail (tail (if (<3) 3 -> then tail (foldr (\x ys -> if (<3) x -> then tail ys else [1,2,3,1]) [] [1]) else [1,2,3,1])) = { if 文を評価 } tail (tail [1,2,3,1]) = { 二番目の tail を適用 } tail [2,3,1] = { 再度 tail を適用 } [3,1]
ということで、たしかに期待した通りに簡約がすすむ。
わたした初期値を無視する fold に、述語に最初のリスト xs をバインドしておくこと、ふたつの点で「その発想はなかった!」 dropWhile 再定義でございました。