Certain types like Integers, Booleans, Characters, Reals, Lists, Tuples and Arrays are
that frequently used that they have been predefined in Clean for reasons of efficiency and/or notational convenience. These
types and the syntactic sugar that has been added to create and to inspect (via
pattern matching) objects of these popular types are treated in this chapter.

There is also a special notation to denote a
string (an unboxed array of characters, see 4.4) as
well as to denote a list of characters (see
4.2.1).

BasicType

=

Int

// see StdInt.dcl

|

Real

// see StdReal.dcl

|

Char

// see StdChar.dcl

|

Bool

// see StdBool.dcl

4.1.1 Creating Constant Values of Basic Type

In a graph expression a constant value of i.basic type ;Int, Real, Bool;or Char can be created.

BasicValue

=

IntDenotation

|

RealDenotation

|

BoolDenotation

|

CharDenotation

IntDenotation;

=

[Sign]{Digit}+

// decimal
number

|

[Sign]0{OctDigit}+

// octal
number

|

[Sign]0x{HexDigit}+

//
hexadecimal number

Sign

=

+ | -

RealDenotation

=

[Sign]{Digit}+.{Digit}+[E[Sign]{Digit}+]

BoolDenotation

=

True | False

CharDenotation

=

CharDel
AnyChar/~CharDel CharDel

CharsDenotation

=

CharDel
{AnyChar/~CharDel}+ CharDel

AnyChar

=

IdChar |
ReservedChar | Special

ReservedChar

=

( | ) | { | } | [ | ] | ; | , | .

Special

=

\n | \r | \f | \b

// new­line,return,formf,backspace

|

\t | \\ | \CharDel

//
tab,backslash,character delete

|

\StringDel

// string
delete

|

\{OctDigit}+

// octal
number

|

\x{HexDigit}+

//
hexadecimal number

Digit

=

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

OctDigit

=

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7

HexDigit

=

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

|

A | B | C | D | E | F

|

a | b|c|d|e|f

CharDel

=

'

StringDel

=

"

Example of denotations.

Integer (decimal): 0|1|2|…|8|9|10| …
|-1|-2| …

Integer (octal): 00|01|02|…|07|010| …
|-01|-02| …

Integer (hexadecimal): 0x0|0x1|0x2|…|0x8|0x9|0xA|0xB
… |-0x1|-0x2| …

Real: 0.0|1.5|0.314E10| …

Boolean: True | False

Character: 'a'|'b'|…|'A'|'B'|…

String: "" | "Rinus"|"Marko"|…

List of characters: ['Rinus']|['Marko']|…

4.1.2 Patterns of Basic Type

A constant value of predefined basic type Int, Real, Bool or Char(see 4.1) can be speci­fied as pat­tern.

BasicValuePattern

=

BasicValue

The denotation of such a value must obey the syntactic description
given in above.

Use of Integer values in a pattern.

nfib:: Int -> Int

nfib 0 = 1

nfib 1 = 1

nfib n = 1 + nfib (n-1) * nfib (n-2)

4.2 Lists

A list is an algebraic data type predefined just for programming
convenience. A list can contain an in­fi­nite number of elements. All
elements must be of the same type. Lists are very often used in func­tional
languages and therefore the usual syntactic sugar is provided for the creation
and ma­ni­pu­lation of lists (dot-dot expressions, list comprehensions) while
there is also special syntax for a list of characters.

Lists can be lazy (default), and optionally be
defined as head strict, spine strict, strict (both head and spine strict), head
strict unboxed, and strict unboxed. Lazy, strict and unboxed lists are all ob­jects
of different type. All these different types of lists have different
time and space properties (see 10.1.3).
Because these lists are of different type, conversion functions are needed to
change e.g. a lazy list to a strict list. Functions defined on one type of a
list cannot be applied to another type of list. However, one can define
overloaded functions that can be used on any list: lazy, strict as well as on
unboxed lists.

