User:Duplode/Haskell leftovers

Old VarFun section on where
So far we have seen two sorts of  in the examples: a variable, which stores some value of our choice, and a parameter of a function (say,  ). But what would happen if we use both at the same time? 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

What do you think that should happen here? Will  work as expected or are we in for an unpleasant surprise? Think about an answer, then test it, see if it was what you expected and then follow on with the text...

(...)

Scope and parameters

 * Warning: this section contains spoilers to the previous "exercise"

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 because of the  definition getting in the way. That does not happen because 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 the technicalities behind 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 depending on context.

Thanks to scope, the value of a parameter is strictly what you pass in when you call the function. Informally, we could say the  in   is not the same   as the one inside our defined function   - the   inside   overrides the other  ; you can think of it as Haskell picking the most specific version of   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.

where clauses
''Readers: don't worry about this missing section. As of now where clauses are explained later on, and there is no need to rush over there to check them. This is just a placeholder for when they are moved to this section in the near future.

Old VarFun/Type basics introduction to types
Let us have another look at the circumference examples in Variables and functions. While playing around with the expressions you might be tempted to try storing a plain value such as  or   and using it in other expressions. Let's see what happens:

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

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. One simple analogy for us to start with are 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  can be interpreted as being   or   (  is one of several types for representing real numbers). For lack of other information, Haskell has "guessed" that its type must be  (which cannot be multiplied with a  ). To work around this, we simply insist that it is to be treated as a 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  or not.

Boolean values
One of the other types found in most languages is called a Boolean, or Bool for short. This has two values: true and false. This turns out to be very useful. For example, consider a program that would ask the user for a name then look that name up in a spreadsheet. It might be useful to have a function,, which indicates whether or not the name of the user exists in the spreadsheet. If it does exist, you could say that it is true that the name exists, and if not, you could say that it is false that the name exists. So we've come across Bools. The two values of bools are, as we've mentioned, true and false. In Haskell, boolean values are capitalized (for reasons that will later become clear):

This shouldn't need too much explaining at this point. The values True and False are categorized as Booleans, that is to say, they have type Bool.

Numeric types
If you've been playing around with typing :t on all the familiar values you've come across, perhaps you've run into the following complication:

Prelude> :t 5 5 :: (Num t) => t

We'll defer the full explanation of this until later. The short version of the story is that there are many different types of numbers (fractions, whole numbers, etc) and  can be any one of them. This weird-looking type relates to a Haskell feature called type classes, which we will be playing with later in this book.

Type signatures in code
As of this revision of Type Basics.

''Readers: As of now there are a few forward references in this section. We are in the process of sorting out some module sequencing issues, which means that problem will be solved soon. In the meantime, you might prefer to go through the next module, "Lists and tuples" and then return here to finish this one.''

Now we've explored the basic theory behind types as well as how they apply to Haskell, let's look at how they appear in actual code. The first thing to point out is that it is a good practice to annotate every function with its associated type. For an applied example, let us have a look at a very small module (modules in Haskell are just a neat way of making a library by packing functions for usage in other programs. No need to worry with details now).

This tiny library provides two handy string manipulation functions. converts a string to uppercase,  to lowercase, and   capitalizes the first letter of every word. Each of these functions takes a String as argument and evaluates to another String. For now, don't worry with how the code actually works, as it is obviously full of features we haven't discussed yet. Anyway - and specially because we don't understand the implementation yet - stating the type for these functions explicitly would make their roles more obvious. For example, most Haskellers would write the above module something like the following:

The added signatures inform the type of the functions to both the interpreter/compiler and the human readers. Note that you can group type signatures together into a single type signature (like ours for  and   above) if the two functions share the same type.

Type inference
We just said that type signatures tell the interpreter (or compiler) what the function type is. However, in the modules before this one you wrote perfectly good Haskell code without any signatures, and it has been accepted by GHC/GHCi. That means it's not strictly necessary to add type signatures. But that doesn't mean that if you don't add type signatures Haskell simply forgets about typing altogether! Indeed, when you didn't tell Haskell the types of your functions and variables, it figured them out. This is a process called type inference, in which the compiler starts with the types of things it knows, then works out the types of the rest of the things. Type inference for Haskell is decidable, which means that the compiler can always work out the types, even if you never write them in. Let's look at some examples to see how the compiler works out types.

This function takes a character and sees if it is an  character. The compiler derives the type for  through a process like the following:

The first line indicates that the type of the function, which tests for equality, is   (you can think of the a as a placeholder which stands for any type - (==) will accept any two arguments as long as they are of the same type. That is called a polymorphic function). The compiler also knows that something in single quotes has type Char, so clearly the literal  has type Char. Next, the compiler starts replacing the type variables in the signature for  with the types it knows. Note that in one step, we went from  to , because the type variable   was used in both the first and second argument, so they need to be the same. And so we arrive at a function that takes a single argument (whose type we don't know yet, but hold on!) and applies it as the first argument to. We have a particular instance of the polymorphic type of, that is, here, we're talking about   because we know that we're comparing Chars. Therefore, as  and we're feeding the parameter into the first argument to , we know that the parameter has the type of Char. Phew!

But wait, we're not finished yet... What's the return type of the function? Thankfully, this bit is a bit easier. We've fed two Chars into a function which (in this case) has type, so we must have a Bool. Note that the return value from the call to  becomes the return value of our   function.

So, let's put it all together. is a function that takes a single argument. We discovered that this argument must be of type Char. Finally, we derived that we return a Bool. So, we can confidently say that  has the type:

And, indeed, if you miss out the type signature, the Haskell compiler will discover this on its own, using exactly the same method we've just run through.

So if type signatures are optional, why bother with them at all? Here are a few reasons:


 * Documentation: the most prominent reason is that it makes your code easier to read. With most functions, the name of the function along with the type of the function is sufficient to guess at what the function does. Of course, you should always comment your code properly too, but having the types clearly stated helps a lot anyway.
 * Debugging: if you annotate a function with a type, then make a typo in the body of the function, the compiler will tell you at compile-time that your function is wrong. Leaving off the type signature could have the effect of allowing your function to compile, and the compiler would assign it an erroneous type. You wouldn't know until you ran your program that it was wrong. In fact, since this is such a crucial point we will explore it some more.

Types prevent errors
Imagine you have a few functions set up like the following:

Our function  takes a Bool. When  receives its arguments, it knows, because of the type signature we've annotated it with, that the first element of the pair is a Bool. So, we could extract this using  and pass that into , and this would work, because the type of the first element of the pair and the type of the argument to   are the same.

This is really central to typed languages. When passing expressions around you have to make sure the types match up like they did here. If they don't, you'll get type errors when you try to compile; your program won't typecheck. This is really how types help you to keep your programs bug-free. To take a very trivial example:

Having that line as part of your program will make it fail to compile, because you can't add two strings together! In all likelihood the intention was to use the similar-looking string concatenation operator, which joins two strings together into a single one:

An easy typo to make, but because you use Haskell, it was caught when you tried to compile. You didn't have to wait until you ran the program for the bug to become apparent.

This was only a simple example. However, the idea of types being a system to catch mistakes works on a much larger scale too. In general, when you make a change to your program, you'll change the type of one of the elements. If this change isn't something that you intended, or has unforeseen consequences, then it will show up immediately. A lot of Haskell programmers remark that once they have fixed all the type errors in their programs, and their programs compile, that they tend to "just work": run flawlessly at the first time, with only minor problems. Run-time errors, where your program goes wrong when you run it rather than when you compile it, are much rarer in Haskell than in other languages. This is a huge advantage of having a strong type system like Haskell does.