Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do control flow in Haskell

I'll give an example of what I want to do right away.

version1 :: IO ()
version1 =
  if boolCheck
     then case maybeCheck of
            Nothing -> putStrLn "Error: simple maybe failed"
            Just v  -> case eitherCheck of
                         Left  e -> putStrLn $ "Error: " ++ show e
                         Right w -> monadicBoolCheck v >>= \case
                                      False -> putStrLn "Error: monadic bool check failed"
                                      True  -> print "successfully doing the thing"
    else putStrLn "simple bool check failed"

Basically I want to "do a thing" under the condition that a number of checks turns out positive. Whenever a single check turns out negative, I want to preserve the information about the offending check and abort the mission. In real life those checks have different types, therefore I called them

boolCheck        :: Bool
maybeCheck       :: Maybe a
eitherCheck      :: Show a => Either a b
monadicBoolCheck :: Monad m => m Bool

Those are just examples. Feel free to also think of monadic Maybe, EitherT or a a singleton list where I extract head and fail when it is not a singleton.

Now I am trying to improve the above implementation and the Either monad came into my mind, because it has the notion of aborting with an error message.

version2 :: IO ()
version2 = do
  result <- runEitherT $ do
    if boolCheck
       then pure ()
       else left "simple bool check failed"
    v <- case maybeCheck of
           Just x  -> pure x
           Nothing -> left "simple maybe check failed"
    w <- hoistEither . mapLeft show $ eitherCheck
    monadicBoolCheck v >>= \case
      True  -> pure ()
      False -> left  "monadic bool check failed"
  case result of
    Left  msg -> putStrLn $ "Error: " ++ msg
    Right _   -> print "successfully doing the thing"

While I prefer version2, the improvement in readability is probably marginal. Version2 is superior when it comes to adding further checks.

Is there an ultimately elegant way of doing this?

What I don't like:

1) I am partly abusing the Either monad and what I actually do is more like a Maybe monad with the rolls of Just and Nothing switched in the monadic bind

2) The conversion of the checks to Either requires either rather verbose use of case or a conversion function (like hoistEither).

Ways of improving readability might be:

1) define helper functions to allow code like

v <- myMaybePairToEither "This check failed" monadicMaybePairCheck

monadicMaybePairCheck :: Monad m => m (Maybe x, y)
...
myMaybePairToEither :: String -> m (Maybe x, y) -> EitherT m e z
myMaybePairToEither _   (Just x, y)  = pure $ f x y
myMaybePairToEither msg (Nothing, _) = left msg

2) consistently use explicit cases, not even use hoistEither

3) defining my own monad to stop the Either abuse ... I could provide all the conversion functions along with it (if no-one has already done something like that)

4) use maybe and either where possible

5) ... ?

like image 428
ruben.moor Avatar asked Mar 03 '26 18:03

ruben.moor


1 Answers

Use maybe, either, and the mtl package. By the by, eitherCheck :: Show a => Either a b's Show a constraint is probably not what you want: it lets callers choose whatever type they want as long as the type implements Show a. You were probably intending having a be a type such that callers would only be able to call show on the value. Probably!

{-# LANGUAGE FlexibleContexts #-}

newtype Error = Error String

gauntlet :: MonadError Error m => m ()
gauntlet = do
  unless boolCheck (throw "simple bool check failed")
  _ <- maybe (throw "simple maybe check failed") pure maybeCheck
  _ <- either throw pure eitherCheck
  x <- monadicBoolCheck
  unless x (throw "monadic bool check failed")
  return ()
  where
    throw = throwError . Error

version2 :: IO ()
version2 =
  putStrLn (case gauntlet of
              Left (Error e) ->
                "Error: " ++ e
              Right _ ->
                "successfully doing thing")
like image 64
hao Avatar answered Mar 06 '26 11:03

hao



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!