Unit 1: Getting started
Introduction
Haskell is a functional programming language defined in
The Haskell 2010 Report.
The Glasgow Haskell Compiler (GHC) is
available free of charge
and is very easy to install on your own computer.
(It is also very easy to uninstall!)
There are slight differences between the language defined in the Report
and that implemented by the GHC; these are mentioned in
section 14, "Known bugs and infelicities", of the
GHC User's Guide.
On these pages, I describe how the GHC works
in interactive mode; this is known as "GHCi".
On an Apple Mac, you invoke GHCi by using the command ghci
in the Terminal application (available in the Utilities folder).
Doing this results in the following being output:
GHCi, version 9.0.1: https://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from ...
ghci>
(The ellipsis is filled in with the name of the file containing configuration information.) You can now type expressions to be evaluated. For example, you can use GHCi like a pocket calculator to evaluate simple arithmetical expressions:
ghci> 142
142
ghci> 6 * 7
42
ghci> 8 + 39
47
ghci> (34 - 21) * (6 + 98)
1352
As well as asking GHCi to evaluate the expressions you input after the prompt, there are several system commands that can also be entered. These all begin with a colon. Amongst the most useful of these are the following:
:q
|
end the current session |
:?
|
get help about system commands |
:e file
|
edit the file named file |
:t exp
|
get the type of the function called exp |
If all you could do with Haskell is use it to evaluate arithmetical expressions, it wouldn't be very interesting or useful. However, Haskell is a programming language, so you can write your own programs which, in Haskell, means defining your own functions. (Some authors call a Haskell program a script.) In order to explain how this is done, I first need to introduce some arithmetical and Boolean-valued operators. Whenever you invoke GHCi, a large number of predefined functions are loaded which are available for you to use without further ado. This collection of functions is known as the standard Prelude. Many additional libraries of useful functions are also available for specific purposes. Unless otherwise mentioned, all the functions I discuss on these pages are included in the standard Prelude.
Integral types
Haskell has two integral types, namely Int
and Integer
.
Int
is the type of limited-precision integers;
this means that there is a smallest integer of type Int
,
namely minBound
, and a greatest integer of type Int
,
namely maxBound
.
Integer
is the type of arbitrary-precision integers which has
neither a smallest nor a largest member.
Note that using Int
leads to more efficient programs than using
Integer
.
Basic arithmetical operators
Haskell has the following basic binary infix arithmetical operators:
+
|
addition |
-
|
subtraction |
*
|
multiplication |
^
|
exponentiation |
Addition, subtraction and multiplication are all left associative and
multiplication has a higher precedence than addition and subtraction
which have the same precedence as each other.
Thus,
2 + 3 + 7 * 11
is equal to 82.
Note that Haskell also has the unary prefix operator -
for indicating negative numbers.
A quirk of Haskell is that in many contexts negative numbers have to be
enclosed in parentheses.
If you get a strange error message when using a negative number,
try enclosing it in parentheses before looking for other causes of the problem.
Note also that /
is not the symbol for integer division;
the operator /
is used to denote the operation of dividing
floating-point numbers.
Haskell also has the following binary prefix operators:
min
|
minimum |
max
|
maximum |
gcd
|
greatest common divisor |
lcm
|
lowest common multiple |
div
|
integer division |
mod
|
remainder after integer division |
The operators div
and mod
obey the following identity:
(div x y) * y + (mod x y) = x
Boolean datatype Bool
Haskell also has a Boolean datatype Bool
,
two of whose values are True
and False
.
It has the usual binary infix Boolean-valued operators:
==
|
equal to |
/=
|
not equal to |
<
|
less than |
<=
|
less than or equal to |
>
|
greater than |
>=
|
greater than or equal to |
There are also several logical operators:
||
(or),
&&
(and) and
not
(not).
The operators ||
and &&
are both
binary infix operators, whereas not
is a prefix unary operator.
Both ||
and &&
are right associative and &&
has a higher precedence than
||
.
Thus,
False && False && False || True
evaluates to True
.
Defining functions
Haskell is a typed language and it is good programming practice, though not a requirement enforced by the GHCi system, to always include the type of any function that you define. Functions are defined like this:
sqInt :: Int -> Int
sqInt x = x * x
smallerInt :: Int -> Int -> Int
smallerInt x y
| x <= y = x
| otherwise = y
The first line here
sqInt :: Int -> Int
indicates the type of the function sqInt
that is being defined.
It states that this function is of type Int -> Int
,
that is to say, it takes an integer as its argument and it returns an integer as its value.
The symbol ::
can be read as "is of type".
The second line contains the actual definition of the function sqInt
.
The single equals sign =
indicates that a function is being defined.
Note that the functions you define have to begin with a lowercase letter.
The definition of smallerInt
introduces several new ideas.
Informally, we would say that smallerInt
takes two arguments
and returns the smaller of them, but all prefix functions in Haskell,
including all user-defined functions, are one-place functions.
Thus, what smallerInt
really does is to take a single integral
argument and return a function which itself takes an integral argument
and this function returns the smallest of those two arguments.
Function application associates to the left,
so f x y
means
(f x) y
,
and it has the highest precedence of any operator.
Note that the arrow in the type Int -> Int -> Int
associates to the right; this means it is equivalent to
Int -> (Int -> Int)
.
The definition of smallerInt
also shows how conditionals are represented in Haskell.
The value of smallerInt x y
is x
if the guard
x <= y
evaluates to True
,
otherwise it is y
.
The keyword otherwise
is just a synonym for True
.
Haskell also has a conditional operator, so smallerInt
could also be defined like this:
smallerInt :: Int -> Int -> Int
smallerInt x y = if (x <= y) then x else y
If you put the definition of sqInt
given above and
the non-conditional definition of smallerInt
into a file called, say,
begin.hs
,
you can load them into the Haskell system by issuing the command
ghci begin.hs
.
(Files containing Haskell programs typically have the file extension
.hs
.)
Issuing the command ghci begin.hs
results in the following:
GHCi, version 9.0.1: https://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from ...
[1 of 1] Compiling Main ( begin.hs, interpreted )
Ok, one module loaded.
(The ellipsis is filled with the name of the file containing
configuration information.)
You can now make use of the functions defined in the file begin.hs
like this:
ghci> sqInt 8
64
ghci> smallerInt 7 18
7
ghci> sqInt (smallerInt 18 (3 * 9))
324
Note that an attempt to evaluate
smallerInt -5 6
results in an error message.
If you want to find the smaller of -5
and 6
,
you need to evaluate
smallerInt (-5) 6
.
If you now issue the system command :e
,
without any filename, you will open an editor into which begin.hs
has been loaded.
To ensure that your favourite editor is used,
before issuing the command :e
,
enter the command :set editor vim
.
(Replace vim
with the name of any other editor.)
To avoid having to issue this :set
command every session
you can put it into a .ghci
file which should be placed in your home
directory (folder).
Comments
There are two ways of adding comments to a Haskell program.
Everything on a line following two hyphens --
is regarded as a comment by GHCi as is everything inside the symbols
{-
and -}
;
these symbols can be nested.
Recursive definitions
Repetition or iteration is obtained by using recursion.
The following function, for example, given an integer x
as its argument, returns the sum of all integers between 0
and x
:
sumInt :: Int -> Int
sumInt x
| x == 0 = 0
| otherwise = x + sumInt (x - 1)
This definition can be found in
sum1.hs
.
Haskell supports pattern-matching.
This means that not only variables are allowed in function definitions.
Using pattern-matching sumInt
can also be defined like this:
sumInt :: Int -> Int
sumInt 0 = 0
sumInt x = x + sumInt (x - 1)
This definition can be found in
sum2.hs
.
It fails miserably, however, for negative arguments.
These can be caught as follows:
sumInt :: Int -> Int
sumInt 0 = 0
sumInt x
| x < 0 = error "sumInt undefined when x < 0"
| otherwise = x + sumInt (x - 1)
This definition can be found in
sum3.hs
.
It uses the function error
of type String -> a
,
that is to say, it takes a string argument, where a string is a list of characters,
and returns a value of any type.
The error
function throws an exception; it terminates normal evaluation and
returns you to the Haskell prompt after displaying the message given to it as
its argument.
Defining the Fibonacci numbers
As another example of function definition, I will define the Fibonacci numbers. The first definition uses explicit guards:
fib :: Int -> Int
fib i
| i == 0 = 0
| i == 1 = 1
| otherwise = fib (i - 1) + fib (i - 2)
The second definition uses pattern-matching:
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib i = fib (i - 1) + fib (i - 2)
The following definition should be used if you need to know that an attempt
has been made to evaluate fib
with a negative argument:
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib i
| i < 0 = error "fib undefined when i < 0"
| otherwise = fib (i - 1) + fib (i - 2)
Local definition
Haskell supports local definitions, for example:
foo :: Int -> Int
foo x
| x > 0 = p + q
| x <= 0 = p - q
where p = x^2 + 1
q = 3 * x^3 - 5
Local definitions obey Landin's offside rule:
"The southeast quadrant that just contains the phrase's first symbol
must contain the entire phrase, except possibly for bracketed subexpressions."
This means that a local definition introduced by means of a
where
-clause has to start either below the letter
w
or to the right of it or both.
A further constraint is that the expressions in the local definition
have to be aligned:
the p
and q
have to be indented by the same amount.
Haskell also allows the use of curly brackets and semicolons to introduce
where
-clauses.
Thus, the above definition of foo
could be written like this:
foo :: Int -> Int
foo x
| x > 0 = p + q
| x <= 0 = p - q
where { p = x^2 + 1 ; q = 3 * x^3 - 5 }
If local definitions weren't permitted in Haskell,
foo
would have to be defined like this:
foo :: Int -> Int
foo x
| x > 0 = x^2 + 1 + 3 * x^3 - 5
| x <= 0 = x^2 + 1 - (3 * x^3 - 5)
let
-clauses and lambda-expressions
In addition to where
-clauses,
Haskell also has let
-clauses and it allows
the use of lambda-expressions.
Thus, the following four definitions are equivalent:
fun1, fun2, fun3, fun4 :: Int -> Int -> Int
fun1 x y = i^2 + j^3
where i = x + y
j = 2 * x - y
fun2 x y = i^2 + j^3 where { i = x + y ; j = 2 * x - y }
fun3 x y = let { i = x + y ; j = 2 * x - y } in i^2 + j^3
fun4 x y = (\i -> \j -> i^2 + j^3) (x + y) (2 * x - y)
Turning prefix functions into infix ones
Haskell has a mechanism for converting prefix functions into infix ones.
Enclosing a binary prefix operator in single opening quotation marks
turns it into an infix operator.
For example div 7 2
is equivalent to 7 `div` 2
.
Turning infix functions into prefix ones
There is also a mechanism for turning infix operators into prefix ones.
Enclosing a binary infix operator in parentheses turns
it into a prefix operator.
For example,
(+) 2 3
is equivalent to 2 + 3
,
(==) 3 4
is equivalent to 3 == 4
,
(*2) 7
is equivalent to 7 * 2
and
(0<) 8
is equivalent to 0 < 8
.
An expression of the form (+)
or (0<)
is called a section.
Note that (-1)
is not a section;
(-1)
represents an integer, minus one.
However, (1-)
is a section.
Programming style
A year is a leap year if it is divisible by 4 unless it is also divisible by 100. However, if it is divisible by 400, it is considered to be a leap year. The following are two ways of testing whether a year is a leap year:
leap1 :: Int -> Bool
leap1 y = (y `mod` 4 == 0) &&
(y `mod` 100 /= 0 ||
y `mod` 400 == 0)
leap2 :: Int -> Bool
leap2 y
| y `mod` 100 == 0 = y `mod` 400 == 0
| otherwise = y `mod` 4 == 0
In Haskell,
leap2
is considered more elegant than
leap1
.
Motivating qualified types
In addition to types like Int
and
Integer
, Haskell also has type classes.
These are collections of types.
To motivate these, consider the problem of defining a square function
for arbitrary-precision integers.
The function sqInt
that I defined earlier cannot be applied to arguments of type
Integer
,
so we would have to defined a new function
sqInteger
.
Then, we would have the following two definitions:
sqInt :: Int -> Int
sqInt x = x * x
sqInteger :: Integer -> Integer
sqInteger x = x * x
The definition of sqInteger
duplicates the earlier definition of sqInt
.
Haskell has a mechanism to avoid such duplication.
This involves qualified types.
Using such a qualified type we can
define a function sqIntegral
which can be applied both to arguments of type Int
and also of type Integer
:
sqIntegral :: Integral a => a -> a
sqIntegral x = x * x
Here, Integral
is a type class whose elements are the two
types Int
and Integer
and a
is a type variable.
The type Integral a => a -> a
is called a qualified type.
Haskell has a polymorphic type system.
Some functions, such as the identity function id x = x
of type
a -> a
, are fully polymorphic;
any type whatsoever can be substituted for the type variable a
.
In the case of sqIntegral
you get an error message if you try substituting any type for the type variable a
other than Int
or Integer
.
© Antoni Diller (31 May 2023)