ListType

=

[[ListKind] Type [SpineStrictness]]

ListKind

=

!

// head
strict list

|

#

// head
strict, unboxed list

SpineStrictness

=

!

// tail
(spine) strict list

All elements of a list must be of the same type.

4.2.1 Creating Lists

Because lists are very convenient and very
frequently used data structures, there are several syntactical con­structs in Clean for creating lists, including ib.dot-dot
expression;and ZF-expressions. Since Clean
is a lazy functional language, the default list in Clean
is a lazy list. However, in some cases strictlists, spine
strictlists and unboxedlists can be more convenient
and more efficient.ib.generator:list;

In Cleanany data
structure can be made (partially) strict or unboxed (see 10.1). This has consequences for the time and space behavior of the data
structure.

For instance, lazy lists are very
convenient (nothing is evaluated unless it is really needed for the
computation, one can deal with infinite data structures), but they can be
inefficient as well if actually always all lists elements are evaluated sooner
or later. Strict list are often more efficient, but one has to be
certain not to trigger a not used infinite computation. Spine strict lists
can be more efficient as well, but one cannot handle infinite lists in this
way. Unboxed lists are head strict. The difference with a strict list is that
the representation of an unboxed list is more compact: instead of a
pointer to the lists element the list element itself is stored in the list.
However, unboxed lists have as disadvantage that they can only be used in
certain cases: they can only contain elements of basic type, records and
tuples. It does not make sense to offer unboxing for arbitrary types: boxing
saves space, but not if Cons nodes are copied often: the lists elements are
copied as well while otherwise the contents could remain shared using the
element pointer instead.

In terms of efficiency it can make quite a
difference (e.g. strict lists can sometimes be 6 times faster) which kind of
list is actually used. But, it is in general not decidable which kind of list
is best to use. This depends on how a list is used in a program. A wrong choice
might turn a program from useful to useless (too inefficient), from terminating
to non-terminating.

