2

I'm basically looking for the find analogue of filterM:

findM :: (a -> m Bool) -> [a] -> m (Maybe a)

but I haven't been able to write it myself using find and some sort of lifting function. I'm currently doing it via Data.Maybe.listToMaybe:

x <- filterM f list
return $ listToMaybe x

which works, but it seems like I should be able to do it directly with find.

Edit: Found some stuff on hackage: Control.Monad.Loops.firstM and Control.Monad.TM.findM both do what I want

Martin DeMello
  • 11,876
  • 7
  • 49
  • 64
  • Is the implementation in terms of `filterM` really what you want? Won't that result in the whole list being sequenced, even if the element is found right at the beginning? – Owen Jan 15 '12 at 03:03
  • yes, it's ideally not what i want, it just happens that for my program it doesn't make much difference. that's why i'd like to rewrite it properly to have the same semantics as find. – Martin DeMello Jan 15 '12 at 03:05
  • 3
    The [monad-loops package](http://hackage.haskell.org/package/monad-loops) has a `firstM` function of this type. Note that when the predicate returns `True`, `firstM` does not apply it to subsequent items. – Joey Adams Jan 15 '12 at 03:12
  • 1
    nice, firstM does just what i want. should have thought to check hayoo as well as hoogle! – Martin DeMello Jan 15 '12 at 03:39

2 Answers2

1

Well, you could write it "from scratch" like

findM p ls =
    case ls of
         []   -> return Nothing
         x:xs -> do
            ok <- p x
            if ok
               then return (Just x)
               else findM p xs

though I realize that is much less elegant than what you are asking for. I could not figure out how to do it with some sort of lifting function. I feel like the lifting would have to occur at the recursive step of find, which is hidden inside the function and not available to be lifted. Though I would love to see someone prove me wrong.

Owen
  • 38,836
  • 14
  • 95
  • 125
  • good point, i think you have put your finger on why lift doesn't work here. marking this as accepted. – Martin DeMello Jan 15 '12 at 03:36
  • 1
    There is no way to lift `find` to its counterpart `findM`, but there is a way to "sink" `findM` to its counterpart `find`, using `sinkM hof f = runIdentity . hof (return . f)`, you can `sinkM findM` and it will behave just like `find`, by running in the Identity monad. I wrote [sinkM a few months ago](http://stackoverflow.com/a/7735551/208257), just felt like sharing. I wonder if there's a package that provides this function. – Dan Burton Jan 15 '12 at 07:12
1

Here is a boring, direct implementation:

findM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
findM _ []     = return Nothing
findM f (x:xs) = do
    found <- f x
    if found
        then return $ Just x
        else findM f xs

Notice that this findM does not apply f to any items after the found one. Thus, it is semantically different from the filterM implementation you posted.

You can't write this based on find without going out of your way. You already have a pure list, but the predicate (a -> m Bool) does not match the (a -> Bool) needed by find. If you use mapM to apply the predicate to each item, all you get is [Bool], which you would need to zip against the original list.

Joey Adams
  • 41,996
  • 18
  • 86
  • 115