ちょっと変わったリストのトラバースはunfoldrでできる

昨日作ってみたvarMapなのですが、コメントでData.List.unfoldrの存在を教えてもらいました。

unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
unfoldr f b  =
  case f b of
   Just (a,new_b) -> a : unfoldr f new_b
   Nothing        -> []

ということで、unfoldrの入力はリストである必要はないのです。昨日のvarMapもリストを入力に取るように書いていましたが、aさんが指摘されたように、リストである必要はありません。ということで、unfoldrとvarMapの違いといえば、コンテキストが関数か定数かの違いでしかないように思います。unfoldrのbパラメタを勝手に関数にすることはできるので、コールバック関数であるところのfがbパラメタであるところの関数を呼び出すようになっていれば、varMapとほぼ同等なものがunfoldrで実現できそうな気がします。どうして関数をコンテキストにするの?と聞かれれば、「イヤー、なんとなく」としか答えられないのが現状です。コールバック内での処理が多岐にわたる場合は、関数を分けて、その関数自身を渡せるのが便利だったりしないかなとかすかに思ったりしています...

ということで、昨日の最後のサンプルをunfoldrで書くと:

module Main
	where

import Data.List

takeLess (0, _)  = Nothing
takeLess (a, xs) = Just (x, (a - 1, xs'))
	where
		(x, xs') = splitAt a xs

main :: IO()
main = putStrLn.show $ unfoldr takeLess (5, [1..20])

となります。関数型コンテキスト版で書けば...と、これは、どうやら簡単には書けないようです。ちょっと試してみましたが、あきらめました。再帰をパッケージ化しているunfoldrやmapなどは、じかに再帰を書いた場合よりも型の自由度が低いことが原因のような気がします。

逆に、varMapをやめて、takeLessの中で直に再帰すると:

module Main
	where

takeLess :: Int -> [a] -> [ [a] ]
takeLess 0 _ = [ ]
takeLess _ [ ] = [ ]
takeLess c xs = x : takeLess (c - 1) xs'
	where
		(x, xs') = splitAt c xs
		
main = putStrLn.show $ takeLess 5 [1..20]

ととてもシンプルになります。再帰型宣言ももちろん不要になります。コンテキスト関数、ただ邪魔なだけ?