3

I've found that I don't know how to handle nested if-else-then in do-blocks in Haskell.

I know already that I can use a case, but that would require all my conditions (a, b and c) to return the same type (Bool, so there are only two cases, but I need three distinct ones) and is therefore not as general (correct me if I'm wrong). I have also tried considering using guards here, but I don't know how to make this work in a do statement, especially if the -- something expressions are meant to be of type IO ().

Suppose I have the following code that is inside a do:

if a then
     -- something
else 
    if b then
        -- something
    else
        if c then
            -- something
        else
            -- something

How do I create the equivalent logic but without all the indenting?

3
  • 3
    What you have written already requires all of your -- something placeholders to return the same type, so your objection to case doesn't really apply. Commented Apr 23, 2019 at 4:12
  • 2
    (1) "that would require all my cases to return the same type and is therefore not as general (correct me if I'm wrong)" -- you also have to return the same type in both branches of an if-expression. Other than syntax, there is essentially no difference between an if-expression and a case-expression on Bool. (2) "I have also tried considering using guards here, but I don't know how to make this work in a do statement" -- You can't use guards within a do-block; if-expressions are indeed the right thing to reach for. Commented Apr 23, 2019 at 4:16
  • Sorry, I misspoke in my original question, it is not the -- something placeholders returning the same type that I am trying to avoid, it is the conditions (a, b and c). I've edited my answer. Commented Apr 23, 2019 at 4:43

1 Answer 1

9

To begin with, it is worth noting that if-expressions do not actually require extra indentation (if-within-do used to be an exception, but Haskell 2010 eliminated that). That means you might collapse all the extra indentation:

test = do
    len <- length <$> getLine
    if len < 4
    then putStrLn "Short"
    else if len > 6
    then putStrLn "Long"
    else putStrLn "Mid"

Personally, though, I don't find that too pleasing aesthetically, as I feel some indentation makes if-expressions easier to follow. A nice alternative is using the MultiWayIf extension:

{-# LANGUAGE MultiWayIf #-}

test = do
    len <- length <$> getLine
    if | len < 4 -> putStrLn "Short"
       | len > 6 -> putStrLn "Long"
       | otherwise -> putStrLn "Mid"

On a final note, nested if-expressions getting unwieldy might reveal a good occasion for breaking things down in separate definitions, or otherwise reorganising your code. See also: How do I deal with many levels of indentation?

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you! This is exactly what I was looking for as an answer. Sorry for my typo in my original question. Haskell is great fun so far!
@Utagai You're welcome, and worry not -- if you only knew how many typos I make in my posts... :)
@Utagai, if you don't want to use MultiWayIf, you can use case () of _ | .... Uglier, but standard.
Thanks @dfeuer, didn't realize I could use a case like that but reading it, I see why it works. Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.