Last modified on 9 November 2014, at 15:15

Haskell/Variables and functions

All the examples in this chapter can be typed into a Haskell source file and evaluated by loading that file into GHC or Hugs. Do not include the "Prelude>" prompts at the beginning of input. When the prompt is shown, you can type the code into an environment like GHCi. Otherwise, you should put the code in a file and run it.

VariablesEdit

We have seen how to use GHCi as a calculator. Of course, this is only practical for short calculations. For longer calculations and for writing Haskell programs, we want to keep track of intermediate results.

Intermediate results can be stored in variables, to which we refer by their name. A variable contains a value, which is substituted for the variable name when the variable is used. For instance, consider the following calculation

ghci> 3.1416 * 5^2
78.53999999999999

That is the approximate area of a circle with radius 5, according to the formula A = \pi r^2. It is cumbersome to type in the digits of \pi \approx 3.1416, or even to remember them at all. In fact, an important motivation of programming is delegating mindless repetition and rote memorization to a machine so that our minds are free to deal with more interesting ideas. For the present case, Haskell already includes a variable named pi that stores over a dozen digits of \pi for us. This allows for not just clearer code, but also greater precision.

ghci> pi
3.141592653589793
ghci> pi * 5^2
78.53981633974483

Note that the variable pi and its value, 3.141592653589793, can be used interchangeably in calculations.

Haskell source filesEdit

Whenever we write code that will be used more than momentarily, we save it in a Haskell source file with the extension .hs. Basically, .hs files are plain text. If you need suggestions for text editors appropriate for coding, the Wikipedia article on text editors is a reasonable place to start. Vim and Emacs are popular choices among Haskell programmers. Proper source code editors will provide syntax highlighting, which colors the code in relevant ways to make reading and understanding easier.

To keep things tidy, create a directory (i.e. a folder) in your computer to save the Haskell files you will create while doing the exercises in this book. Call the directory something like HaskellWikibook. Then, create a new file in that directory called Varfun.hs with the following code:

r = 5.0

That code defines the variable r as being 5.0. Setting our own variables can make it easier to do future calculations.

Note: make sure that there are no spaces at the beginning of the line because Haskell is sensitive to whitespace.

Next, navigate to the HaskellWikibook directory in your terminal, start GHCi and load the Varfun.hs file using the :load command:

Prelude> :load Varfun.hs
[1 of 1] Compiling Main             ( Varfun.hs, interpreted )
Ok, modules loaded: Main.

:load can be abbreviated as :l (as in :l Varfun.hs).

If GHCi gives an error like Could not find module 'Varfun.hs', you probably are in the wrong directory. You can use the :cd command to change directories within GHCi (for instance, :cd HaskellWikibook).

With the file loaded, you can use the newly defined variable r in your calculations.

*Main> r
5.0
*Main> pi * r^2
78.53981633974483

So, to calculate the area of a circle of radius 5, we simply define r = 5.0 and then type in the well-known formula \pi r^2 for the area of a circle. There is no need to write the numbers out every time; that's very convenient!

Since this was so much fun, let's add another definition: Change the contents of the source file to

r = 5.0
area = pi * r ^ 2

Save the file and type the :reload (shorter version is :r) command in GHCi to load the new contents (note that this is a continuation of the last session):

*Main> :reload
Compiling Main             ( Varfun.hs, interpreted )
Ok, modules loaded: Main.
*Main>

Now we have two variables r and area available

*Main> area
78.53981633974483
*Main> area / r
15.707963267948966

Note

It is also possible to define variables directly at the GHCi prompt, without a source file. Skipping the details, the syntax for doing so uses the let keyword (a word with a special meaning) and looks like:

Prelude> let area = pi * 5 ^ 2

Although we will occasionally use let this way for expediency, it will become obvious that this practice is not convenient once we move into slightly more complex tasks. So, we are emphasizing the use of source files from the very start.


Note

GHC can also be used as a compiler (that is, you can use GHC to convert your Haskell files into a standalone program that can be run without depending on the interpreter). How that is done will be explained later, in the Standalone programs chapter.


CommentsEdit

Before we continue, it is good to understand that it is possible to include text in a program without having it treated as code. This is achieved by use of comments. In Haskell, a comment can be started with -- and continues until the end of the line:

x = 5     -- The variable x is 5.
y = 6     -- The variable y is 6.
-- z = 7

In this case, x and y are defined, but z is not. Comments can also go anywhere using the alternative syntax {- ... -}:

x = {- Do this just because you can. -} 5

Comments are generally used for explaining parts of a program that might be, on their own, confusing to readers. Beware of overdoing it, though. Too many comments make programs harder to read. Also, comments must be carefully updated whenever the corresponding code is changed, so that they do not become outdated, incorrect and misleading.

Variables in imperative languagesEdit

If you are already familiar with imperative programming languages like C, you will notice that variables in Haskell are quite different from variables as you know them. We now explain why and how.

If you have no programming experience, you might like to skip this section and continue reading with Functions.


