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've 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 in a context. For instance, consider the following calculation

ghci> 3.1416 * 5^2
78.53999999999999

This 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. We want to delegate mindless repetition and rote memorization to a machine so our minds are free to think about larger ideas. In this case, Haskell already includes a variable named pi that stores over a dozen digits of \pi for us.

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

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

Haskell source filesEdit

When we write code we want to use more than momentarily, we save it in a Haskell source file.

Let's make a directory (i.e. a folder) on your computer for saving your Haskell files — call it "HaskellWikibook". We will use this to save our test projects as we learn and try the exercises.

Haskell source files have the file extension .hs which stands for "Haskell". Depending on your setup, you can either make empty files and then open them in a text editor or you will start the text editor and save files after you have entered some code. If you need suggestions for text editors appropriate for coding, the Wikipedia article on text editors is a reasonable place to start. Most Haskell programmers probably use Vim or Emacs. Proper source code editors will provide syntax highlighting, a feature which colourises code in relevant ways so as to make it easier to read.

To run code from saved .hs files, you will need to load the files into GHCi. To make things easiest, navigate to your HaskellWikibook directory in the terminal before running ghci or use the change-directory command within ghci (e.g. :cd HaskellWikibook). Now, you can load .hs files in ghci with :load Filename (or shorter version :l Filename). For example:

Prelude> :cd c:\HaskellWikibook
Prelude> :load somefile.hs
Compiling Main             ( somefile.hs, interpreted )
Ok, modules loaded: Main.
*Main>

In your /HaskellWikibook directory, make a new file called Varfun.hs with the following code:

r = 5.0

Make sure that there are no spaces at the beginning of the line because Haskell is sensitive to whitespace (more about indentation later).

Now, load your Varfun.hs file in ghci:

Prelude> :l Varfun

If GHCi gives an error like Could not find module 'Varfun.hs', you probably are in the wrong directory (see the Getting set up chapter for more details on loading files).

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

To experienced programmers: GHC can also be used as a compiler (that is, you could use GHC to convert your Haskell files into a stand-alone program that can be run without running the interpreter). How to do so will be explained much later.


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 may be somewhat confusing to readers otherwise. Of course, too many comments make programs harder to read, and comments must be carefully updated whenever corresponding code is changed or else the comments may become outdated and incorrect.

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 changeable locations in a computer's memory. You would definitely never see a variable in a math classroom change its value within the same problem. In Haskell, the compiler will give an error for the code above: "multiple declarations of r". People familiar with imperative programming, which involves explicitly telling the computer what to do, may be accustomed to read this as first setting r = 5 and then changing it to r = 2. In contrast, functional programming languages are higher-level abstractions where we let the compiler do the work of figuring out what to do actually 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 this is radically different from imperative languages). If the initial r had any value, then r = r + 1 in Haskell would bring an error message. Consider that it is invalid to say mathematically that  5 = 5 + 1 .

Because their values don't 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. Admittedly, this key feature of purely functional programming requires a very different approach and different mindset from imperative programming.

FunctionsEdit

Now, suppose that we have multiple circles with different radii whose areas we want to calculate. For instance, let's calculate the area of another circle with radius 3. If we continue from the program we wrote above, we already defined r = 5.0. We could change the entire program of course, but then we will lose the ability to calculate the first circle. W could make a new variable r2, and then we would need another new variable area2 to use our new r2.[1] Our new source file for both circles is:

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

Clearly, this 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 a argument which we name r:

area r = pi * r^2

The syntax here is very specific: the function name comes first (in our example, that's "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 this 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 call a lot of functions, 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 because multiplication is done before addition
5 * (3 + 2)     -- 5 * 5 = 25 because parentheses define the groups to be evaluated
area 5 * 3      -- (area 5) * 3
area (5 * 3)    -- area 15

Notice how Haskell functions take precedence over all other operators. Even multiplication is secondary to functions.


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. 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.
  • Write functions that can help calculate approximately how many blocks of stone are used the famous pyramids at Giza. Hint: you'll need to know 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.

It won't work to just write the definitions in sequence

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 different 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 something similar to context, called scope.

We won't explain the technicalities behind scope (at least not now), just know 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. It's only *within* the same function that we can't define the same variable to multiple values.

SummaryEdit

  1. Variables store values. In fact, they store any arbitrary Haskell expressions.
  2. Variables do not change within a function.
  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 indicates, 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.


Last modified on 24 April 2014, at 02:28