4

I have an input list of type [Maybe SomeType] and a predicate p of type SomeType -> Bool, and I want to answer the question "Does the predicate p hold for all SomeTypes that happen to be in the input?".

The first part is easy: (map . fmap) p list is of type [Maybe Bool].

One important info is that I know that length list >= 1 and all isNothing list == False both hold, so there must be at least a Just True in (map . fmap) p list.

But how do I pull out one single Bool out of that list?

I thought that I could take advantage of folding (e.g. via foldl) and Maybe's MonadPlus instance, doing something like the following:

allTrueOrNothing :: [Maybe Bool] -> Bool
allTrueOrNothing = fromJust . foldl mplus mzero

but this is not quite true, because mplus returns the left operand if it's Just something regardless of what something is, so allTrueOrNothing will return True even if its input is [Just True, Just False].

What's the cleanest/most idiomatic way I can accomplish the task?

I see that I could simply filter out the Nothings and then and together the Justs, something like this:

allTrueOrNothing' :: [Maybe Bool] -> Bool
allTrueOrNothing' = all fromJust . filter (fmap not isNothing)

But I was more curious to know if there's a way to have those Maybe Bools behave like a Monoid aware of its Bool content.

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • 1
    Can you get ride of the `Nothing` values *before* you map `p`? `all p . catMaybes`. Since `all p [] == True`, it doesn't matter if there's at least one non-`Nothing` value in the original input. – chepner Sep 13 '21 at 15:23

3 Answers3

11

I would just use all directly:

all . all :: (a -> Bool) -> [Maybe a] -> Bool

If you must have the phase distinction you describe for some reason, then you can use the specialization and = all id:

all and :: [Maybe Bool] -> Bool
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • 4
    It took me a second to realize that this works becuse `[Maybe a]` is of the form `f (g a)` where `f` and `g` are both foldables. Clever (perhaps a bit too much ;-), but I like it). – chi Sep 13 '21 at 15:45
  • It helps to be explicit about type argument `all @[] . all @Maybe` and `all @[] $ and @Maybe` – Iceland_jack Sep 13 '21 at 22:11
  • 1
    Do `Foldable`s compose in general? – chepner Sep 14 '21 at 11:34
  • 1
    @chepner Yes, via `foldMap = foldMap . foldMap`. – Daniel Wagner Sep 14 '21 at 14:10
  • 1
    @DanielWagner It typechecks, sure, but I wasn't sure if there was some law that could be violated depending on the monoid. – chepner Sep 14 '21 at 14:12
  • 1
    @chepner The `Foldable` laws aren't very interesting: if you define only `foldMap`, all of the laws are satisfied by the default implementations of the other methods, because the laws only relate `Foldable` methods to each other. – Daniel Wagner Sep 14 '21 at 14:27
5

This seems to work:

> and . catMaybes $ [Just False, Nothing, Just False]
False
> and . catMaybes $ [Just False, Nothing, Just True]
False
> and . catMaybes $ [Just True, Nothing, Just True]
True

You can use catMaybes to convert the list to [Bool], and and to conclude.

(Note that this will return True on an all-Nothings list, which is an "impossible" case according to your assumptions.)

If you absolutely want to use a monoid, I guess you can do that, but it's a bit cumbersome. It would involve to wrap each element of the list in some newtype And = And (Maybe Bool), then defining the relevant monoid instance, then mconcating everying, and finally unwrapping.

Untested code:

newtype And = And (Maybe Bool)

instance Semigroup And where
   And Nothing  <> x            = x
   x            <> And Nothing  = x
   And (Just a) <> And (Just b) = And (Just (a && b))

instance Monoid And where
   mempty = Nothing

allTrueOrNothing :: [Maybe Bool] -> Bool
allTrueOrNothing = fromMaybe False . coerce . mconcat @And . coerce
chi
  • 111,837
  • 3
  • 133
  • 218
1

The cleanest way is and . catMaybes.

But you wanted to use Monoid that is aware of its Bool content, in the && kind of way. That's All:

> foldMap (fmap All) [Just True,Nothing,Just False]
Just (All {getAll = False})

> foldMap (fmap All) [Just True,Nothing,Just True]
Just (All {getAll = True})
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Accepting this as it addresses the question more closely, imho, despite the answer leveraging the `Foldable` instance of `Maybe` is great too. – Enlico Oct 05 '21 at 06:24
  • @Enlico you meant "besides" perhaps, instead of "despite"? but anyway it doesn't. it uses `instance Monoid a => Monoid (Maybe a)` and `instance Foldable []`: `foo :: (a -> Maybe All) -> [a] -> Maybe All ; foo = foldMap` works as well. (cf. `foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m`). :) – Will Ness Oct 05 '21 at 08:43
  • No, I meant _despite_. The problem is that I mistakenly wrote _the_ instead of _another_ right before _answer_. I was referring to Daniel Wagner's answer. – Enlico Oct 05 '21 at 08:56