The type system, the primitive data types and the names of their operators have a fairly complicated underlying structure, some of it will be explained later. But at a simple level Haskell provides the following basic types:
+, - (unary and binary),
*, /, and giving integer results: mod and
div.
^ (power of),
+, - (unary and binary),
*, and /.
Bool, has the values: True and
False, (note the initial capitals). There are the standard operations:
&& logical and, || logical or, and not negation.
'a', 'A', '*' and
'\n'. The C convention for escaping special characters is used, so
'\n' means newline. Characters are ordered by their underlying
ASCII representation order and can be compared.
Expressions are a way of describing a new value as the result of combining other values using operators and function application (calling). All values have a type, operators yield results of a given type so expressions have type.
3 * 3 + 2 * 2
is a simple expression of type Int. And the expression:
1.0 + sqrt 10.89
has value 4.3 of type Float. It is the addition operator
applied to 1.0 and the result of the function application
of the function sqrt to the number 10.89. In most
functional languages function calling (or application) is written
by juxtaposing the function and its argument, this causes the
function to be evaluated with the argument value producing a result.
Parentheses (round brackets) are not needed to indicate application,
they are only used to control order of evaluation. Also note that
application is more binding than any operator. So:
sqrt (10.89) + 1.0
(sqrt 10.89) + 1.0
sqrt 10.89 + 1.0
all mean the same, apply sqrt to 10.89 and then add 1.0.
But the following means something different:
sqrt (10.89 + 1.0)
first evaluate the addition yielding 11.89 and then apply
sqrt to the result.
An expression can be selected on the basis of the evaluation of a conditional expression, the format is:
if expressionexpression![]()
then expression![]()
else expression![]()
A let expression contains local definitions and one expression which is evaluated in the scope of the new definitions. After the let expression the local definitions are forgotten. So:
100 + let a = 4
b = a + a
in
a + b
should produce the result 112.
For more about the form of the definitions see the next section.
Definitions introduce new types or values into a program and declarations just constrain the meaning of a name. Simple value definitions just give names to values5.2:
y = x + a
message = "hello world\n"
x = 100
a = x * x
year = 1991
the name y is given the value 10100 etc.
These definitions are just namings, they are like the CONST
declarations of, for example, Modula-2, or Ada. They are not changeable storage
variables. They are like equations that the compiler solves, therefore
the construct x=x+1 which is meaningful if it is
an assignment statement is meaningless if it is just defining a value
for x: it is not soluble.
Note that Haskell uses a recursive scope, ie. the definitions
are not just elaborated in sequence but that names can be used that
are defined later. However some definitions might have no solution:
x = y + 1
y = x + 1
and will produce errors.
Function values for names can be defined, here the
name on the left of the = is followed by one or more
formal argument patterns, on the right is the expression
to be evaluated when the function is applied:
double n = n * 2
nonzero a = a /= 0
sqr :: Int -> Int
sqr x = x * x
sumsq a b = sqr a + sqr b
Defines four functions. Functions are values like any other value
and so they have types, the type of a function is given as the
type of its domain (its source), an arrow -> and the
type of its codomain (its target). So the type of double
is: Int -> Int and the type of nonzero is Int -> Bool.
Think of the evaluation of an expression (ie. a whole functional program) as the repeated replacement of names by their defined values and the replacement of built-in operations by their results until a non-replaceable value is left, this is the result. So given the above definitions, the expression:
x
rewrites to:
100
and the expression:
year + x
rewrites to:
1991 + 100
which rewrites, applying built-in operators, to:
2091
and now rewriting with functions, the only difference is that
when a function and the actual argument it is applied to is replaced
by the functions meaning (the right hand side) there is a substitution
of the actual arguments for the formals:
sumsq 4 5
where a is 4 and b is 5 rewrites to:
sqr 4 + sqr 5
which, taking the replacements one at a time, rewrites to:
4 * 4 + sqr 5
and evaluating the built in * first gives:
16 + sqr 5
and now the last name:
16 + 5 * 5
then:
16 + 25
and finally:
41
That is the end of the interlude.
Functions definitions can, in fact, be a lot more complicated then
the simple single equation ones show here. The full forms
are described in section
.
Names can also be ``typed'', ie. constrained
to be of a certain type, this information can be given
in a type declaration, name :: type:
flag:: Bool
count:: Int
nonzero:: Int -> Bool
names can only be given one typing, and any subsequent definition
of the name must match the typing, so that later:
flag = 77
will produce a static type error. The functional languages:
Haskell, SML, Hope and Miranda are all statically strongly typed which
means all functions and operators can only be applied to arguments or
operands of the appropriate type, that's the strong bit, and
that this can be checked before the program is evaluated, that's
the static bit.
However, programs don't have to have any type declarations,
the compiler will use type inference to work out
the correct types of all names and expressions. If there were
no declaration of the type of nonzero it will
be inferred from the comparison of a with 0 so
the source is Int and the result of the not-equals
comparison is Bool so this is the type of the target.
If the function is applied in an expression then this information
can be used to check for type correctness.
Composite types are types of values with other types as components, ie not primitive, they are sometimes called data structures. User defined composite types will be dealt with later.
[1,2,3,5,7,11,13,17]
[True, False, True, True]
['h', 'e', 'l', 'l', 'o' ]
[]
the first is a list of numbers, its type is [Int], the element
type enclosed in square brackets. The empty list: ``[]''
is also a list. Lists of numbers are not the same
type as lists of characters, so given that ++ joins (or concatenates)
lists:
[1,2,3] ++ ['a','b','b','a']
is a static type error, but:
[1,2,3] ++ [4,5]
is alright and will produce the Int list:
[1,2,3,4,5]
notice that lists of any length are compatible types so
long as the elements are the same.
The constructor operator for lists is ``:'', it takes
a value of any type and a list with elements of the same type
and constructs a new list like its second argument but with the
new value at the front. This operation is known as ``prefix''
or ``cons'' (after the original Lisp function that constructed lists).
The expression:
False : [False, True, False]
will give the result:
[False, False, True, False]
The prefix operator, ``:'', can only create a new list with an
additional element at the front. To understand why it only does this
it might help to understand how lists are usually implemented. This
is very naughty, looking below a high level language shouldn't be done
but sometimes it helps to relate it to other constructs like, for example,
chained records and pointers in Modula-2.
So now a diagram:
From this it can be seen that
the square bracket notation is a shorthand for repeated use
of ``:''; any list can be built by repeatedly using
``:'' to join elements onto
an empty list: [].
The list: [10,20,30] is equivalent to:
10 : (20 : (30 : []))
All list values can be built from the type's two constructors:
the constant [] and the binary constructor ``:''.
['h', 'e', 'l', 'l', 'o' ]
will be printed as:
hello
Also the notation "..." can be used in
expressions. There is no ``string'' type in Haskell,
the quoted string of characters is an abbreviation for a list
of characters. The type is [Char].
So a list of strings is in fact a list of lists of characters:
["apple", "orange", "pear" ]
has type [[Char]].
The interpreter to use is called gofer. All definitions and declarations must be in a file (sometimes called a script), there must be no expressionsWhen the interpreter is run it must be provided with the file name as a command line argument, the script is compiled and any error messages are displayed, then the interpreter prompts for input. The only Haskell that can be entered interactively is an expressions to be evaluated in the context of the definitions provided in the file. no definitions can be entered interactively. So script-definitions, interactively-expressions.
Aswell as expressions one can interactively ask for the type
of expressions whether inferred or declared, these are expressions
precede by the word ``type''.
Apart from expressions and typings system commands or directives
can be entered, these are a ``:'' followed by a name:
? :info sqrt sqrt :: Float -> Float -- primitive
^d also works
first.gs
containing:
sqr:: Int -> Int -- optional
foo = 77 + bar
sumsq a b = sqr a + sqr b
sqr n = n * n
snarf = 4
bar = 23
name = "Jo Soap"
(Note: in a Haskell script everything after -- is
ignored, it's a comment).
The following is a sample interactive session with the Haskell interpreter and compiler:
bacon(262)$ gofer first.gs Gofer Version 2.28b Copyright (c) Mark P Jones 1991-1993 Reading script file "/usr/local/lib/Gofer/standard.prelude": Reading script file "first.gs": Gofer session for: /usr/local/lib/Gofer/standard.prelude first.gs Type :? for help ? foo + 1 101 (6 reductions, 10 cells) ? sumsq 3 snarf 25 (9 reductions, 12 cells) ? sqrt (sqr bar) ERROR: Type error in application *** expression : sqrt (sqr bar) *** term : sqr bar *** type : Int *** does not match : Float ? sqrt (fromInteger (sqr bar)) 23.0 (6 reductions, 13 cells) ? sqr 3 + 1 10 (4 reductions, 9 cells) ? :q [Leaving Gofer] bacon(262)$
© University of Hertfordshire Higher Education Corporation (1998)