If-Then-Else
In Python, we would probably implement our sign
function like this:
def sign(x):
if x > 0:
return 1
elif x == 0:
return 0
else:
return -1
The Haskell code looks rather similar, only there is no elif
or else if
keyword—we need to achieve the same effect by nesting an if then else
expression within the else
branch of the outer if then else
expression.
sign :: Int -> Int
sign x = if x > 0 then
1
else
if x == 0 then
0
else
-1
The other difference is that we don't have a return
statement. Let me
emphasize this one more time: return
ing a value is something we do in an
imperative language, and there is no way to tell the computer to do anything
in a functional language. We can only define the value of a function, here
sign
. You should read this as "sign x
is 1
if x > 0
. Otherwise, if
x == 0
, then sign x
is 0
. Otherwise, it is -1
."
The indentation looks a bit excessive here. In fact, with the right
indentation, we can make it look as if there were an else if
keyword in
Haskell:
sign :: Int -> Int
sign x =
if x > 0 then
1
else if x == 0 then
0
else
-1
As always, it may make things feel less abstract to play with this function in GHCi:
>>> :{
| sign x =
| if x > 0 then
| 1
| else if x == 0 then
| 0
| else
| -1
| :}
>>> sign 5
1
>>> sign (-10)
-1
>>> sign 0
0
Remember that you can indent fairly freely, as long as the indentation levels indicate the block structure of your code, as discussed here.
You may have noticed that I called if then else
an "if then else
expression". You're probably more used to calling it an if
statement.
The distinction is important. A statement states something we should do. An
expression defines a value. This has an important consequence. In an
imperative language, we can have if
statements without an else
branch. If
a condition holds, we need to do something. Otherwise, don't do anything. For
example, in Python:
def abs(x):
if x < 0:
x = -x
return x
In Haskell, if then else
is an expression. If the condition after if
does
not hold, we cannot simply let the expression have no value. Thus,
The
else
branch is mandatory in Haskell.
A possible implementation of the abs
function in Haskell is:
abs :: Int -> Int
abs x = if x < 0 then -x else x
>>> abs x = if x < 0 then -x else x
>>> abs 5
5
>>> abs 0
0
>>> abs (-10)
10
Exercise
Haskell offers functions odd
and even
to test whether a number is odd or
even. I've used these functions in some examples before.
>>> even 5
False
>>> odd 5
True
>>> even 8
True
>>> odd 8
False
Let's pretend that these functions don't exist, and let's implement our own
versions of these two functions, which we call myOdd
and myEven
to avoid
name clashes with the two functions odd
and even
that do actually exist.
We want these two functions to behave exactly like odd
and even
:
>>> myEven 5
False
>>> myOdd 5
True
>>> myEven 8
True
>>> myOdd 8
False
To implement them, you'll find the mod
operator and equality tests handy.
Think about how you would implement the function myEven
in Python:
def myEven(x):
if x % 2 == 0:
return True
else:
return False
Or, more succinctly:
def myEven(x):
return x % 2 == 0
To complete this exercise, all you need to do is translate this function
into Haskell, and then do the same with myOdd
. Implement both versions,
the one using if then else
, and the one that directly returns the result
of the comparison.
Solution
With if then else
:
>>> :{
| myEven x =
| if x `mod` 2 == 0 then
| True
| else
| False
| :}
>>> myEven 1
False
>>> myEven 2
True
Without if then else
:
>>> myEven x = x `mod` 2 == 0
>>> myEven 1
False
>>> myEven 2
True
And, as good software engineers, we won't replicate the logic but simply use that a number is odd if it is not even:
>>> myOdd x = not (myEven x)
>>> myOdd 1
True
>>> myOdd 2
False
Given my dislike for parentheses, and as an illustration of the function
application operator ($)
, here is a version using this operator.
Remember, not $ myEven x
applies not
to myEven x
, so it's exactly
the same as not (myEven x)
:
>>> myOdd x = not $ myEven x
>>> myOdd 1
True
>>> myOdd 2
False
Here's an even shorter version using function composition. Instead of
defining what the value of myOdd
is on a given argument x
, as the
previous two definitions do, we focus on how the function myOdd
itself
can be built from existing functions. To produce the result of myOdd
on an argument x
, we need to apply myEven
to x
and then apply
not
to the result of this application of myEven
. That's nothing but
the composition of not
and myEven
, so we can define
myOdd = not . myEven
:
>>> myOdd = not . myEven
>>> myOdd 1
True
>>> myOdd 2
False