4

I noticed that the Foldable class contains fold, foldl, foldr, foldl', and foldr', but there's no fold' (for strict monoidal folds)

How can I emulate the behavior of fold' with an IntMap (which is implemented as a tree, but doesn't give direct access to the internal nodes).


Motivation:

In particular, if I have an IntMap containing M IntMap's of size K (with total size N = M*K), I'd like to union them in O(N * log(M)) big-O running time. Something like:

unionMaps :: IntMap (IntMap a) -> IntMap a
unionMaps = fold'

This would work because IntMap is an instance of Monoid with mappend defined as union. Note that in general, using foldl' or foldr' is theoretically slower since it requires Omega(N * log N) worst-case running time. Admittedly, this is probably an insignificant difference in practice, but I'm pedantic enough to care about theoretically optimal bounds


Oops, the above is wrong. I went over it more carefully and now I realize it doesn't matter whether you use fold or foldl or foldr, the running time will be in O(N * log(M)). So I no longer have any motivation for this question.

dspyz
  • 5,280
  • 2
  • 25
  • 63
  • 1
    With `IntMap` elements, what would the difference even be between `fold` and a hypothetical `fold'`? `union` is spine-strict. It will perform the entire tree's worth of unions at the same time, when the result is demanded. – Carl Jun 16 '14 at 21:30
  • Oh, I wasn't aware of that. I guess in general I can get the same result if I use a monoid for which mappend is strict in its arguments. Is there an easy way to take a monoid and replace it with an identical one which is strict in its arguments? – dspyz Jun 16 '14 at 21:40
  • Well, I suppose you could write a couple newtype wrappers for forcing their inputs - one for WHNF, the other for NF, for instance. Then `foldMap` would apply the wrappers and do the monoidal combining all at once. – Carl Jun 16 '14 at 23:06
  • Actually, I've been thinking about it and I realize that my running-time analysis only applies if IntMaps are not only height-balanced, but also weight-balanced. I don't think this is guaranteed, so probably the best approach is to extract the values out to a list and then write a smarter unionsWith function. – dspyz Jun 16 '14 at 23:34
  • To be ideal on merge costs, you'd want to use something like the Huffman algorithm. But then you introduce additional bookkeeping that may well outweigh the merge cost savings. Have fun! – Carl Jun 16 '14 at 23:52
  • Nope, I got it wrong again. I'm going over it more carefully and it looks like whether you use foldl or fold, the running time is always O(N log M) – dspyz Jun 17 '14 at 00:40

1 Answers1

3

Since I couldn't find any library with the full Foldable fold', to have an answer to the basic question, I wrote up some code for @Carl's suggestion from the comments above (WHNF only):

{-# LANGUAGE BangPatterns #-}
import Data.Monoid
import Data.Foldable

newtype StrictM m = StrictM { getStrict :: m }

instance Monoid m => Monoid (StrictM m) where
    mempty = StrictM mempty
    mappend (StrictM !x) (StrictM !y) = StrictM (mappend x y)

fold' :: (Foldable t, Monoid m) => t m -> m
fold' = getStrict . foldMap StrictM
Ørjan Johansen
  • 18,119
  • 3
  • 43
  • 53