bash0r
bash0r

Reputation: 804

Haskell check if a value in monad exists

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?

Upvotes: 9

Views: 1271

Answers (4)

Zeta
Zeta

Reputation: 105876

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

Upvotes: 12

drquicksilver
drquicksilver

Reputation: 1635

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.

Upvotes: 2

Ben
Ben

Reputation: 71400

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.

Upvotes: 5

&#216;rjan Johansen
&#216;rjan Johansen

Reputation: 18189

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.)

Upvotes: 5

Related Questions