3
\$\begingroup\$

My Haskell set/map/list folds often become terse and difficult to read. I'm looking for tips on how to make my functional code easier to follow.

I'm working around a bug/feature in a plotting library. I have a map from tags to lists of (time,count) which I plot as a stacked graph:

import           Data.Time (UTCTime)
import qualified Data.Map as M
import qualified Data.Set as S

type CountMap = M.Map String [(UTCTime, Int)]

-- Data used for plotting data series 'foo' and 'bar'
test :: CountMap
test = 
  M.fromList [ ("foo", [(read "2012-09-28 12:00:00", 3), (read "2012-09-29 12:00:00", 4)])
             , ("bar", [(read "2012-09-28 12:00:00", 3)])
             ]

You will note that the above "bar" series is missing a sample for 2012-09-29. I need to fill these gaps with zeros before I pass the data to the plotting library.

The same data with the gaps closed becomes:

fromList [ ("bar",[(2012-09-28 12:00:00 UTC,3),(2012-09-29 12:00:00 UTC,0)])
         , ("foo",[(2012-09-28 12:00:00 UTC,3),(2012-09-29 12:00:00 UTC,4)])]

The code I use for filling the gaps with zero samples is as follows:

import           Data.Time (UTCTime)
import qualified Data.Map as M
import qualified Data.Set as S

-- Insert zero counts into date buckets that are missing a sample.
substZeroCount :: CountMap -> CountMap
substZeroCount m =
  M.map zeros m
  where
    allDates = M.fold (flip (foldr (\(date,_) -> S.insert date))) S.empty m

    zeros cs = M.toList $ S.fold insertMissing (M.fromList cs) allDates

    insertMissing date acc = if M.member date acc then acc else M.insert date 0 acc

It works and it's not even too long. But somehow I don't feel happy about its readability. It looks somehow too terse. Maybe it's just about code layout.. or perhaps there'd be a nicer way to compose these functions. Or maybe it's just because a lot happens in 3 lines of code. ;)

Any suggestions on how to make substZeroCount easier on the eyes?

\$\endgroup\$
2
\$\begingroup\$

I think you should keep CountMap as Map String (Map UTCTime Int). That is a better way of keeping counts. If you do that then you can use mysc function directly. I find mysc much more readable and clear.

import           Data.Time (UTCTime)
import qualified Data.Map as M

type CountMap = M.Map String [(UTCTime,Int)]

-- Data used for plotting data series 'foo' and 'bar'
test :: CountMap
test =
  M.fromList [ ("foo", [(read "2012-09-28 12:00:00", 3), (read "2012-09-29 12:00:00", 4)])
             , ("bar", [(read "2012-09-28 12:00:00", 3)])
             ]

susetCount :: CountMap -> CountMap
susetCount = M.map M.toList . mysc . M.map M.fromList
mysc m =  M.map (flip M.union allelems) m
 where
    -- Find all possible UTCTime that can exist. 
    allelems = M.map (const 0) $ M.foldr' M.union M.empty m
\$\endgroup\$
  • \$\begingroup\$ Nice! You probably mean M.fold instead of M.foldr'? \$\endgroup\$ – Nurpax Sep 29 '12 at 10:32
  • \$\begingroup\$ Nurpax: Yeah you can use M.fold with some changes but this will also work . \$\endgroup\$ – Satvik Sep 29 '12 at 10:38
  • \$\begingroup\$ At least my Data.Map doesn't have a foldr', only fold, hence the question. Replacing M.foldr' above with M.fold works without any other changes. \$\endgroup\$ – Nurpax Sep 29 '12 at 10:47
  • \$\begingroup\$ @Nurpax You might have been using older version of containers. It now has that. hackage.haskell.org/packages/archive/containers/0.4.2.1/doc/… \$\endgroup\$ – Satvik Sep 29 '12 at 11:06
  • \$\begingroup\$ Gotcha, I was running an old HP. Thanks again. \$\endgroup\$ – Nurpax Sep 29 '12 at 11:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.