views:

202

answers:

3

The lookup function in Data.Map and Data.IntMap currently return values wrapped in Maybe with the type signature

lookup  :: Ord  k => k -> Map  k a -> Maybe  a

It used to have the more general type of

lookup  :: (Monad  m, Ord  k) => k -> Map  k a -> m a

I realize the former likely reduces the need of extra type specification, but the latter would make it much more general and allow lookup to be used in list comprehensions. Is there any way to mimic this behavior with the newer version, or would I have to use an older version of the library?

+13  A: 

the latter would make it much more general and allow lookup to be used in list comprehensions

The latter is also more unsafe, as the majority of monad classes define fail as error. That is, the common case of not finding an element in the Map is a program-terminating error for most monads. That, coupled with the increased likelihood of the wrong type context being inferred, means we tend to discourage the 'monadic fail return' style now.

Is there any way to mimic this behavior with the newer version

Indeed there is! Simply lift Maybe a into Monad a, like so:

lift :: Monad m => Maybe a -> m a
lift Nothing  = fail "you die now"
lift (Just a) = return a

And now you can write, e.g. lift . lookup

Don Stewart
Thanks for the quick reply Don. That's much more concise than what I came up with.
sabauma
The question explicitly asked for `Monad`, which is why there is little else to use but `fail` for `Nothing` (and why `fail` exist at all in `Monad`). We should really point to something like `MonadPlus`, though:`maybe mzero return :: (MonadPlus m) => Maybe a -> m a``> maybe mzero return $ Just 4 :: Maybe Int``Just 4``> maybe mzero return $ Just 4 :: [] Int``[4]``> maybe mzero return $ Nothing :: [] Int``[]``> maybe mzero return $ Nothing :: Maybe Int``Nothing`
claus
+4  A: 

Don's lift converts Maybe's elements to their general Monad counterparts, so perhaps it should be named convert or generalize or something;-)

If you just want to use lookup mainly in list comprehensions and other monads that implement a fail, you could also make use of the mapping of pattern match failure to fail:

Prelude> [ v | Just v <- return $ lookup "hi" [("ho","silver")] ]
[]
Prelude> [ v | Just v <- return $ lookup "ho" [("ho","silver")] ]
["silver"]

Prelude> do Just v <- return $ lookup "hi" [("ho","silver")] ; print v
*** Exception: user error (Pattern match failure in do expression at <interactive>:1:3-8)
Prelude> do Just v <- return $ lookup "ho" [("ho","silver")] ; print v
"silver"
claus
This illustrates perfectly why use of fail is bad: it is almost always a program terminating exception. Not what you want when looking up in maps.
Don Stewart
Well, I said "monads that implement a fail", didn't I?-) `error` is undefined is not implemented. I just wanted to illustrate that case, too.Personally, I use pattern match fail in do-blocks when the monad is also a `MonadPlus` and implements `fail` as `mzero`. That is when this technique is most useful.
claus
+3  A: 

For the specific case of the list monad, the simplest solution is to use maybeToList:

Prelude> :m +Data.Maybe -- Note: Use "import Data.Maybe" in a program file

Data.Maybe> [ v | v <- maybeToList $ lookup "hi" [("ho","silver")] ]
[]
Data.Maybe> [ v | v <- maybeToList $ lookup "ho" [("ho","silver")] ]
["silver"]
Yitz