Unlike in imperative languages, variables in Haskell do not vary. Once defined, their value never changes; they are immutable. For instance, the following code does not work:

r = 5
r = 2

The variables in functional programming languages are more related to variables in mathematics than to changeable locations in a computer's memory. In a math classroom, you would definitely never see a variable change its value within a single problem. Likewise, in Haskell the compiler will respond to the code above with an error: "multiple declarations of r". Those familiar with imperative programming, which involves explicitly telling the computer what to do, may be used to read this as first setting r = 5 and then changing it to r = 2. In functional programming languages, however, the program is in charge of figuring out what to do with the computer's memory.

Here's another example of a major difference from imperative languages:

r = r + 1

Instead of "incrementing the variable r", this is actually a recursive definition of r in terms of itself (we will explain recursion in detail later on; just remember that there is a radical difference from what would happen in an imperative language). If r had been defined with any value beforehand, then r = r + 1 in Haskell would bring an error message. That is akin to saying, in a mathematical context, that 5 = 5 + 1, which is plainly wrong.

Because their values do not change within a program, variables can be defined in any order. For example, the following fragments of code do exactly the same thing:

 y = x * 2
 x = 3
 x = 3
 y = x * 2

We can write things in any order that we want, there is no notion of "x being declared before y" or the other way around. This is also why you can't declare something more than once; it would be ambiguous otherwise. Of course, using y will still require a value for x, but this is unimportant until you need a specific numeric value.

By now, you might be wondering how you can actually do anything at all in Haskell where variables don't change. But trust us; as we hope to show you in the rest of this book, you can write every program under the sun without ever changing a single variable! In fact, variables that don't change make life much easier because it makes programs much more predictable. That is a key feature of purely functional programming; it requires a very different approach and mindset from those of imperative programming.

FunctionsEdit

Now, suppose that we have multiple circles with different radii whose areas we want to calculate. For instance, let us calculate the area of another circle with radius 3. If we were to build on the program we have just written, r would already have been defined as 5. We could change the entire program of so that r = 3, but then we would lose the ability to calculate the first circle. An alternative is defining a new variable r2, and also another new variable area2 for the area calculated with r2.[1] Our new source file for both circles is:

r  = 5
area  = pi * r ^ 2
r2 = 3
area2 = pi * r2 ^ 2

Clearly, that is unsatisfactory because we are repeating the formula for the area of a circle verbatim. To eliminate this mindless repetition, we would prefer to write it down only once and then apply it to different radii. That's exactly what functions allow us to do.

A function takes an argument value (or parameter) and gives a result value (this is essentially the same as mathematical functions). Defining functions in Haskell is simple: It is like defining a variable, except that we take note of the function argument that we put on the left hand side. For instance, the following is the definition of a function area which depends on an argument which we name r:

area r = pi * r ^ 2

Look closely at the syntax: the function name comes first (in our example, it is area), followed by a space and then the argument (r in the example). Following the = sign, the function definition is a formula that uses the argument in context with other already defined terms.

Now, we can plug in different values for the argument in a call to the function. Save the code in a file, load it into GHCi and try the following:

*Main> area 5
78.53981633974483
*Main> area 3
28.274333882308138
*Main> area 17
907.9202768874502

Thus, we can call this function with different radii to calculate the area of a circle with a radius of any length.

Our function here is defined mathematically as

A(r) = \pi \cdot r^2

In mathematics, the parameter is enclosed between parentheses, as in A(5) = 78.54 or A(3) = 28.27. The Haskell code will also work with parentheses, but they are normally omitted. As Haskell is a functional language, we will use functions all the time, and whenever possible we want to minimize extra symbols.

Parentheses are still used for grouping expressions (any code that gives a value) to be evaluated together. Note how the following expressions are parsed differently:

5 * 3 + 2       -- 15 + 2 = 17 (multiplication is done before addition)
5 * (3 + 2)     -- 5 * 5 = 25 (thanks to the parentheses)
area 5 * 3      -- (area 5) * 3
area (5 * 3)    -- area 15

Notice how Haskell functions take precedence over all other operators such as + and *, in the same way that, for instance, multiplication is done before addition in mathematics.

EvaluationEdit

Let us try to understand what exactly happens when you enter an expression into GHCi. After you press the enter key, GHCi will evaluate the expression you have given. That means it will replace each function with its definition and calculate the results until a single value is left. For example, the evaluation of area 5 proceeds as follows:

   area 5
=>    { replace the left-hand side  area r = ...  by the right-hand side  ... = pi * r^2 }
   pi * 5 ^ 2
=>    { replace  pi  by its numerical value }
   3.141592653589793 * 5 ^ 2
=>    { apply exponentiation (^) }
   3.141592653589793 * 25
=>    { apply multiplication (*) }
   78.53981633974483

As this shows, to apply or call a function means to replace the left-hand side of its definition by its right-hand side. As a last step, GHCi prints the final result on the screen.

Here are some more functions:

