Search code examples
haskellmaxmonoidsbounded-typesfoldable

How to compute the maximum of a floating point property of a Foldable via foldMap?


Say I have a map with measurable objects, and I'd like the maximum of their widths, defaulting to 0. The machinery of Foldable (foldMap) and Semigroup (Max) seems perfect, except that I can't seem to introduce the arbitrary lower bound.

data Thing

width :: Thing -> Double

maxWidth :: Map k Thing -> Double
maxWidth things = getMax . foldMap (Max . width) $ things

This rightly complains about the missing Bounded instance for Double, since the Monoid instance of Max a uses mempty = minBound.

I see that the source for Data.Foldable uses a different definition of newtype Max to implement maximum. That variant would do nicely, but it seems to not be exported:

maxWidth things = fromMaybe 0 . getMax . foldMap (Max . Just . width) $ things

Solution

  • You can use the Option monoid to get a monoid instance for any Semigroup. Option a is just a newtype wrapper over Maybe a, but it's Monoid instance only requires Semigroup a rather than Monoid a. So we can use it with Max:

    maximumMaybe :: (Ord a, Foldable t) => t a -> Maybe a
    maximumMaybe = fmap getMax . getOption . foldMap (Option . Just . Max)  
    

    If you want a default value for the empty list case, you can use fromMaybe:

    maximumWithDefault :: (Ord a, Foldable t) => a -> t a -> a 
    maximumWithDefault d = fromMaybe d . maximumMaybe
    

    Another option is to just use maximumMay :: (Ord a, Foldable t) => t a -> Maybe a from the safe package.

    If you are using base-4.11.0 or higher, you no longer need the Option a type since the restriction on Monoid (Maybe a) has been lifted to Semigroup a. So as of base-4.11.0, which came with GHC 8.4.1, you can write:

    maximumMaybe :: (Ord a, Foldable t) => t a -> Maybe a
    maximumMaybe = fmap getMax . foldMap (Just . Max)