I am working with some data that has missing values, which is simply represented as lists of Maybe values. I would like to perform various aggregates/statistical operations, which simply ignore the missing values.
This is related to the following questions:
Idiomatic way to sum a list of Maybe Int in haskell
How to use the maybe monoid and combine values with a custom operation, easily?
However, the former question is content with returning Nothing
if any value is missing, which is not an option in my case. I have a solution which involves creating a Num
instance for Maybe
. However, that means it is specific to addition and multiplication and it has some other problems, too.
instance Num a => Num (Maybe a) where
negate = fmap negate
(+) = liftA2 (+)
(*) = liftA2 (*)
fromInteger = pure . fromInteger
abs = fmap abs
signum = fmap signum
Based on that, we can do something like this:
maybeCombineW :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
maybeCombineW f (Just x) (Just y) = Just (f x y)
maybeCombineW _ (Just x) Nothing = Just x
maybeCombineW _ Nothing (Just y) = Just y
maybeCombineW _ Nothing Nothing = Nothing
maybeCombineS :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
maybeCombineS f (Just x) (Just y) = Just (f x y)
maybeCombineS _ _ _ = Nothing
class (Num a) => Num' a where
(+?) :: a -> a -> a
(*?) :: a -> a -> a
(+!) :: a -> a -> a
(*!) :: a -> a -> a
(+?) = (+)
(*?) = (*)
(+!) = (+)
(*!) = (*)
instance {-# OVERLAPPABLE #-} (Num a) => Num' a
instance {-# OVERLAPPING #-} (Num' a) => Num' (Maybe a) where
(+?) = maybeCombineW (+?)
(*?) = maybeCombineW (*?)
(+!) = maybeCombineS (+!)
(*!) = maybeCombineS (*!)
sum' :: (Num' b, Foldable t) => t b -> b
sum' = foldr (+?) 0
sum'' :: (Num' b, Foldable t) => t b -> b
sum'' = foldr (+!) 0
What I like about this: It gives me two functions, a lenient sum'
and a strict sum''
, from which I can choose as needed. I can use the same functions to sum any Num
instances, so I can reuse the same code for lists without Maybe
without converting them first.
What I don't like about this: The instance overlap. Also, for any operation other than addition and multiplication, I have to specify a new type class and make new instances.
Therefore, I was wondering whether it is somehow possible to get a nice and general solution, perhaps along the lines suggested in the second question, which treats Nothing
as the mempty
for any operation in question.
Is there a nice idiomatic way of doing this?
Edit: Here is the best solution so far:
inout i o = ((fmap o) . getOption) . foldMap (Option . (fmap i))
sum' = Sum `inout` getSum
min' = Min `inout` getMin
-- etc.
There is an instance of Monoid
that does exactly the right thing:
instance Monoid a => Monoid (Maybe a) where
mempty = Nothing
Nothing `mappend` m = m
m `mappend` Nothing = m
Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
It's in Data.Monoid
.
Thus,
foldMap (liftA Sum) [Just 1, Nothing, Just 2, Nothing, Just 3] =
fold [Just (Sum 1), Nothing, Just (Sum 2), Nothing, Just (Sum 3)] =
Just (Sum 6)
For strict left-folding versions, instead of fold
one can use foldl' mappend mempty
, and instead of foldMap f
use foldl' (mappend . f) mempty
. In the Maybe
monoid, mempty
is Nothing
.
How about just using catMaybes
from Data.Maybe
to discard all the Nothing
values? Then you can run any aggregations and calculations on a list of just plain values.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With