Please Buy now at Leanpub
Written and illustrated by GetContented.
Published on 2017-12-25.
In this chapter, we'll explain another way that might help you to think about functions work when they’re used. All of these views of functions can help you to get a rounded idea of how to use them well. We're spending so much time on this because it's the underpinning of everything in Haskell.
We could think of them like they were electronic devices, waiting to have something plugged into them. Depending on what you plug in, you will get a different result.
Functions are one of the most basic ways to re-use expressions in a program. They save us repeating ourselves as we write programs. Functions take variables or parameters that can have different values (of a type) for each new time the function is applied.
Functions are also themselves values, but they are a special kind of mapping-value from values to other values.
This is why
putStrLn "hi" means to “apply the
putStrLn function to the
Another way to think of it is that
putStrLn makes a kind of mapping between any
String value to a corresponding
IO () value.
Let’s imagine there’s a function called
plus5 which takes a whole number as its single parameter. When we give it a value, the result is whatever its input is, but 5 more.
If we were applying this function to the number
53, for example, we would write the function application
-- we apply the function plus5 -- to the number 53 plus5 53
Above, we show this function as a machine box with a plug and a screen that shows the “answer”.
Int is one of Haskell’s names for the type of whole numbers; both negative and positive. The mathematical name for a whole number is integer, which comes from latin and means “entire”.
The function’s type is written in Haskell as
Int -> Int, which means “the type of all functions that map from an
Int value, to another
We can also say that
Int -> Int is “an
Int that is a function of another
Int”. Its value isn’t just an
Int until you “give” it an
So in the graphic, we “plug”
53 into this
plus5 box, and it displays
58. Comparing our graphic with the way function application is actually written, we can see it’s reversed. In Haskell, as well as in Math, we usually plug values in to the right of the function, (or sometimes, with operators, the left and right), whereas in our box graphic, or with audio equipment, we’re plugging values in on the left of the boxes (which represent functions).
This is an important point. The way people think and do things is often put a different way around compared to the way you have to write things in Haskell. This is often the same thing with Math. In Math and Haskell, you have to be more consistent and precise. For example, in everyday life, we'd say "add five to three", but in Math and Haskell, we'd write
5 + 3.
It's good to realise there are usually many ways to look at something. For example, if we look at a toggle switch, and it’s marked “off”, does that mean it’s currently off, or that pressing it will “action” the off functionality? (ie turn it off). There is no one true right answer, so it’s good to be aware there are many ways to express the same thing that can often appear as complete opposites to each other.
Getting back to our machine, the moment we plug our
53 :: Int into the box, the type of the box changes from
Int -> Int to just
Int. We can then plug that whole expression
(plus5 53) into any other function box that takes an
Int as an argument. We may have to use brackets, though, in Haskell.
-- apply plus5 to 53: plus5 53 -- apply plus5 to -- the application of plus5 to 6 plus5 (plus5 6)
What if we wanted to plug the expression
plus5 53, into the function
plus5 again? Well, we could do that like this:
plus5 (plus5 53), and the result would be
We can clearly see that the “output” of the first box as 58 can be plugged into the “input” of the second box. (Don’t get confused, this is not an
IO action! We’re not talking about actually outputting these numbers on the screen or anything, just plugging values into functions as a metaphor).
So, let’s move on and read a definition for this
plus5 function. We already know what it does:
plus5 :: Int -> Int plus5 x = x + 5
This is the type declaration and definition for a function that takes one argument of type
Int. We’re naming that argument
x here in this definition (that’s why the
x appears on the left of the
= sign). We could have named it almost anything we liked. To “use” this function, you supply
plus5 with an
Int value by placing it to the right of it like this:
The definition uses something special we haven’t seen yet called an operator. Here it's an infix function called
(+) that is named as the
+ symbol, and it takes two numeric arguments, one on either side! A function is called an infix function when it appears between its arguments. Normal functions are called prefix because they are placed before their argument(s). Functions that are named as symbols like
(+) here are called operators, and they almost always only ever take exactly two arguments, and are usually infix, like
(+). You'll also notice when we talk about them, we put parentheses around them. In Haskell this is how they're referred to outside of when you're using them in a function application.
Let’s look at another program that does almost the same thing. Notice we’re using a different variable name here (we called it number), rather than
plus6 :: Int -> Int plus6 number = number + 6
Now we’ll present another way to write this. If you have an infix operator such as
(+), and you want to use it as a prefix function, you can just wrap it in parentheses. This function works exactly the same as
plus6' :: Int -> Int plus6' number = (+) number 6
Next we’ll present another identical function, but using what’s called a section. A section is a partially applied operator. That means it has one of its two arguments supplied, and that becomes a function. It always uses round brackets.
plus6'' :: Int -> Int plus6'' number = (+6) number
See if you can guess the type of
(+6) right now. First, you might want to think about the type of the
(+) operator. It takes two numeric arguments, and returns one numeric argument. So, if one of its arguments are supplied, it will become a function of only one argument. Then, we apply this function to the number variable to get our result.
So, you can think of the type of the function
(+6) as taking a single number, then returning a number.
It doesn't matter which side you put the value on with the operator
(+) takes two identical arguments, and is an operation that works the same no matter the order. In math, this property is called the commutative property.
The word commute can be broken into com-, which means altogether, and mut- which means to change. Commutativity means the ability to interchange, so we can see that we can interchange the numbers between either side of
(+), and it makes no difference.
Here are four functions that are identical in result:
sevenPlus :: Int -> Int sevenPlus number = (7+) number sevenPlus' :: Int -> Int sevenPlus' = (7+) plusSeven :: Int -> Int plusSeven number = (+7) number plusSeven' :: Int -> Int plusSeven' = (+7)
Your homework is to get familiar with more definitions, and seeing how function arguments are used in function bodies. Don’t get too worried or confused by the many tricky weird looking things you’ll see as you look at other code.
We’ll show you some code below. You should also do an internet search for ”haskell defining functions”, go through the first 10 or so returned pages and quickly scan through them for value and function definitions. Remember there are two ways to define functions: either with the lambda syntax like
sevenPlus = \number -> (7+) number or with the “normal” syntax like
sevenPlus number = (7+) number. Your aim here is simply to recognise where the definitions are.
Here are the examples. Remember, don’t get caught on what you don’t know, just look for what you do know. Remember, you’re just looking for the definitions, especially the function definitions:
squaredNum :: Integer -> Integer squaredNum x = x ^ 2 lengthNum :: Show a => a -> Int lengthNum n = length $ show n bool1, bool2 :: Bool bool1 = True bool2 = False notBool :: Bool -> Bool notBool b = if b == bool1 then bool2 else bool1 veryNotBool :: Bool -> Bool veryNotBool = \aBool -> notBool aBool sumFrom1To :: Integral a => a -> a sumFrom1To 0 = 0 sumFrom1To n = n + sumFrom1To (n - 1) isEven :: Integral a => a -> Bool isEven n = n `mod` 2 == 0
If you’ve enjoyed reading this, please consider purchasing a copy at Leanpub today
Please follow us, and check out our videos Follow @HappyLearnTutes
Also, Volume 2 is now in beta and being written! Show your support and register your interest at its Leanpub site.