Reputation: 361
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
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
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
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
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
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
Reputation: 152682
Too bad, all if
s come with else
s. 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