Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

terminating a monadic fold early

I've written this so that I can terminate a monadic fold early:

myfoldM             :: (Monad m) => (a -> b -> m (Maybe a)) -> a -> [b] -> m (Maybe a)
myfoldM _ a []      =  return $ Just a
myfoldM f a (x:xs)  =  do ma <- f a x;
                          case ma of
                            Nothing -> return Nothing
                            Just a' -> myfoldM f a' xs

and I'm wondering if there is a more elegant way to express this or if something similar exists in a library. Of course there is an analogous version replacing Maybe with Either.

Update... here is a complete solution based on Petr Pudlák's answer:

import Control.Monad (foldM,Monad,mzero)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.Trans.Class (lift)

myfoldM' :: (Monad m) => (a -> b -> MaybeT m a) -> a -> [b] -> m (Maybe a)
myfoldM' f = (runMaybeT .) . foldM f

go :: Int -> Int -> MaybeT IO Int
go s i = do
  lift $ putStrLn $ "i = " ++ show i
  if i <= 4 then return (s+i) else mzero

test n = do
  myfoldM' go 0 [1..n] >>= print      

-- test 3 => Just 6
-- test 5 => Nothing
like image 986
ErikR Avatar asked Jun 15 '14 02:06

ErikR


1 Answers

This is just a standard monadic foldM enhanced with early exit. This can be done just by wrapping the inner computation into MaybeT:

import Control.Monad
import Control.Monad.Trans.Error
import Control.Monad.Trans.Maybe

myfoldM :: (Monad m) => (a -> b -> m (Maybe a)) -> a -> [b] -> m (Maybe a)
myfoldM f = (runMaybeT .) . foldM ((MaybeT .) . f)

But I'd say it's more convenient for the folding function to be given using MaybeT directly, because then it can easily terminate the computation by mzero, instead of manipulating Maybe values, and in such a case it's almost not worth writing a separate function for it:

myfoldM' :: (Monad m) => (a -> b -> MaybeT m a) -> a -> [b] -> m (Maybe a)
myfoldM' f = (runMaybeT .) . foldM f

For Either it's the same:

myfoldM'' :: (Monad m, Error e)
          => (a -> b -> ErrorT e m a) -> a -> [b] -> m (Either e a)
myfoldM'' f = (runErrorT .) . foldM f
like image 66
Petr Avatar answered Oct 27 '22 08:10

Petr