Because lists are so frequently used, special
syntax is provided to make it easier for a programmer to change from one type
of list to another, just by changing the kind of brackets used. One can define
a list of which the head element is strict but the spine is lazy
(indicated by [! ]), a list of which the spine is strict but the head element
is lazy (indicated by [ !]) and a completely evaluated list (indicated by [! !]). One can
have an unboxed list with a strict head element (indicated by [# ]) and a
completely evaluated unboxed list of which in addition the spine is strict as
well (indicated by [# !]).

Note that all these different lists are of
different type and consequently these lists cannot be mixed and unified with
each other. With conversion functions offered in the Clean
libraries it is possible to convert one list type into another. It is also
possible to define an overloaded list and overloaded functions that work on any
list (see hereafter).

With a dot-dot expression the list elements can
be enumerated by giving the first element (n1), an op­tional second element (n2) and an optional
last element (e).

Alternative ways to define a list a dot dot
expression.

[1,3..9] // [1,3,5,7,9]

[1..9] // [1,2,3,4,5,6,7,8,9]

[1..] // [1,2,3,4,5 and so on…

['a'..'c'] // ['abc']

The generated list is in general calculated as
follows:

from_then_to:: !a !a !a -> .[a] | Enum a

from_then_to n1 n2 e

| n1 <= n2 = _from_by_to n1 (n2-n1) e

=
_from_by_down_to n1 (n2-n1) e

where

from_by_to n s e

| n<=e = [n :
_from_by_to (n+s) s e]

= []

from_by_down_to n s e

| n>=e = [n : _from_by_down_to (n+s) s e]

= []

The step size is one by default. If no last element is
specified an infinite list is generated.

With a dot-dot expression one can also define a
lazy list, a strict list, an unboxed list or an overloaded list.

Different types of lists (lazy, strict, unboxed
and overloaded can be defined with a dot dot expression as well.

[ 1,3..9 ] // a lazy list

[! 1,3..9 ] // a head strict list

[! 1,3..9 !] // a strict list (head and spine)

[# 1,3..9 ] // a head strict list, unboxed

[# 1,3..9 !] // a strict list (head and spine), unboxed

[| 1,3..9 ] // an overloaded list

Dot-dot expression can only be used if one imports StdEnum from the
standard library.

Dot-dot expressions are predefined on objects of type Int, Real and Char, but dot-dots
can also be applied to any user defined data structure for which the class
enumeration type has been instanti­ated (see Cleans
Standard Environment).

With a list generator called a ZF-expression one
can construct a list composed from elements drawn from other lists or arrays.
With a listgenera­tor one can draw elements from lazy list. The symbol '<-' is used to
draw elements from a lazy list, the symbol '<|-' can be used to draw elements
from any (lazy, strict, unboxed) list. With an array genera­tor (use symbol '<-:') one can draw elements from any array. One can define several generators in a row separated by a comma.
The last generator in such a sequence will vary first. One can also define
several genera­tors in a row separated by a '&'. All genera­tors in such a
sequence will vary at the same time but the drawing of el­ements will stop as
soon of one the generators is exhausted. This construct can be used instead of
the zip-functions that are com­monly used. Selectors are simple patterns
to iden­tify parts of a graph ex­pression. They are ex­plained in Section 3.6.
Only those lists produced by a generator that match the specified selector are
taken into account. Guards can be used as filter in the usual way.

The scope of the selector variables introduced on
the left-hand side of a generator is such that the vari­ables can be used in
the guards and other generators that follow. All variables introduced in this
way can be used in the expression before the \\ (see the picture below).

merge_l merges two (sorted) lists into one (sorted) list. Any list (lazy,
strict or unboxed) can be merged, but both lists have to be the same (both
lazy, strict, head strict, tail strict or unboxed)

merge_l:: (l a)(l a) -> (l a) | List l a

merge_l f [|] = f

merge_l [|] s = s

merge_l f=:[|x:xs] s=:[|y:ys]

| x<y = [|x:merge_l xs s]

| x==y = merge_l f ys

| otherwise = [|y:merge_l f ys]

4.3 Tuples

A tuple is an algebraic data type predefined for reasons of programming
conve­nience and effi­ciency. Tuples have as advantage that they allow bundling
a finite number of objects of arbitrarytype into a new
object with­out being forced to define a new algebraic type for such a new
object. This is in particular handy for functions that return several val­ues.

Tuples can be created that can be used
to combine different (sub-)graphs into one data structure with­out being forced
to define a new type for this combination. The elements of a tuple need not
be of the same type. Tuples are in particular handy for functions that return
multiple results.

Tuple

=

(GraphExpr,{GraphExpr}-list)

Example of a Tuple of type (String,Int,[Char]).

("this is a tuple with",3,['elements'])

One can turn
a lazy tuple element into a strict one by putting strictness annotations in the
correspond­ing type instance on the tuple ele­ments that one would like to make
strict. When the cor­respond­ing tuple is put into a strict context the tuple
and the strict annotated tuple elements will be evaluated. As usual one has to
take care that these elements do not represent an infinite computation.

Strict and lazy tuples are regarded to be of
different type. However, unlike is the case with any other data structure,
the compiler will automatically convert strict tuples into lazy ones, and the
other way around. This is done for programming convenience. Due to the
complexity of this automatic transfor­mation, the conversion is done for tuples
only! For the programmer it means that he can freely mix tuples with strict and
lazy tuple elements. The type system will not complain when a strict tuple is
offered while a lazy tuple is required. The compiler will automatically insert
code to convert non-strict tuple elements into a strict version and backwards
when­ever this is needed.

imagpart (_,im) = im // selecting the imaginary part of a Complex
number

4.4 Arrays

An array is an algebraic data type predefined for reasons of efficiency.
Arrays contain a finite num­ber of elements that all have to be of the same
type. An array has as property that its ele­ments can be ac­cessed via indexing
in constant time. An array index must be an integer value between 0 and the number of el­ements of the
array-1. Destructive update of array ele­ments is possible thanks to unique­ness
typ­ing. For pro­gramming con­venience special syntax is provided for the cre­a­tion,
se­lec­tion and updating of ar­ray elements (array comprehensions) while there
is also special syntax for strings (i.e. unboxed ar­rays of characters).
Arrays have as disad­vantage that their use increases the possi­bility of a
run-time error (indices that might get out-of-range).

To obtain optimal efficiency in time and space,
arrays are implemented different depending on the concrete type of the array
elements. By default an array is implemented as a lazy array (type {a}), i.e. an array
consists of a contiguous block of memory containing pointers to the array
elements. The same re­presentation is chosen if strict arrays (define
its type as {!a}) are being used. The difference is that all elements of a
strict array will be evaluated if the array is evaluated. It makes no sense to
make a distinction between head strictness and tail strictness for arrays as
is the case for lists. As usual one has to take care that the strict elements
do not represent an infinite computation.

For ele­ments of basic type, record type and
tuple type an unboxed array (define its type as {#a}) can be used.
In that latter case the pointers are replaced by the array elements themselves.
Lazy, strict and unboxed arrays are considered to be ob­jects of different
type. However, most predefined operations on arrays are overloaded such that
they can be used on lazy, on strict as well as on unboxed ar­rays.

ArrayType

=

{[ArrayKind] Type}

ArrayKind

=

!

// strict
array

|

#

// unboxed
array

4.4.1 Creating Arrays and Selection of field Elements

An array is a tuple/record-like data structure in which all elements
are of the same type. Instead of se­lection by position or field name
the elements of an array can be selected very efficiently in con­stant time
by indexing. The update of arrays is done destructively in Clean and therefore arrays have to be uni­que (see Chapter 9) if one wants to use this feature. Arrays are very useful if time
and space con­sumption is becoming very critical (Clean
arrays are implemented very efficiently). If efficiency is not a big issue we
recommend not to use arrays but to use lists instead: lists induce a
much better pro­gramming style. Lists are more flexible and less error prone:
array elements can only be accessed via in­dices and if you make a calculation
error, indices may point outside the array bounds. This is detected, but only
at run-time. In Clean, array indices al­ways start with 0.
More di­mensional arrays (e.g. a ma­trix) can be defined as an ar­ray of
arrays.

For efficiency reasons, arrays are available of
several types: there are lazy arrays (type {a}), strict arrays (type {!a}) and unboxed
arrays (e.g. type {#Int}). All these arrays are conside­red to be of different type.
By using the overloading mechanism (type constructor classes) one can still
define (overloaded) functions that work on any of these arrays.

A new array can be created in a number of ways. A
direct way is to simply list the array elements.

ArrayDenotation

=

{{GraphExpr}-list}

|

StringDenotation

StringDenotation

=

StringDel{AnyChar/~StringDel}StringDel

StringDel

=

"

By default a lazy array will be created.
Arrays are created uni­que (the * or. attribute in front of the type,
see Chapter 4) to make destructive updates possible.

A lazy array is a box with pointers pointing to
the array elements. One can also create a strict array (explicitly define its
type as {!Int}), which will have the property that the elements to which the ar­ray
box points will always be evaluated. One can furthermore create an unboxed
array (explicitly define its type as {#Int}), which will have the property that the evaluated elements (which
have to be of basic value) are stored directly in the array box itself. Clearly
the last one is the most efficient representa­tion (see also Chapter 10).

Creating a two dimensional array, in this case a
unique array of unique arrays of unboxed integers.

MatrixA:: {.{#Int}}

MatrixA = {{1,2,3,4},{5,6,7,8}}

To make it possible to use operators such as
array selection on any of these arrays (of actually dif­ferent type) a multi
parameter type constructor class has been defined (in StdArray) which expresses that “some
kind of array structure is created”. The compiler will therefore deduce the
following general type:

Array :: .(a Int)| Array a Int

Array = {1,3,5,7,9}

A string is predefined type which
equivalent to an unboxedarray of characters{#Char}. Notice
that this array is not unique, such that a destructive update of a
string is not allowed. There is special syntax to denote strings.

Some ways to define a string, i.e. an unboxed
array of character.

"abc"

{'a','b','c'}

There are a number of handy functions for the
creation and manipulation of arrays prede­fined in Clean's
Standard Environment. These functions are overloaded to be able to deal with
any type of array. The class restrictions for these functions express that “an
array structure is requi­red” contai­n­ing “an ar­ray element”.

Left from the & (a & [i]= v is pronounced as: array a with for a.[i] the value v) the old array
has to be speci­fied which has to be of unique type to make destructive
updating possible. On the right from the & those ar­ray elements are
listed in which the new array dif­fers from the old one. One can change any
element of the array or any field or array element of a record or array stored
in the array. The &-operator is strict in its ar­guments.

An array expression must be of type array.

The array expression to the left of the update operator ‘&’ should
yield an ob­ject of type unique ar­ray.

An array index must be an integer value between 0 and the number of
elements of the array-1. An index out of this range will result in a run-time error.

Unboxed arrays can only be of basic type, record type or array type.

A unique array of any type created by an overloaded function cannot
be converted to a non-unique ar­ray.

Important: For
reasons of efficiency we have defined the updates only on arrays which are of uni­que
type (*{…}), such that the update can always be done de­structively (!)
which is se­mantically sound be­cause the original unique array is known not to
be used any­more.

Creating an array with the integer elements
1,3,5,7,9 using the update operator.

{CreateArray 5 0 & [0] = 1, [1] = 3, [2] = 5,
[3] = 7, [4] = 9}

{CreateArray 5 0 & [1] = 3, [0] = 1, [3] = 7,
[4] = 9, [2] = 5}

One can use an array comprehension or a list comprehension (see 4.2.1) to list these ele­ments com­pactly in the same spirit as with an
list comprehension.

Array comprehensions can be used in combination
with the update operator. Used in combination with the update operator the
original uniquely typed array is updated destructively. The combina­tion of
array comprehensions and update operator makes it possible to selectively
update array el­ements on a high level of abstraction.

Creating an array with the integer elements 1,3,5,7,9
using the update operator in combination with ar­ray and list comprehensions.

{CreateArray 5 0 & [i] = 2*i+1 \\ i <-
[0..4]}

{CreateArray 5 0 & [i] = elem \\ elem <-:
{1,3,5,7,9} & i <- [0..4]}

{CreateArray 5 0 & elem \\ elem <-:
{1,3,5,7,9}}

Array comprehensions used without update operator
automatically generate a whole new array. The size of this new array will be
equal to the size of the first array or list gene­rator from which el­ements
are drawn. Drawn elements that are rejec­ted by a corresponding guard result in
an un­defined ar­ray ele­ment on the corresponding posi­tion.

Creating an array with the integer elements
1,3,5,7,9 using array and list comprehensions.

{elem \\ elem <-: {1,3,5,7,9}}

{elem \\ elem <- [1,3,5,7,9]}

Array creation, selection, update). The most
general types have been defined. One can of course always restrict to a more
specific type.

With an array selection (using the ‘.’ symbol) one can select an array element. When an object a is of type Array, the ith el­e­ment
can be selected (computed) via a.[i]. Array selection is left-associa­tive: a.[i,j,k] means ((a.[i]).[j]).[k].
A “unique” selection using the ‘!’ symbol returns a tuple containing the demanded array element and
the original array. This type of array selection can be very handy for destructively
updating of uniquely typed arrays with values that depend on the current
contents of the array. Array se­lection binds more tightly (priority 11) than
application (priority 10).

4.4.2 Array Patterns

An object of type array can be specified as pattern. Notice that only simple array pat­terns
can be specified on the left-hand side (one cannot use array comprehensions).
Only those array ele­ments which contents one would like to use in the
right-hand side need to be mentioned in the pat­tern.

ArrayPattern

=

{{GraphPattern}-list}

|

{{ArrayIndex = Variable}-list}

|

StringDenotation

All array elements of an array need to be of same type.

An array .i i.index; must be
an integer value between 0 and the number of elements of the array-1. Ac­cessing an array with
an index out of this range will result in a run-time error.

It is allowed in the pattern to use an index
expression in terms of the other formal arguments (of type Int) passed to the
function to make a flexible array access possible.

Use of array patterns.

Swap:: !Int !Int !*(a e) ->.(a e) | Array a e

Swap i j a=:{[i]=ai,[j]=aj} = {a &
[i]=aj,[j]=ai}

4.5 Predefined Type
Constructors

Predefi­ned types can also be used in curried
way. To make this possible a type constructor has been predefined for all
predefined types of higher order kind(see also 5.1.2). The kind X stands for any
so-called first-order type: a type expecting no further arguments ((Int, Bool, [Int], etcetera).
All function arguments are of kind X. The kind X -> X stands for a type that can be applied to a (first-order) type,
which then yields another first-order type, X -> X -> X expects two type
arguments, and so on.

Int, Bool, [Int], Tree [Int] :: X

[], Tree, (,) Int, (->) a, {} :: X -> X

(,), (->) :: X -> X
-> X

(,,) :: X -> X ->
X -> X

PredefinedTypeConstructor

=

[]

// list type
constructor

|

[! ]

// head
strict list type constructor

|

[ !]

// tail
strict list type constructor

|

[!!]

// strict
list type constructor

|

[#]

// unboxed
head strict list type

|

[#!]

// unboxed
strict list type

|

({,}+)

// tuple
type constructor (arity >= 2)

|

{}

// lazy
array type constructor

|

{!}

// strict
array type constructor

|

{#}

// unboxed
array type constructor

|

(->)

// arrow
type constructor

So, all predefined types can be written down in
prefix notation as well, as follows:

The arrow type is a predefined type constructor used to indicate function
objects (these functions have at least arity
one). One can use the Cartesian product (uncurried version) to denote the
function type (see 3.7) to obtain a com­pact
no­ta­tion. Curried functions appli­cations and types are automatically
converted to their uncurried equiva­lent versions.

ArrowType

=

(Type -> Type)

Example of an arrow type.

((a b -> c) [a] [b] -> [c])

being equivalent with:

((a -> b -> c) -> [a] -> [b] ->
[c])

4.7 Predefined Abstract Types

Abstract data types are types of which
the actual def­ini­tion is hid­den (see 5.4). In Clean
the types World and and File are predefined abstract data types. They are recognised by
the compiler and treated specially, either for effi­ciency or because they play
a special role in the lan­guage. Since the ac­tual defini­tion is hidden it is
not possible to denotate constant val­ues of these prede­fined abstract ty­pes.
There are functions predefined in the Clean
li­brary for the creation and manipu­la­tion of these predefined ab­stract data
types. Some func­tions work (only) on unique objects.

An object of type *World (* indicates that the world is unique)
is automatically created when a pro­gram is started. This object is optionally
given as argument to the Start function (see
2.3). With this object effi­cient in­terfacing with
the outside world (which is indeed unique) is pos­sible.

An object of type File or *File (unique File) can be
created by means of the functions defined in Std­Fi­leIO (see Cleans Standard Library). It makes direct manipulation of persistent data
possible. The type File is pre­de­fined for rea­sons of efficiency: CleanFiles are directly coupled to concrete files.

The type String is a predefined synonym type that
is predefined for convenience. The type is synonym for an unboxed array of
characters {#Char}.