double x    = 2 * x
quadruple x = double (double x)
square x    = x * x
half   x    = x / 2
Exercises
  • Explain how GHCi evaluates quadruple 5.
  • Define a function that subtracts 12 from half its argument.

Multiple parametersEdit

Of course, functions can also have more than one argument. For example, here is a function for calculating the area of a rectangle given its length and its width:

areaRect l w = l * w
*Main> areaRect 5 10
50

Another example that calculates the area of a triangle \left(A = \frac{bh}{2}\right):

areaTriangle b h = (b * h) / 2
*Main> areaTriangle 3 9
13.5

As you can see, multiple arguments are separated by spaces. That's also why you sometimes have to use parentheses to group expressions. For instance, to quadruple a value x, you can't write

quadruple x = double double x

because that would mean to apply a function named double to the two arguments double and x: functions can be arguments to other functions (and you will see why later). Instead, you have to put parentheses around the argument:

quadruple x = double (double x)

Arguments are always passed in the order given. For example:

subtract x y = x - y
*Main> subtract 10 5
5
*Main> subtract 5 10
-5

Here, subtract 10 5 evaluates to 10 - 5, but subtract 5 10 evaluates to 5 - 10 because the order changes.

Exercises
  • Write a function to calculate the volume of a box.
  • Approximately how many stones are the famous pyramids at Giza made up of? Hint: you will need estimates for the volume of the pyramids and the volume of each block.

Remark on combining functionsEdit

It goes without saying that you can use functions that you have already defined to define new functions, just like you can use the predefined functions like addition (+) or multiplication (*) (operators are defined as functions in Haskell). For example, to calculate the area of a square, we can reuse our function that calculates the area of a rectangle:

areaRect l w = l * w
areaSquare s = areaRect s s
*Main> areaSquare 5
25

After all, a square is just a rectangle with equal sides.

This principle may seem innocent enough, but it is really powerful, in particular when we start to calculate with other objects instead of numbers.

Exercises
  • Write a function to calculate the volume of a cylinder. The volume of a cylinder is the area of the base, which is a circle (you already programmed this function in this chapter, so reuse it) multiplied by the height.

Local definitionsEdit

where clausesEdit

When defining a function, it is not uncommon to define intermediate results that are local to the function. For instance, consider Heron's formula A = \sqrt{s(s-a)(s-b)(s-c)} for calculating the area of a triangle with sides a, b, and c:

heron a b c = sqrt (s * (s - a) * (s - b) * (s - c))
    where
    s = (a + b + c) / 2

The variable s is half the perimeter of the triangle and it would be tedious to write it out four times in the argument of the square root function sqrt.

Simply write the definitions in sequence will not work...

heron a b c = sqrt (s * (s - a) * (s - b) * (s - c))  -- s is not defined here
s = (a + b + c) / 2                                   -- a, b, and c are not defined here

... because the variables a, b, c are only available in the right-hand side of the function heron, but the definition of s as written here is not part of the right-hand side of heron. To make it part of the right-hand side, we have to use the where keyword.

Note that both the where and the local definitions are indented by 4 spaces, to distinguish them from subsequent definitions. Here is another example that shows a mix of local and top-level definitions:

areaTriangleTrig  a b c = c * height / 2   -- use trigonometry
    where
    cosa   = (b ^ 2 + c ^ 2 - a ^ 2) / (2 * b * c)
    sina   = sqrt (1 - cosa ^ 2)
    height = b * sina
areaTriangleHeron a b c = result           -- use Heron's formula
    where
    result = sqrt (s * (s - a) * (s - b) * (s - c))
    s      = (a + b + c) / 2

ScopeEdit

If you look closely at the previous example, you'll notice that we have used the variable names a, b, c twice, once for each of the two area functions. How does that work?

Fortunately, the following fragment of code does not contain any unpleasant surprises:

Prelude> let r = 0
Prelude> let area r = pi * r ^ 2
Prelude> area 5
78.53981633974483

An "unpleasant surprise" here would have been getting 0 for the area because of the let r = 0 definition getting in the way. That does not happen because when you defined r the second time you are talking about a different r. This is something that happens in real life as well. How many people do you know that have the name John? What's interesting about people named John is that most of the time, you can talk about "John" to your friends, and depending on the context, your friends will know which John you are referring to. Programming has a notion similar to context, called scope.

We will not explain the technicalities behind scope (at least not now). Just keep in mind that the value of a parameter is strictly what you pass in when you call the function, regardless of what the variable was called in the function's definition.

SummaryEdit

  1. Variables store values. In fact, they store any arbitrary Haskell expressions.
  2. Variables do not change.
  3. Functions help you write reusable code.
  4. Functions can accept more than one parameter.

We also learned that comments are non-code text within a source file.


NotesEdit

  1. As this example shows, the names of variables may contain numbers as well as letters. Variables must begin with a lowercase letter, but for the rest, any string consisting of letter, numbers, underscore (_) or tick (') is allowed.