Swapnil Sharma
Swapnil Sharma

Reputation: 361

How do I use if-then-else statement with no else condition in Haskell?

I have a list of relations and I wish to print the names of all fathers. Since there's no else condition, the following code doesn't work:

relations = [("father", "arushi", "anandan"), ("mother", "arushi", "abigale"), ("father", "anandan", "ayuta"), ("mother", "anandan", "akanksha")]

father ((r, c, f):xs) = if r == "father" then print(f)

main = do
    father (relations)

I do not wish to put any statement after else.

Upvotes: 10

Views: 12156

Answers (6)

MERLIN
MERLIN

Reputation: 436

Was struggling with this style as well, & with Haskell formatting requirements. But I found that instead of placing emphasis on a required [else], one can use [else do] to include all following lines of code without an additional indention on concurrent lines, such as..

main = do
    --if conditions exit main, 
    if t < 1 || t > 100000 
    then return ()
    else
        do
    --code here, even with the else, 
    -- is only run when if condition above -> is false

Proof code can be in simpler form

if True
    return ()
else
    do
-- add code below this line to prove return works

Upvotes: 0

urbanslug
urbanslug

Reputation: 146

I feel the need to explain why an if must have an else in Haskell.

Haskell is an implementation of typed lambda calculus and in lambda calculus we have expressions and values nothing else. In it we evaluate/reduce expressions to values or into expressions that can't be reduced any further.

Now in typed lambda calculus we add types and abstractions but we still to evaluate down to values and expressions one of these expressions being if predicate then value else value. This if expression must reduce to a value therefore both branches of the if expression must reduce to values of the same type. If we had an "if predicate then value" it means we would have a branch that doesn't reduce to a value.

you can use run, reduce and evaluate interchangeably in the context of this answer.

When we run Haskell code we are reducing lambda terms into values or expressions that can't be reduced any further. The compiler exists to help us write valid lambda terms.

Going by lambda calculus we see that the if statement must, when evaluated, reduce to a value (or be capable of doing so) and because Haskell is implemented typed lambda calculus an if expression in Haskell without an else wouldn't have the possibility of evaluating down to a value all the time.

TL;DR

The "if ... then ... else" statement should when evaluated reduce to a value. As long as both branches of the if statement evaluates to the same type it evaluates correctly.

If any branch doesn't evaluate to a value or are going to evaluate to values of different types that is not a valid lambda term and the code will not typecheck.

Upvotes: 2

chepner
chepner

Reputation: 530892

You can write a function that always writes the name, but then ensure it only gets called on values containing father.

relations :: [(String,String,String)]
relations = [("father", "arushi", "anandan")
            ,("mother", "arushi", "abigale")
            ,("father", "anandan", "ayuta")
            ,("mother", "anandan", "akanksha")
            ]

printName :: (String,String,String) -> IO ()
printName (_, _, name) = print name

printFathers :: [(String,String,String)] -> [IO ()]
printFathers = fmap printName . filter (\(f, _, _) -> f == "father")

main = sequence (printFathers relations)

The definition of filter hides the logic of skipping certain elements of the list. The argument to filter always returns either True or False, but the result of filter only contains those elements for which you want to call print.

(sequence, here, just turns the list of IO values into the single IO value that main must be by "swapping" IO and []. You could incorporate this into printName by defining it as sequence . fmap printName . ..., and replace sequence . fmap foo with traverse foo.)


Note that if foo then bar else baz is syntactic sugar for a complete case expression

case foo of
  True -> foo
  False -> baz

However, a case expression doesn't have to handle every possible value of the foo argument. You could write

father ((r, c, f):xs) = (case r of "father" -> print f) : father xs

It would be instructive, though, to see what happens when r doesn't match "father".

Upvotes: 6

chi
chi

Reputation: 116139

The idiomatic Haskell way to solve such issues is to avoid mixing computation and I/O, when possible.

In this case, instead of "printing the names of all fathers", you can first "compute the names of all fathers" (no I/O here) and then "print the computed names" (I/O here)

relations = 
   [ ("father", "arushi", "anandan")
   , ("mother", "arushi", "abigale")
   , ("father", "anandan", "ayuta")
   , ("mother", "anandan", "akanksha")
   ]

-- compute only the fathers
fathers = [ f | ("father", _, f) <- relations ]

-- print them
main :: IO ()
main = mapM_ putStrLn fathers

No if needed, since mapM_ iterates over the list for us, and all the list entries have to be printed.

Upvotes: 13

dfeuer
dfeuer

Reputation: 48580

Every if must have an else.

father ((r, c, f):xs) =
  if r == "father"
    then print f
    else _what

If you try to compile that, you'll be informed that there's a hole

_what :: IO ()

So you need to manufacture something of that type. Fortunately, that's easy:

father ((r, c, f):xs) =
  if r == "father"
    then print f
    else pure ()

pure x does nothing and returns x.

Since what you're trying to do is quite common, there are two functions specifically designed for the task:

when :: Applicative f => Bool -> f () -> f ()
when b m = if b then m else pure ()

unless :: Applicative f => Bool -> f () -> f ()
unless = when . not

You can find both of these functions in Control.Monad.

father ((r, c, f):xs) =
  when (r == "father") $ print f

Upvotes: 8

Daniel Wagner
Daniel Wagner

Reputation: 152682

Too bad, all ifs come with elses. But that's okay, there's a distinguished do-nothing IO action.

father ((r, c, f):xs) = if r == "father" then print f else return ()

There are many other ways to skin this cat. One is pattern matching.

father (("father", c, f):xs) = print f
father ((r, c, f):xs) = return ()

Another that is specific to monadic actions is to use when.

father ((r, c, f):xs) = when (r == "father") (print f)

Of course, that's just hiding the else, which is again return ():

when p act = if p then act else pure () -- okay, it's actually pure, not return

Upvotes: 25

Related Questions