In this assignment you will get familiar with the Haskell programming environment and write some simple programs.
ghc
and ghci
)Read the first four chapters of the Gentle Introduction to Haskell. This is a fair amount of reading, but once it's done you'll understand the most basic parts of the Haskell language.
DO NOT SKIP THIS SECTION!
Many of you will want to develop and run your programs on your own computer, and you are welcome to do so, but the official environment for developing your programs is on the CS cluster. This consists of (a) the computers in Annenberg 104 which run Linux, and (b) any remote login machines available for the cluster. [Currently, there are no remote login machines available on the cluster, but we're working on it.] They all run the same software. You need to do a few small things to get set up to use an up-to-date programming environment on the CS cluster.
The first rule is as follows:
Do not, under any circumstances, develop or run your programs on the CS
cluster machine called login.cs.caltech.edu
or
login.cms.caltech.edu
! This computer is not intended to be
used for program development at all.
Instead, if you want to develop and test your code remotely, use a remote login machine, if there is one. It will work just like all the other CS cluster computers in the lab.
The software for the CS 11 Haskell track lives in the directory
/cs/courses/cs11/install
. In order to make this software available
to you, you need to do these simple steps:
Log in to a CS cluster computer.
Execute the following line in a terminal (where %
represents
the terminal prompt):
% cp /cs/courses/cs11/setup/bashrc-cs11 ~/.bashrc-cs11
If you do not have a file in your home directory called
.bashrc
(use ls -a
to check for files beginning with a
dot, which are normally hidden), execute the following line in the terminal:
% cp ~setup/general.bashrc ~/.bashrc
At the end of your .bashrc
file, add the following line:
source ~/.bashrc-cs11
Now log out and log back in again. You're all set!
For this track we will be using the Glasgow Haskell Compiler as our
Haskell programming environment. This is the most advanced and
"industrial-strength" Haskell compiler there is. It supports the full
Haskell 98 language plus lots of language extensions (which we won't cover in
this track). It includes both a stand-alone compiler (ghc
) and
an interactive interpreter (ghci
); we'll be working with both of
them. We'll refer to the system as ghc
from now on; this will
mean both the compiler and the interpreter. When we want to specifically
refer to the interpreter we'll say ghci
.
The main deficiency of ghc
is that the error messages it
generates can be difficult to understand unless you know a lot about the
language. If you get an error message which is incomprehensible, email it to
me and I'll tell you what it means.
ghci
For now we'll only be using ghci
to run Haskell programs.
Type this at the unix prompt:
% ghci
and you'll see this (but the version number may be different):
GHCi, version 7.0.3: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Loading package ffi-1.0 ... linking ... done. Prelude>
Now type in some simple expressions:
Prelude> 1 + 1 2 Prelude> [1..10] [1,2,3,4,5,6,7,8,9,10] Prelude> fst (1, "foo") 1 Prelude> snd (1, "foo") "foo" Prelude>
Note the Prelude>
prompt. The prelude is the standard
library of Haskell functions that are loaded up when ghci
starts
running. When you interact with ghci
, you type in an expression
and ghci
returns the value obtained after evaluating your
expression. In addition to this, there are a lot of other useful interpreter
commands built into ghci
that are not part of the Haskell
language; these all start with a colon (:). You can get a list of these by
typing :?
or :help
at the prompt. The most useful
colon commands are these:
:quit
(or :q
for short) to quit
ghci
.:load <filename>
: load and compile a Haskell source
code file. You can abbreviate this as :l <filename>
.:reload
(or :r
for short): reload the last file
loaded. This is useful when writing code in an editor which you want to test
interactively.:type <expression>
(or :t
<expression>
for short): print the type of
<expression>
.Here's an example:
Prelude> :t "foo" "foo" :: [Char]
This says that the string "foo"
has the type
[Char]
(which is also known as type String
). This
notation means that character strings have the type "list of Char".
Sometimes, types are more complicated than you'd expect:
Prelude> :t 42 42 :: forall t. (Num t) => t
You'd expect 42 to be an Int
or an Integer
, but
instead you get this bizarre type. This has to do with the type class system
wherein a literal number like 42 can have any of multiple types as long as
they obey certain rules; we'll get to that later in the track. If you
declare the type explicitly:
Prelude> let n = 42 :: Int Prelude> :t n n :: Int
then the type is what you expect.
Note that although you can type in any expression to ghci
,
you can't type in function definitions or type definitions. These have to be
typed in to a separate file and loaded in (or reloaded).
Write the following functions in a file called lab1.hs
. All
numeric function arguments and results are of type Double
unless
otherwise specified. Write type declarations for all your
functions.
Write a function called add_and_double
which adds two
numbers together and then doubles the result.
Write an infix operator called +*
which does the same
thing as add_and_double
. Define it in terms of
add_and_double
using the backtick (`
)
notation.
Write a function called solve_quadratic_equation
which takes
in three arguments (a
, b
, and c
) which
are coefficients to the quadratic equation a x2 + b x + c =
0
. a
, b
, c
, and x
should have type Double
. The output should be a tuple containing
the two roots. Don't worry about complex roots; if you apply the
sqrt
function to a negative number you will get NaN
(Not A Number). Use a let
or a where
expression to
define the square root of the discriminant (sqrt(b ** 2 - 4 * a *
c)
).
Write a function called first_n
which takes an
Int
(not an Integer
!) value (n) and returns a list of
the first n Ints
starting from 1. In your solution, use an
infinite list and the take
function from the Prelude (see here for
documentation on the Prelude functions). We use Ints
rather than
Integers
because the take
function's first argument is
of type Int
.
Re-write first_n
to a new function,
first_n_integers
which will take an Integer
argument and return a list of Integers
. Do this by defining a
local helper function, take_integer
, which takes an
Integer
as its first argument and a list of
Integers
as its second argument. Use a let
or a
where
expression to define the local helper function. Note that
you can use the error
function to signal an error. Check that
take_integer
's first argument is >= 0 (a pattern guard works
well) and that its second argument is not an empty list if its first argument
is greater than 0. take_integer
should be a recursive
function.
Define a function called double_factorial
which takes an
Integer
n and computes the product of all the factorials from 0
up to and including n. Use a let
or where
expression to define a factorial
function to be used by this
function. Both factorial
and double_factorial
will
be recursive functions.
Define an infinite list of factorials called factorials
using the zipWith
function (which you should look up in the
Prelude documentation). The first factorial should be factorial 0
i.e. 1. This can be done in one line (plus another line for the type
declaration). This list of factorials should have type
[Integer]
. Hint: You can use the infinite list of factorials in
its own definition.
ghci
If you invoke ghci
with the -W
option, then
several useful warnings will be enabled. Most importantly, any incomplete
(non-exhaustive) pattern matches in your code will be reported as such. We
expect that all of your code will go through ghci -W
without
warnings. So you can type this at the unix prompt:
% ghci -W lab1.hs
and then test your code inside ghci
. Another way to do this
is to create a file called .ghci
in your home directory. Inside
that file put this line:
:set -W
What this will do is automatically set the -W
option every
time you use ghci
. With this done, you can just type:
% ghci lab1.hs
and the -W
option will automatically be enabled. This is the
approach we recommend.
Note that sometimes ghci
will report incomplete pattern
matches which don't look incomplete to you. A classic example would be this
function:
absoluteValue :: Int -> Int absoluteValue x | x >= 0 = x | x < 0 = -x
If you load this into ghci
with the -W
option
enabled, you'll get something like this:
abs.hs:2:0: Warning: Pattern match(es) are non-exhaustive In the definition of `absoluteValue': Patterns not matched: _
The problem here is that ghci
is not smart enough to realize
that the patterns you've given cover all possible Int
arguments. A solution is simple:
absoluteValue :: Int -> Int absoluteValue x | x >= 0 = x absoluteValue x = -x
or, alternatively:
absoluteValue :: Int -> Int absoluteValue x | x >= 0 = x | otherwise = -x
Just because your function really does exhaustive pattern matching
isn't good enough for us; we want you to make sure ghci
thinks
so too.
Your program lab1.hs
.
A Gentle Introduction to Haskell, by Hudak, Peterson and Fasel, chapters 1 to 4.
The Haskell home page.
A tour of the Haskell Prelude. This is useful for looking up prelude functions.