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.
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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With