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: returning 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
elsebranch 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