You were closer than you realized when you said:
but how to get a result like this when input is [1..6]:
([2,3,4], fromList[("bigOne", 1), ("lessFive", 2)])
In other words, you want something that takes a list as an input and returns a list and a map as output:
newtype Filter a = Filter { runFilter :: [a] -> (CountMap, [a]) }
Why not just encode all of your filters directly using the representation you actually wanted:
import Data.List (partition)
import qualified Data.Map as M
import Data.Monoid
newtype CountMap = CountMap (M.Map String Int)
instance Show CountMap where
show (CountMap m) = show m
instance Monoid CountMap where
mempty = CountMap M.empty
mappend (CountMap x) (CountMap y) = CountMap (M.unionWith (+) x y)
filterOn :: String -> (a -> Bool) -> Filter a
filterOn str pred = Filter $ \as ->
let (pass, fail) = partition pred as
in (CountMap (M.singleton str (length fail)), pass)
bigOne :: Filter Int
bigOne = filterOn "bigOne" (> 1)
lessFive :: Filter Int
lessFive = filterOn "lessFive" (< 5)
We're missing one lass piece of the puzzle: how to combine filters. Well, it turns out that our Filter
type is a Monoid
:
instance Monoid (Filter a) where
mempty = Filter (\as -> (mempty, as))
mappend (Filter f) (Filter g) = Filter $ \as0 ->
let (map1, as1) = f as0
(map2, as2) = g as1
in (map1 <> map2, as2)
Experienced readers will recognize that this is just the State
monad in disguise.
This makes it easy to compose filters using (<>)
(i.e. mappend
), and we run them just by unwrapping our Filter
type:
ghci> runFilter (bigOne <> lessFive) [1..6]
(fromList [("bigOne",1),("lessFive",2)],[2,3,4])
This shows how often the best path is the most direct one!