Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chained if/else statements in the IO Monad

Tags:

haskell

monads

I'm wondering if there is an idiomatic way to write control code similar to chained if/else statements in an imperative language in the IO Monad.

So in a language like Python, I would typically right something like this:

if οs.path.isdir(fname):
    # do whatever
elif os.path.isfile(fname):
    # ...
else:
    # ...

The best I could come up in Haskell is the following:

isf <- doesFileExist path
isd <- if isf then return False else doesDirectoryExist path
case (isf, isd) of
    (True,  _)      -> return ...
    (_,     True)   -> return ...
    _               -> return ...

Which is not as good, and I am wondering if there is a better way to write this sort of thing.

Also, to validate my understanding: the if isf part in isd <- ... is required in the case of the IO Monad, if you don't want to always do both operations. My guess would be that in other Monads (lazy Monads?), this would not be needed because isd would be evaluated lazily.

Edit

Based on the first comments, I ended up with the following:

firstMatchM :: (Monad m) => a -> [(a -> m Bool, b)] -> b -> m b
firstMatchM arg [] def   = return def
firstMatchM arg ((check,x):xs) def = do
    t <- check arg
    if t then return x else firstMatchM arg xs def

doFirstM :: (Monad m) => a -> [(a -> m Bool, a -> m b)] -> (a -> m b) -> m b
doFirstM arg acts def = do
    fm <- firstMatchM arg acts def
    fm arg

handlePath2 path = doFirstM path
   [( \p -> doesFileExist p,
         \p ->  return "file"
   ),(\p -> doesDirectoryExist p,
         \p -> return "dir"
   )] $ \p -> return "Error"

Which is similar to @chi's second suggestion, bit I prefer ifM, because it's closer to the imperative version.

like image 656
ynimous Avatar asked Nov 08 '15 10:11

ynimous


People also ask

How to do multiple if statements in Haskell?

In Haskell, multiple lines of if will be used by separating each of the if statement with its corresponding else statement. In the above example, we have introduced multiple conditions in one function.

What is the IO Monad?

The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.

Does Haskell have else if?

As a consequence, the else is mandatory in Haskell. Since if is an expression, it must evaluate to a result whether the condition is true or false, and the else ensures this.

Is IO a Monad Haskell?

The IO type constructor provides a way to represent actions as Haskell values, so that we can manipulate them with pure functions. In the Prologue chapter, we anticipated some of the key features of this solution. Now that we also know that IO is a monad, we can wrap up the discussion we started there.


1 Answers

If we don't want to involve monad transformers, a basic option is rolling our own monadic if:

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM act t e = do
  b <- act
  if b then t else e

Then the code structure is similar to the one in imperative languages:

test :: IO String
test = ifM anAction (do
          putStrLn "branch a"
          return "a")
       $ ifM otherAction (do
          putStrLn "branch b"
          return "b")
       $ return "none"

where anAction, otherAction :: IO Bool.

Alternatively, use something like

ifChain :: [(IO Bool, IO a)] -> IO a -> IO a
ifChain [] e = e
ifChain ((g, a) : acts) e = do
   b <- g
   if b then a else ifChain acts e
like image 78
chi Avatar answered Oct 03 '22 11:10

chi