Haskell/Variables and functions

From Wikibooks, the open-content textbooks collection

Jump to: navigation, search

(All the examples in this chapter can be typed into a Haskell source file and evaluated by loading that file into GHC or Hugs.)

Variables and functions (Solutions)

Contents

Haskell Basics

Getting set up
Variables and functions
Lists and tuples
Next steps
Type basics
Simple input and output
Type declarations

[edit] Variables

Previously, we saw how to do simple arithmetic operations like addition and subtraction. Pop quiz: what is the area of a circle whose radius is 5 cm? No, don't worry, you haven't stumbled through the Geometry wikibook by mistake. The area of our circle is πr2 where r is our radius (5cm) and π, for the sake of simplicity, is 3.14. So let's try this out in GHCi:

   ___         ___ _
  / _ \ /\  /\/ __(_)
 / /_\// /_/ / /  | |      GHC Interactive, version 6.4.1, for Haskell 98.
/ /_\\/ __  / /___| |      http://www.haskell.org/ghc/
\____/\/ /_/\____/|_|      Type :? for help.

Loading package base-1.0 ... linking ... done.
Prelude>

So let's see, we want to multiply pi (3.14) times our radius squared, so that would be

Prelude> 3.14 * 5^2
78.5

Great! Well, now since we have these wonderful, powerful computers to help us calculate things, there really isn't any need to round pi down to 2 decimal places. Let's do the same thing again, but with a slightly longer value for pi

Prelude> 3.14159265358979323846264338327950 * (5 ^ 2)
78.53981633974483

Much better, so now how about giving me the circumference of that circle (hint: r)?

Prelude> 2 * 3.14159265358979323846264338327950 * 5
31.41592653589793

Or how about the area of a different circle with radius 25 (hint: πr2)?

Prelude> 3.14159265358979323846264338327950 * (25 ^ 2)
1963.4954084936207

What we're hoping here is that sooner or later, you are starting to get sick of typing (or copy-and-pasting) all this text into your interpreter (some of you might even have noticed the up-arrow and Emacs-style key bindings to zip around the command line). Well, the whole point of programming, we would argue, is to avoid doing stupid, boring, repetitious work like typing the first 20 digits of pi a million times. What we really need is a means of remembering the value of pi:

Prelude> let pi = 3.14159265358979323846264338327950
Note

If this command does not work, you are probably using hugs instead of GHCi, which expects a slightly different syntax. Check out Next steps on how to load code from source files.

Here you are literally telling Haskell to: "let pi be equal to 3.14159...". This introduces the new variable pi, which is now defined as being the number 3.14159265358979323846264338327950. This will be very handy because it means that we can call that value back up by just typing pi again:

Prelude> pi
3.141592653589793

Don't worry about all those missing digits; they're just skipped when displaying the value. All the digits will be used in any future calculations.

Having variables takes some of the tedium out of things. What is the area of a circle having a radius of 5 cm? How about a radius of 25cm?

Prelude> pi * 5^2
78.53981633974483
Prelude> pi * 25^2
1963.4954084936207
Note

What we call "variables" in this book are often referred to as "symbols" in other introductions to functional programming. This is because other languages, namely the more popular imperative languages have a very different use for variables: keeping track of state. Variables in Haskell do no such thing; they store a value and an immutable one at that. That is to say, "variables" here are just names for values (or expressions that result in values), but they do not vary: you don't change the stored value later on.

[edit] Types

Following the previous example, you might be tempted to try storing a value for that radius. Let's see what happens:

Prelude> let r = 25
Prelude> 2 * pi * r

