I'm wondering if there is an equivalent or something for null
from lists generalized for monads. At the moment I'm trying to find erroneous values with == mzero
. But this kind of check provokes false positives since mzero /= Left "foo"
applies.
Is there an elegant way to express what I want in standard libraries?
The notion "contains a value" isn't captured by the Monad
typeclass. After all, a Monad
only gives you something to sequence (>>=
) actions (bind the previous result). And MonadPlus
gives you a way to "shortcut" a computation with mzero
(have a look at the laws and mplus
).
However, being able to contain one or many values usually goes hand in hand with being able to fold all those values together into something. And indeed, Data.Foldable
contains a function conveniently also called null
:
> import qualified Data.Foldable as F
>
> showAndTell :: (Show (t a), Foldable t) => t a -> IO ()
> showAndTell k =
> putStrLn $ " F.null (" ++ show k ++ ") is " ++ show (F.null k)
> main = do
> putStrLn "Using F.null on 'empty' things:"
> showAndTell $ (Left "Error" :: Either String Int)
> showAndTell $ (Nothing :: Maybe Integer)
> showAndTell $ ([] :: [Double])
>
> putStrLn ""
> putStrLn "Using F.null on 'filled' things:"
> showAndTell $ (Right 123 :: Either String Integer)
> showAndTell $ (Just 123 :: Maybe Integer)
> showAndTell $ ([1,2,3] :: [Int])
Result:
Using F.null on 'empty' things:
F.null (Left "Error") is True
F.null (Nothing) is True
F.null ([]) is True
Using F.null on 'filled' things:
F.null (Right 123) is False
F.null (Just 123) is False
F.null ([1,2,3]) is False
So what you're looking for is part of Foldable
, not Monad
. This will only work on GHC 7.10 or higher, since Data.Foldable.null
was introduces in base 4.8.0.0. If you're stuck with an older version of GHC, you can use
> isEmpty :: F.Foldable t => t a -> Bool
> isEmpty = F.foldr (\_ _ -> False) True
I cannot on the spot remember any simple function that works for all the monads Maybe
, Either e
, MaybeT m
and ExceptT e m
, which are the main monads I can think of that have "erroneous values".
Since GHC 7.10, null
actually has been generalized (to Foldable
), so it works on the first two.
For the last three (Either e
works with both methods) and transformed versions of them, you can probably use the catchError
function.
(Although the last two have Foldable
instances, their null
does the wrong thing.)
Definitely not from outside the monad. If you're searching for something like nonEmpty :: MonadPlus m => m a -> Bool
then it's impossible.
You'd need to actually run the monadic computation to find out whether it's "empty". But the monad might need some sort of input to run (e.g. things like State
or even just Reader
), and in the extreme case of IO
you can't run the monad at all from outside. Now those examples aren't MonadPlus
on their own AFAICR, but augment them with failure (e.g. MaybeT
) and suddenly there's an obvious definition of what it means for them to be "empty", but the same restrictions still apply. Since an unknown monad could be one of those, you can't get any information out.
A possible signature might be nonEmpty :: MonadPlus m => m a -> m Bool
(although I'm not sure that that has a sensible implementation either). But I don't think that's what you're after, since it doesn't actually generalise null
; you'd end up retiring [False]
or [True]
for lists (or possibly even [True, True, True, ...]
with the same number of elements as the input), which is a bit weird.
I think monads are on the wrong "axis of generalisation" for null
; you want an abstraction that better characterises containers than Monad
does (lots of monads are containers, but so are lots of other things that are very different, so code that works on arbitrary monads can't assume container-like properties). Generalising to Foladable
as happened in GHC-7.10 seems a pretty good bet. You could probably make a CanBeEmpty
type class that admits a few more things than Foldable
; I don't know that such a thing exists already though.
The idiomatic thing to do is not to try to detect the zero directly, but to provide the behaviour you want in case of zero.
This is what mplus
or equivalently (in recent Preludes) <|>
do; it chains an action to run in the case that the first action fails. This is the same as catchError
on those Monads which catchError
supports. It is analogous to the common idiom in shell or perl programming foo || bar
meaning run foo and then run bar if foo failed.
Beware in the list monad that it will run all the options though because that is how the list monad is designed to work, modelling 'multiple possibilities'.
I frequently use <|>
on MaybeT
or EitherT
to model left-biased choice, i.e. "try all of these alternatives in turn until one succeeds".
Beware that using <|>
for EitherT
does discard the error message (because it turns a failing computation into a succeeding one); if you want to save the error message and process it somehow it is, again, catchError
you wanted.
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