Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell check if a value in monad exists

Tags:

haskell

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?

like image 594
bash0r Avatar asked Oct 20 '15 07:10

bash0r


4 Answers

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
like image 179
Zeta Avatar answered Oct 20 '22 11:10

Zeta


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

like image 36
Ørjan Johansen Avatar answered Oct 20 '22 12:10

Ørjan Johansen


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.

like image 5
Ben Avatar answered Oct 20 '22 11:10

Ben


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.

like image 2
drquicksilver Avatar answered Oct 20 '22 11:10

drquicksilver