Table of Contents
Every expression and function in Haskell has a
type. For example, the value
True has the type Bool, while
"foo" has the type
String. The type of a value indicates that it
shares certain properties with other values of the same type.
For example, we can add numbers, and we can concatenate lists;
these are properties of those types. We say an expression
X”, or “is of type
Before we launch into a deeper discussion of Haskell's type system, let's talk about why we should care about types at all: what are they even for? At the lowest level, a computer is concerned with bytes, with barely any additional structure. What a type system gives us is abstraction. A type adds meaning to plain bytes: it lets us say “these bytes are text”, “those bytes are an airline reservation”, and so on. Usually, a type system goes beyond this to prevent us from accidentally mixing types up: for example, a type system usually won't let us treat a hotel reservation as a car rental receipt.
The benefit of introducing abstraction is that it lets us forget or ignore low-level details. If I know that a value in my program is a string, I don't have to know the intimate details of how strings are implemented: I can just assume that my string is going to behave like all the other strings I've worked with.
What makes type systems interesting is that they're not all equal. In fact, different type systems are often not even concerned with the same kinds of problems. A programming language's type system deeply colours the way we think, and write code, in that language.
There are three interesting aspects to types in Haskell: they are strong, they are static, and they can be automatically inferred. Let's talk in more detail about each of these ideas. When possible, we'll present similarities between concepts from Haskell's type system and related ideas in other languages. We'll also touch on the respective strengths and weaknesses of each of these properties.
When we say that Haskell has a strong type system, we mean that the type system guarantees that a program cannot contain certain kinds of errors. These errors come from trying to write expressions that don't make sense, such as using an integer as a function. For instance, if a function expects to work with integers, and we pass it a string, a Haskell compiler will reject this.
Another aspect of Haskell's view of strong typing is that it will not automatically coerce values from one type to another. (Coercion is also known as casting or conversion.) For example, a C compiler will automatically and silently coerce a value of type int into a float on our behalf if a function expects a parameter of type float, but a Haskell compiler will raise a compilation error in a similar situation. We must explicitly coerce types by applying coercion functions.
Strong typing does occasionally make it more difficult to write certain kinds of code. For example, a classic way to write low-level code in the C language is to be given a byte array, and cast it to treat the bytes as if they're really a complicated data structure. This is very efficient, since it doesn't require us to copy the bytes around. Haskell's type system does not allow this sort of coercion. In order to get the same structured view of the data, we would need to do some copying, which would cost a little in performance.
The huge benefit of strong typing is that it catches real bugs in our code before they can cause problems. For example, in a strongly typed language, we can't accidentally use a string where an integer is expected.
|Weaker and stronger types|
It is useful to be aware that many language communities have their own definitions of a “strong type”. Nevertheless, we will speak briefly and in broad terms about the notion of strength in type systems.
In academic computer science, the meanings of “strong” and “weak” have a narrowly technical meaning: strength refers to how permissive a type system is. A weaker type system treats more expressions as valid than a stronger type system.
For example, in Perl, the expression
The fireworks around type systems have their roots in ordinary English, where people attach notions of value to the words “weak” and “strong”: we usually think of strength as better than weakness. Many more programmers speak plain English than academic jargon, and quite often academics really are throwing brickbats at whatever type system doesn't suit their fancy. The result is often that popular Internet pastime, a flame war.
Having a static type system means that the compiler knows the type of every value and expression at compile time, before any code is executed. A Haskell compiler or interpreter will detect when we try to use expressions whose types don't match, and reject our code with an error message before we run it.
True && "false"<interactive>:1:8: Couldn't match expected type `Bool' against inferred type `[Char]' In the second argument of `(&&)', namely `"false"' In the expression: True && "false" In the definition of `it': it = True && "false"
This error message is of a kind we've seen
before. The compiler has inferred that the type of the
"false" is [Char].
(&&) operator requires each
of its operands to be of type Bool, and its left
operand indeed has this type. Since the actual type of
"false" does not match the required type,
the compiler rejects this expression as ill typed.
Static typing can occasionally make it difficult to write some useful kinds of code. In languages like Python, “duck typing” is common, where an object acts enough like another to be used as a substitute for it. Fortunately, Haskell's system of typeclasses, which we will cover in Chapter 6, Using Typeclasses, provides almost all of the benefits of dynamic typing, in a safe and convenient form. Haskell has some support for programming with truly dynamic types, though it is not quite as easy as in a language that wholeheartedly embraces the notion.
Haskell's combination of strong and static typing makes it impossible for type errors to occur at runtime. While this means that we need to do a little more thinking “up front”, it also eliminates many simple errors that can otherwise be devilishly hard to find. It's a truism within the Haskell community that once code compiles, it's more likely to work correctly than in other languages. (Perhaps a more realistic way of putting this is that Haskell code often has fewer trivial bugs.)
Programs written in dynamically typed languages require large suites of tests to give some assurance that simple type errors cannot occur. Test suites cannot offer complete coverage: some common tasks, such as refactoring a program to make it more modular, can introduce new type errors that a test suite may not expose.
In Haskell, the compiler proves the absence of type errors for us: a Haskell program that compiles will not suffer from type errors when it runs. Refactoring is usually a matter of moving code around, then recompiling and tidying up a few times until the compiler gives us the “all clear”.
A helpful analogy to understand the value of static typing is to look at it as putting pieces into a jigsaw puzzle. In Haskell, if a piece has the wrong shape, it simply won't fit. In a dynamically typed language, all the pieces are 1x1 squares and always fit, so you have to constantly examine the resulting picture and check (through testing) whether it's correct.
Finally, a Haskell compiler can automatically deduce the types of almost all expressions in a program. This process is known as type inference. Haskell allows us to explicitly declare the type of any value, but the presence of type inference means that this is almost always optional, not something we are required to do.
For example, instead of simply writing some code and running it to see if it works as you might expect in Python or Ruby, you'll first need to make sure that your program passes the scrutiny of the type checker. Why stick with the learning curve?
While strong, static typing makes Haskell safe, type inference makes it concise. The result is potent: we end up with a language that's both safer than popular statically typed languages, and often more expressive than dynamically typed languages. This is a strong claim to make, and we will back it up with evidence throughout the book.
Fixing type errors may initially feel like more work than if you were using a dynamic language. It might help to look at this as moving much of your debugging up front. The compiler shows you many of the logical flaws in your code, instead of leaving you to stumble across problems at runtime.
Furthermore, because Haskell can infer the types of your expressions and functions, you gain the benefits of static typing without the added burden of “finger typing” imposed by less powerful statically typed languages. In other languages, the type system serves the needs of the compiler. In Haskell, it serves you. The tradeoff is that you have to learn to work within the framework it provides.
We will introduce new uses of Haskell's types throughout this book, to help us to write and test practical code. As a result, the complete picture of why the type system is worthwhile will emerge gradually. While each step should justify itself, the whole will end up greater than the sum of its parts.
In the section called “First steps with types”, we introduced a few types. Here are several more of the most common base types.
The Int type is used for signed, fixed-width integer values. The exact range of values representable as Int depends on the system's longest “native” integer: on a 32-bit machine, an Int is usually 32 bits wide, while on a 64-bit machine, it is usually 64 bits wide. The Haskell standard only guarantees that an Int is wider than 28 bits. (There exist numeric types that are exactly 8, 16, and so on bits wide, in signed and unsigned flavours; we'll get to those later.)
An Integer value is a signed integer of unbounded size. Integers are not used as often as Ints, because they are more expensive both in performance and space consumption. On the other hand, Integer computations do not silently overflow, so they give more reliably correct answers.
Values of type Double are used for floating point numbers. A Double value is typically 64 bits wide, and uses the system's native floating point representation. (A narrower type, Float, also exists, but its use is discouraged; Haskell compiler writers concentrate more on making Double efficient, so Float is much slower.)
We have already briefly seen Haskell's notation
for types in the section called “First steps with types”. When we write a
type explicitly, we use the notation
MyType to say that
has the type MyType. If we omit the
:: and the type that follows, a Haskell compiler
will infer the type of the expression.
:type 'a''a' :: Char
'a' :: Char'a'
[1,2,3] :: Int<interactive>:1:0: Couldn't match expected type `Int' against inferred type `[a]' In the expression: [1, 2, 3] :: Int In the definition of `it': it = [1, 2, 3] :: Int
We don't use parentheses or commas to group or
separate the arguments to a function; merely writing the name of
the function, followed by each argument in turn, is enough. As
an example, let's apply the
function, which takes two arguments.
compare 2 3LT
compare 3 3EQ
compare 3 2GT
(compare 2 3) == LTTrue
compare 2 3 == LTTrue
compare (sqrt 3) (sqrt 6)LT
compare to the
results of applying
sqrt 3 and
respectively. If we omit the parentheses, it looks like we are
trying to pass four arguments to
instead of the two it accepts.
We've already seen the list type mentioned in the section called “Strings and characters”, where we found that Haskell represents a text string as a list of Char values, and that the type “list of Char” is written [Char].
tail *** Exception: Prelude.tail: empty list
As you can see, we can apply
tail to lists
of different types. Applying
head to a
[Char] value returns a Char value,
while applying it to a [Bool] value returns a
Bool value. The
doesn't care what type of list it deals with.
Because the values in a list can have any type, we call the list type polymorphic. When we want to write a polymorphic type, we use a type variable, which must begin with a lowercase letter. A type variable is a placeholder, where eventually we'll substitute a real type.
|Distinguishing type names and type variables|
When we talk about a list with values of a
specific type, we substitute that type for our type variable.
So, for example, the type [Int] is a list of values
of type Int, because we substituted
Similarly, the type [MyPersonalType] is a list of
values of type MyPersonalType. We can perform this
substitution recursively, too: [[Int]] is a list of
values of type [Int], i.e. a list of lists of
:type [[True],[False,False]][[True],[False,False]] :: [[Bool]]
|Lists are special|
Lists are the “bread and butter” of Haskell collections. In an imperative language, we might perform a task many items by iterating through a loop. This is something that we often do in Haskell by traversing a list, either by recursing or using a function that recurses for us. Lists are the easiest stepping stone into the idea that we can use data to structure our program and its control flow. We'll be spending a lot more time discussing lists in Chapter 4, Functional programming.
A tuple is a fixed-size collection of values, where each value can have a different type. This distinguishes them from a list, which can have any length, but whose elements must all have the same type.
To help to understand the difference, let's say we want to track two pieces of information about a book. It has a year of publication, which is a number, and a title, which is a string. We can't keep both of these pieces of information in a list, because they have different types. Instead, we use a tuple.
:type (True, "hello")(True, "hello") :: (Bool, [Char])
(4, ['a', 'm'], (16, True))(4,"am",(16,True))
There's a special type, (), that acts
as a tuple of zero elements. This type has only one value, also
(). Both the type and the value are
usually pronounced “unit”. If you are familiar
with C, () is somewhat similar to
Haskell doesn't have a notion of a one-element tuple. Tuples are often referred to using the number of elements as a prefix. A 2-tuple has two elements, and is usually called a pair. A “3-tuple” (sometimes called a triple) has three elements; a 5-tuple has five; and so on. In practice, working with tuples that contain more than a handful of elements makes code unwieldy, so tuples of more than a few elements are rarely used.
A tuple's type represents the number, positions, and types of its elements. This means that tuples containing different numbers or types of elements have distinct types, as do tuples whose types appear in different orders.
:type (False, 'a')(False, 'a') :: (Bool, Char)
:type ('a', False)('a', False) :: (Char, Bool)
In this example, the expression
'a') has the type (Bool, Char), which is
distinct from the type of
('a', False). Even though
the number of elements and their types are the same, these two
types are distinct because the positions of the element types
:type (False, 'a', 'b')(False, 'a', 'b') :: (Bool, Char, Char)
Our discussion of lists and tuples mentioned how
we can construct them, but little about how we do anything with
them afterwards. We have only been introduced to two list
functions so far,
A related pair of list functions,
two arguments. Given a number
n and a list,
take returns the first
elements of the list, while
all but the first
elements of the list. (As these functions take two arguments,
notice that we separate each function and its arguments
using white space.)
take 2 [1,2,3,4,5][1,2]
drop 3 [1,2,3,4,5][4,5]
If your background is in any of a number of other languages, each of these may look like an application of a function to two arguments. Under Haskell's convention for function application, each one is an application of a function to a single pair.
|Haskell tuples aren't immutable lists|
If you are coming from the Python world, you'll probably be used to lists and tuples being almost interchangeable. Although the elements of a Python tuple are immutable, it can be indexed and iterated over using the same methods as a list. This isn't the case in Haskell, so don't try to carry that idea with you into unfamiliar linguistic territory.
As an illustration, take a look at the type
In Haskell, function application is left
associative. This is best illustrated by example: the
a b c d is equivalent to
c) d). If we want to use one expression as an
argument to another, we have to use explicit parentheses to
tell the parser what we really mean. Here's an
head (drop 4 "azerty")'t'
We can read this as “pass the expression
drop 4 "azerty" as the argument to
head”. If we were to leave out
the parentheses, the offending expression would be similar to
passing three arguments to
Compilation would fail with a type error, as
head requires a single argument, a
:type lineslines :: String -> [String]
lines "the quick\nbrown fox\njumps"["the quick","brown fox","jumps"]
lines function splits a
string on line boundaries. Notice that its type signature gave
us a hint as to what the function might actually do: it takes
one String, and returns many. This is an
incredibly valuable property of types in a functional
A side effect introduces a dependency between the global state of the system and the behaviour of a function. For example, let's step away from Haskell for a moment and think about an imperative programming language. Consider a function that reads and returns the value of a global variable. If some other code can modify that global variable, then the result of a particular application of our function depends on the current value of the global variable. The function has a side effect, even though it never modifies the variable itself.
Side effects are essentially invisible inputs to, or outputs from, functions. In Haskell, the default is for functions to not have side effects: the result of a function depends only on the inputs that we explicitly provide. We call these functions pure; functions with side effects are impure.
:type readFilereadFile :: FilePath -> IO String
Now that we know how to apply functions, it's time we turned our attention to writing them. While we can write functions in ghci, it's not a good environment for this. It only accepts a highly restricted subset of Haskell: most importantly, the syntax it uses for defining functions is not the same as we use in a Haskell source file. Instead, we'll finally break down and create a source file.
-- file: ch03/add.hs add a b = a + b
On the left hand side of the
= is the
name of the function, followed by the arguments to the function.
On the right hand side is the body of the function. With our
source file saved, we can load it into ghci, and use our new
add function straight away. (The prompt
that ghci displays will change after you load your file.)
:load add.hs[1 of 1] Compiling Main ( add.hs, interpreted ) Ok, modules loaded: Main.
add 1 23
|What if ghci cannot find your source file?|
When you run ghci it may not be able to find your source
file. It will search for source files in whatever directory
it was run. If this is not the directory that your source
file is actually in, you can use ghci's
Haskell doesn't have a return
keyword, as a function is a single expression, not a sequence of
statements. The value of the expression is the result of the
function. (Haskell does have a function called
return, but we won't discuss it for a
while; it has a different meaning than in imperative
In Haskell, a variable provides a way to give a name to an expression. Once a variable is bound to (i.e. associated with) a particular expression, its value does not change: we can always use the name of the variable instead of writing out the expression, and get the same result either way.
If you're used to imperative programming languages, you're likely to think of a variable as a way of identifying a memory location (or some equivalent) that can hold different values at different times. In an imperative language we can change a variable's value at any time, so that examining the memory location repeatedly can potentially give different results each time.
The critical difference between these two notions of a variable is that in Haskell, once we've bound a variable to an expression, we know that we can always substitute it for that expression, because it will not change. In an imperative language, this notion of substitutability does not hold.
x = 10 x = 11 # value of x is now 11 print x
-- file: ch02/Assign.hs x = 10 x = 11
:load Assign[1 of 1] Compiling Main ( Assign.hs, interpreted ) Assign.hs:4:0: Multiple declarations of `Main.x' Declared at: Assign.hs:3:0 Assign.hs:4:0 Failed, modules loaded: none.
Like many other languages, Haskell has an
if expression. Let's see it in action, then
we'll explain what's going on. As an example, we'll write our
own version of the standard
function. Before we begin, let's probe a little into how
drop behaves, so we can replicate its
drop 2 "foobar""obar"
drop 4 "foobar""ar"
drop 4 [1,2]
drop 0 [1,2][1,2]
drop 7 
drop (-2) "foo""foo"
From the above, it seems that
drop returns the original list if the
number to remove is less than or equal to zero. Otherwise, it
removes elements until either it runs out or reaches the given
number. Here's a
myDrop function that has
the same behaviour, and uses Haskell's
expression to decide what to do. The
null function below checks whether a list
-- file: ch02/myDrop.hs myDrop n xs = if n <= 0 || null xs then xs else myDrop (n - 1) (tail xs)
:load myDrop.hs[1 of 1] Compiling Main ( myDrop.hs, interpreted ) Ok, modules loaded: Main.
myDrop 2 "foobar""obar"
myDrop 4 "foobar""ar"
myDrop 4 [1,2]
myDrop 0 [1,2][1,2]
myDrop 7 
myDrop (-2) "foo""foo"
We'll refer to the expressions after the
else keywords as
“branches”. The branches must have the same
if expression will also have this type. An
expression such as
if True then 1 else "foo" has
different types for its branches, so it is ill typed and will
be rejected by a compiler or interpreter.
Recall that Haskell is an expression-oriented
language. In an imperative language, it can make sense to
else branch from an
because we're working with statements,
not expressions. However, when we're working with
if that was missing an
else wouldn't have a result or type if the
predicate evaluated to
False, so it would
:type nullnull :: [a] -> Bool
:type (||)(||) :: Bool -> Bool -> Bool
|Operators are not special|
if expression spans
several lines. We align the
else branches under the
neatness. So long as we use some indentation, the exact
amount is not important. If we wish, we can write the
entire expression on a single line.
-- file: ch02/myDrop.hs myDropX n xs = if n <= 0 || null xs then xs else myDropX (n - 1) (tail xs)
def myDrop(n, elts): while n > 0 and elts: n = n - 1 elts = elts[1:] return elts
In our description of
we have so far focused on surface features. We need to go
deeper, and develop a useful mental model of how function
application works. To do this, we'll first work through a few
simple examples, until we can walk through the evaluation of the
myDrop 2 "abcd".
We've talked several times about substituting an expression for a variable, and we'll make use of this capability here. Our procedure will involve rewriting expressions over and over, substituting expressions for variables until we reach a final result. This would be a good time to fetch a pencil and paper, so that you can follow our descriptions by trying them yourself.
-- file: ch02/RoundToEven.hs isOdd n = mod n 2 == 1
Before we explain how evaluation proceeds in
Haskell, let us recap the sort of evaluation strategy used by
more familiar languages. First, evaluate the subexpression
1 + 2, to give
3. Then apply the
odd function with
3. Finally, evaluate
2 to give
1 == 1 to
In Haskell, the subexpression
1 + 2
is not reduced to the value
3. Instead, we create a “promise”
that when the value of the expression
isOdd (1 + 2)
is needed, we'll be able to compute it. The record that we
use to track an unevaluated expression is referred to as a
thunk. This is all
that happens: we create a thunk, and defer the actual
evaluation until it's really needed. If the result of this
expression is never subsequently used, we will not compute its
value at all.
Non-strict evaluation is often referred to as lazy evaluation.
print (myDrop 2 "abcd")"cd"
Our first step is to attempt to apply
myDrop to the values
"abcd". We bind the variable
n to the value
"abcd". If we substitute
these values into
myDrop's predicate, we
get the following expression.
:type 2 <= 0 || null "abcd"2 <= 0 || null "abcd" :: Bool
We then evaluate enough of the predicate to find
out what its value is. This requires that we evaluate the
(||) expression. To determine its value,
(||) operator needs to examine the
value of its left operand first.
2 <= 0False
:type False || null "abcd"False || null "abcd" :: Bool
If the left operand had evaluated to
(||) would not
need to evaluate its right operand, since it could not affect
the result of the expression. Since it evaluates to
evaluate the right operand.
False || FalseFalse
|Short circuiting for free|
Many languages need to treat the logical-or
operator specially so that it short circuits if its left
operand evaluates to
-- file: ch02/shortCircuit.hs newOr a b = if a then a else b
If we write an expression like
Were we to write a comparable function in, say, Python,
strict evaluation would bite us: both arguments would be
evaluated before being passed to
:type (2 - 1) <= 0 || null (tail "abcd")(2 - 1) <= 0 || null (tail "abcd") :: Bool
:type (2 - 1) <= 0(2 - 1) <= 0 :: Bool
2 - 11
1 <= 0False
:type null (tail "abcd")null (tail "abcd") :: Bool
:type (1 - 1) <= 0 || null (tail "bcd")(1 - 1) <= 0 || null (tail "bcd") :: Bool
:type (1 - 1) <= 0(1 - 1) <= 0 :: Bool
1 - 10
0 <= 0True
True || null (tail "bcd")True
:type tail "bcd"tail "bcd" :: [Char]
Remember, we're now inside our second recursive
myDrop. This application
tail "bcd". We return from the
application of the function, substituting this expression for
myDrop (1 - 1) (tail "bcd"), to become the result
of this application.
myDrop (1 - 1) (tail "bcd") == tail "bcd"True
myDrop (2 - 1) (tail "abcd") == tail "bcd"True
myDrop 2 "abcd" == tail "bcd"True
Notice that as we return from each successive recursive
application, none of them needs to evaluate the expression
"bcd": the final result of evaluating the original expression is
a thunk. The thunk is only
finally evaluated when ghci needs to print it.
myDrop 2 "abcd""cd"
If we want to fetch the last element of a list, we
last function. The value that it
returns must have the same type as the elements of the list, but
last operates in the same way no matter
what type those elements actually are.
:type lastlast :: [a] -> a
|Identifying a type variable|
Type variables always start with a lowercase letter. You can always tell a type variable from a normal variable by context, because the languages of types and functions are separate: type variables live in type signatures, and regular variables live in normal expressions.
It's common Haskell practice to keep the names of type variables very short. One letter is overwhelmingly common; longer names show up infrequently. Type signatures are usually brief; we gain more in readability by keeping names short than we would by making them descriptive.
This kind of polymorphism is called parametric polymorphism. The choice of naming is easy to understand by analogy: just as a function can have parameters that we can later bind to real values, a Haskell type can have parameters that we can later bind to other types.
|A little nomenclature|
When we see a parameterised type, we've already noted that the code doesn't care what the actual type is. However, we can make a stronger statement: it has no way to find out what the real type is, or to manipulate a value of that type. It can't create a value; neither can it inspect one. All it can do is treat it as a fully abstract “black box”. We'll cover one reason that this is important soon.
Parametric polymorphism is the most visible kind of polymorphism that Haskell supports. Haskell's parametric polymorphism directly influenced the design of the generic facilities of the Java and C# languages. A parameterised type in Haskell is similar to a type variable in Java generics. C++ templates also bear a resemblance to parametric polymorphism.
In mainstream object oriented languages, subtype polymorphism is more widespread than parametric polymorphism. The subclassing mechanisms of C++ and Java give them subtype polymorphism. A base class defines a set of behaviours that its subclasses can modify and extend. Since Haskell isn't an object oriented language, it doesn't provide subtype polymorphism.
Also common is coercion polymorphism, which allows a value of one type to be implicitly converted into a value of another type. Many languages provide some form of coercion polymorphism: one example is automatic conversion between integers and floating point numbers. Haskell deliberately avoids even this kind of simple automatic coercion.
This is not the whole story of polymorphism in Haskell: we'll return to the subject in Chapter 6, Using Typeclasses.
In the section called “Function types and purity”, we talked about
figuring out the behaviour of a function based on its type
signature. We can apply the same kind of reasoning to
polymorphic functions. Let's look again at
:type fstfst :: (a, b) -> a
The result type of
a. We've already mentioned that
parametric polymorphism makes the real type inaccessible:
fst doesn't have enough information to
construct a value of type
nor can it turn an
a into a
b. So the
only possible valid behaviour (omitting
infinite loops or crashes) it can have is to
return the first element of the pair.
There is a deep mathematical sense in which
any non-pathological function of type (a,b) ->
a must do exactly what
does. Moreover, this line of reasoning extends to more
complicated polymorphic functions. The paper
covers this procedure in depth.
It's been suggested that we should create a “theory box” for discussions of the deep stuff, and references to academic papers.
:type taketake :: Int -> [a] -> [a]
It's pretty clear that there's something going on
with an Int and some lists, but why are there two
-> symbols in the signature? Haskell
groups this chain of arrows from right to left; that is,
-> is right-associative. If we introduce
parentheses, we can make it clearer how this type signature is
-- file: ch02/Take.hs take :: Int -> ([a] -> [a])
From this, it looks like we ought to read the type signature as a function that takes one argument, an Int, and returns another function. That other function also takes one argument, a list, and returns a list of the same type as its result.
This is correct, but it's not easy to see what its
consequences might be. We'll return to this topic in the section called “Partial function application and currying”, once we've spent
a bit of time writing functions. For now, we can treat the type
following the last
-> as being the function's
return type, and the preceding types to be those of the
-- file: ch02/myDrop.hs myDrop :: Int -> [a] -> [a]
Haskell provides a standard function,
Because the result of applying a pure function can only
depend on its arguments, we can often get a strong hint of what
a pure function does by simply reading its name and
understanding its type signature. As an example, let's look at
:type notnot :: Bool -> Bool
Purity makes the job of understanding code easier. The behaviour of a pure function does not depend on the value of a global variable, or the contents of a database, or the state of a network connection. Pure code is inherently modular: every function is self-contained, and has a well-defined interface.
A non-obvious consequence of purity being the default is that working with impure code becomes easier. Haskell encourages a style of programming in which we separate code that must have side effects from code that doesn't need them. In this style, impure code tends to be simple, with the “heavy lifting” performed in pure code.
Much of the risk in software lies in talking to the outside world, be it coping with bad or missing data, or handling malicious attacks. Because Haskell's type system tells us exactly which parts of our code have side effects, we can be appropriately on our guard. Because our favoured coding style keeps impure code isolated and simple, our “attack surface” is small.
In this chapter, we've had a whirlwind overview of Haskell's type system and much of its syntax. We've read about the most common types, and discovered how to write simple functions. We've been introduced to polymorphism, conditional expressions, purity, and about lazy evaluation.
This all amounts to a lot of information to absorb. In Chapter 3, Defining Types, Streamlining Functions, we'll build on this basic knowledge to further enhance our understanding of Haskell.
 “If it walks like a duck, and quacks like a duck, then let's call it a duck.”
 Occasionally, we need to give the compiler a little information to help it to make a choice in understanding our code.
 The environment in which ghci operates is called the IO monad. In Chapter 7, I/O, we will cover the IO monad in depth, and the seemingly arbitrary restrictions that ghci places on us will make more sense.
 The terms “non-strict” and “lazy” have slightly different technical meanings, but we won't go into the details of the distinction here.