I would suggest using ExceptT
and either
in foo
as follows:
import Control.Monad.IO.Class
import Control.Monad.Trans.Except
import Data.Traversable
foo :: [Int] -> IO [Int]
foo count = runEarlyReturn $ for count $ \u ->
if even u
then do
liftIO (putStrLn (show u <> " is Even!"))
earlyReturn [u]
else do
liftIO (print u)
pure u
-- ghci> foo [1,3,5,7]
-- 1
-- 3
-- 5
-- 7
-- [1,3,5,7]
-- ghci> foo [1,3,4,5,7]
-- 1
-- 3
-- 4 is Even!
-- [4]
runEarlyReturn :: Monad m => ExceptT b m b -> m b
runEarlyReturn m = (pure . either id id) =<< runExceptT m
earlyReturn :: Monad m => a -> ExceptT a m r
earlyReturn = throwE
However, bar
is more subtle. It demonstrates well the need for lightweight streams/iterators. Luckily Bluefin (package: bluefin
) has them! This is what foo
and bar
look like in Bluefin:
import Bluefin.EarlyReturn (returnEarly, withEarlyReturn)
import Bluefin.IO (effIO, runEff)
import Bluefin.Jump (jumpTo, withJump)
import Bluefin.Stream (yield, yieldToList)
import Data.Foldable (for_)
foo :: [Int] -> IO [Int]
foo count = runEff $ \io -> do
withEarlyReturn $ \early -> do
(as, ()) <- yieldToList $ \y -> do
for_ count $ \u -> do
if even u
then do
effIO io (putStrLn (show u <> " is Even!"))
returnEarly early [u]
else do
effIO io (print u)
yield y u
pure as
-- ghci> foo [1,3,5,7]
-- 1
-- 3
-- 5
-- 7
-- [1,3,5,7]
-- ghci> foo [1,3,4,5,7]
-- 1
-- 3
-- 4 is Even!
-- [4]
bar :: [Int] -> IO [Int]
bar count = runEff $ \io -> do
(as, ()) <- yieldToList $ \y -> do
withJump $ \break -> do
for_ count $ \a -> do
yield y a
if even a
then do
effIO io (putStrLn (show a <> " is even!"))
jumpTo break
else
effIO io (print a)
pure as
-- ghci> bar [1,3,5,7]
-- 1
-- 3
-- 5
-- 7
-- [1,3,5,7]
-- ghci> bar [1,3,4,5,7]
-- 1
-- 3
-- 4 is even!
-- [1,3,4]