<interactive>:1:9:
    Couldn't match `Double' against `Integer'
      Expected type: Double
      Inferred type: Integer
    In the second argument of `(*)', namely `r'
    In the definition of `it': it = 2 * pi * r

Whoops! You've just run into a programming concept known as types. Types are a feature of many programming languages which are designed to catch some of your programming errors early on so that you find out about them before it's too late. We'll discuss types in more detail later on in the Type basics chapter, but for now it's useful to think in terms of plugs and connectors. For example, many of the plugs on the back of your computer are designed to have different shapes and sizes for a purpose. This is partly so that you don't inadvertently plug the wrong bits of your computer together and blow something up. Types serve a similar purpose, but in this particular example, well, types aren't so helpful.

The tricky bit here is that numbers like 25 can either be interpreted as being Double or Integer (among other types)... but for lack of other information, Haskell has "guessed" that its type must be Integer (which cannot be multiplied with a Double). To work around this, we simply insist that it is to be treated as a Double

Prelude> let r = 25 :: Double
Prelude> 2 * pi * r
157.07963267948966

Note that Haskell only has this "guessing" behaviour in contexts where it does not have enough information to infer the type of something. As we will see below, most of the time, the surrounding context gives us all of the information that is needed to determine, say, if a number is to be treated as an Integer or not.

Note

There is actually a little bit more subtlety behind this problem. It involves a language feature known as the monomorphism restriction. You don't actually need to know about this for now, so you can skip over this note if you just want to keep a brisk pace. Instead of specifying the type Double, you also could have given it a polymorphic type, like Num a => a, which means "any type a which belongs in the class Num". The corresponding code looks like this and works just as seamlessly as before:

Prelude> let r :: Num a => a ; r = 25
Prelude> 2 * pi * r
157.07963267948966

Haskell could in theory assign such polymorphic types systematically, instead of defaulting to some potentially incorrect guess, like Integer. But in the real world, this could lead to values being needlessly duplicated or recomputed. To avoid this potential trap, the designers of the Haskell language opted for a more prudent "monomorphism restriction". It means that values may only have a polymorphic type if it can be inferred from the context, or if you explicitly give it one. Otherwise, the compiler is forced to choose a default monomorphic (i.e. non-polymorphic) type. This feature is somewhat controversial. It can even be disabled with the GHC flag (-fno-monomorphism-restriction), but it comes with some risk for inefficiency. Besides, in most cases, it is just as easy to specify the type explicitly.

[edit] Variables within variables

Variables can contain much more than just simple values such as 3.14. Indeed, they can contain any Haskell expression whatsoever. So, if we wanted to keep around, say the area of a circle with radius of 5, we could write something like this:

Prelude> let area = pi * 5^2

What's interesting about this is that we've stored a complicated chunk of Haskell (an arithmetic expression containing a variable) into yet another variable.

We can use variables to store any arbitrary Haskell code, so let's use this to get our acts together.

Prelude> let r = 25.0
Prelude> let area2 = pi * r ^ 2
Prelude> area2
1963.4954084936207

So far so good.

Prelude> let r = 2.0
Prelude> area2
1963.4954084936207
Variables do not vary

Wait a second, why didn't this work? That is, why is it that we get the same value for area as we did back when r was 25? The reason this is the case is that variables in Haskell do not change[1]. What actually happens when you defined r the second time is that 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 scope (at least not now), but Haskell's lexical scope is the magic that lets us define two different r and always get the right one back. Scope, however, does not solve the current problem. What we want to do is define a generic area function that always gives you the area of a circle. What we could do is just define it a second time:

Prelude> let area3 = pi * r ^ 2
Prelude> area3
12.566370614359172

But we are programmers, and programmers loathe repetition. Is there a better way?

[edit] Functions

What we are really trying to accomplish with our generic area is to define a function. Defining functions in Haskell is dead-simple. It is exactly like defining a variable, except with a little extra stuff on the left hand side. For instance, below is our definition of pi, followed by our definition of area:

Prelude> let pi = 3.14159265358979323846264338327950
Prelude> let area r = pi * r ^ 2

To calculate the area of our two circles, we simply pass it different values:

Prelude> area 5
78.53981633974483
Prelude> area 25
1963.4954084936207

Functions allow us to make a great leap forward in the reusability of our code. But let's slow down for a moment, or rather, back up to dissect things. See the r in our definition area r = ...? This is what we call a parameter. A parameter is what we use to provide input to the function. It's something that the function depends on, like here the area depends on the radius. When Haskell is interpreting the function, the value of its parameter must come from the outside. In the case of area, the value of r is 5 when you say area 5, but it is 25 if you say area 25.

Exercises

Say I type something in like this (don't type it in yet):

Prelude> let r = 0
Prelude> let area r = pi * r ^ 2
Prelude> area 5
  1. What do you think should happen? Are we in for an unpleasant surprise?
  2. What actually happens? Why? (Hint: remember what was said before about "scope")

[edit] Scope and parameters

Warning: this section contains spoilers to the previous exercise

We hope you have completed the very short exercise (I would say thought experiment) above. 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 the value 0. This is just a consequence of what we wrote above, namely the value of a parameter is strictly what you pass in when you call the function. And that is directly a consequence of our old friend scope. Informally, the r in let r = 0 is not the same r as the one inside our defined function area - the r inside area overrides the other r; you can think of it as Haskell picking the most specific version of r there is. If you have many friends all named John, you go with the one which just makes more sense and is specific to the context; similarly, what value of r we get depends on the scope.

[edit] Multiple parameters

Another thing you might want to know about functions is that they can accept more than one parameter. Say for instance, you want to calculate the area of a rectangle. This is quite simple to express:

Prelude> let areaRect l w = l * w
Prelude> areaRect 5 10
50

Or say you want to calculate the area of a right triangle \left(A = \frac{bh}{2}\right):

Prelude> let areaRtTriangle b h = (b * h) / 2
Prelude> areaRtTriangle 3 9
13.5

Passing parameters in is pretty straightforward: you just give them in the same order that they are defined. So, whereas areaRtTriangle 3 9 gives us the area of a triangle with base 3 and height 9, areaRtTriangle 9 3 gives us the area with the base 9 and height 3.

Exercises
Write a function to calculate the volume of a box. A box has width, height and depth. You have to multiply them all to get the volume.

[edit] Functions within functions

To further cut down the amount of repetition, it is possible to call functions from within other functions. A simple example showing how this can be used is to create a function to compute the area of a square. We can think of a square as a special case of a rectangle (the area is still the width multiplied by the length); however, we also know that the width and length are the same, so why should we need to type it in twice?

Prelude> let areaRect l w = l * w
Prelude> let areaSquare s = areaRect s s
Prelude> areaSquare 5
25
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.

[edit] Summary

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

[edit] Notes

  1. For readers with prior programming experience: Variables don't change? I only get constants? Shock! Horror! No... trust us, as we hope to show you in the rest of this book, you can go a very long way without changing a single variable! In fact, this non-changing of variables makes life easier because it makes programs so much more predictable.


Personal tools
Create a book