Search code examples
haskellmonad-transformersstate-monad

Breaking the loop


I have the following code sample:

data Smth = A | B

data MyError = MkMyError

data MyState = MkMyState

run :: [Smth] -> Either MyError (Maybe Integer)
run param =
  evalState
    ( foldM
        ( \acc a -> do
            res <- go a
            case res of
              Right (Just _) -> undefined -- I want to break here when Right value is Just
              _ -> return (acc >> res)
        )
        (Right Nothing)
        param
    )
    MkMyState
  where
    go :: Smth -> State MyState (Either MyError (Maybe Integer))
    go = undefined

I have list of Smth that are processed sequentially and they process a result based on state in the State monad and the Smth value.

I want to break in the run when the go results in MyError (left value of the Either). This works with the code snippet using the >> operator.

However, I want to also have the possibility to break folding when the go function results in Right (Just _) (There is a comment on the line).

Question

How to break the following loop when I get the Just value? I want to break the loop in 2 cases:

  • in case of error - go resulting in Left value
  • in case of Just value - go resulting in Right (Just _) value. This is some kind of flipped behaviour of the Maybe monad and the >> operator. I don't want to break on Nothing, but on Just.

How could this be composed?


Solution

  • Am I correct that you want to process the Smths only as long as go keeps returning Right Nothing, and stop at the first go call that results in Left _ or Right (Just _) without running any more go calls?

    If so, I don't think your foldM makes any sense here. In case of error or Just, you want to stop right away, but the foldM just keeps processing Smths after the error or Just. The acc >> res makes sure that the fold eventually returns the value of the first error, but you still process all the Smths (or run forever, if the input list is infinite).

    Instead, you want something like process:

    run :: [Smth] -> Either MyError (Maybe Integer)
    run param = evalState (process param) MkMyState
      where
        process :: [Smth] -> State MyState (Either MyError (Maybe Integer))
        process (a:as) = do
          res <- go a
          case res of
            Right Nothing -> process as
            _ -> return res -- stop on Left _ or Right (Just _)
        process [] = return $ Right Nothing
    
        go :: Smth -> State MyState (Either MyError (Maybe Integer))
        go = undefined
    

    If you really want to write process as a fold, you can, though it's just a foldr, not a foldM:

    process :: [Smth] -> State MyState (Either MyError (Maybe Integer))
    process = foldr step (return $ Right Nothing)
      where step a acc = do
              res <- go a
              case res of
                Right Nothing -> acc
                _ -> return res