Department of Computer Science



next up previous
Next: More about functions Up: A functional programming language: Previous: Functional programming languages


Basic components


Subsections


Primitive, built-in data types

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:

Int
The constant values can be written as: 47, -2, etc. There are the normal complement of operators: +, - (unary and binary), *, /, and giving integer results: mod and div.
Float
The constant values can be written as: 0.712, -10.1, or 1.34e-4. There are the normal complement of operators: ^ (power of), +, - (unary and binary), *, and /.
Bool
The boolean type, called Bool, has the values: True and False, (note the initial capitals). There are the standard operations: && logical and, || logical or, and not negation.
Char
Character values. Constants are: '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

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.

if expressions

An expression can be selected on the basis of the evaluation of a conditional expression, the format is:

if expression$_1$
then expression$_2$
else expression$_3$
expression$_1$ is evaluated, if it yields True the expression$_2$ is evaluated to give the result, otherwise expression$_3$ is evaluated.

let expressions

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 and declarations

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.

Simple function definitions

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.


Interlude on expression evaluation

Now a tiny interlude about a way of thinking about the evaluation of functional programs. It is an informal model to help understand how they work:

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 [*].

Type constraint declarations

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.


Type inference

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.


Pre-defined composite types

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.

Lists

A list is a sequence of values, all of the same type. It can be written as:
        [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:

The list is represented as a sequence of cells pointing to an element and the rest of the list. Prefixing just creates one cell making the first field point to the new head value and the second field point to the second argument, the list. The pointer to the new cell is returned as the result which is the new list.

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 ``:''.

Strings or Char lists

When a list of characters is printed by the Haskell compiler they are joined up and come out as a string, so:
        ['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]].


Evaluating Haskell programs

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:

:?
for the list of directives
:info
describes what some named value is:
? :info sqrt
sqrt :: Float -> Float   -- primitive
:edit
invoke an editor on the script, when the edit finishes it returns to Haskell. The editor can be changed using the EDITOR environment variable.
:quit
leave gofer, typing ^d also works
So given the following very simple script file called 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)$



Footnotes

... values5.2
These are in fact a simplified case of conformal definitions, see the summary, see a fuller language description.

next up previous
Next: More about functions Up: A functional programming language: Previous: Functional programming languages


Page generated: 2002-11-04 by Bob Dickerson

© University of Hertfordshire Higher Education Corporation (1998)

Disclaimer