Chapter 13: Language-defined types
The Aldor language defines only those types which
are required in specifying what the language does.
Most of the types which are usually found in high-level programming languages
are delegated to libraries in Aldor. This allows the library designer
maximum flexibility in dressing the basic types with desired operations.
The language defined types are listed below.
Here, n, m >= 0.
13.1 : Type
Type
" is the type of all data type objects, including itself.
Sometimes it it not possible to tell whether a value is a type.
Unless a value has been explicitly asserted to be a type, then
it is not treated as one.
In the example below, the parameter "t
" of the function
"higher
" is a type in some possible calls but not in others.
#include "aldor.as" higher(T: Type, f: T->T, t: T): T == f f t; -- next next 2 n: Integer == higher(Integer, next, 2); -- List List Integer L: BasicType == higher(BasicType, List, Integer);
13.2 : (S1,..,Sn)->(T1,..,Tm)
I ==> Integer | A shorthand for these examples. |
(I, I) -> () | No result --- useful for side-effecting functions. |
(I, I) -> I | One result --- most common case. |
(I, I) -> (I, I) | Two results. |
Tuple I -> Tuple I | Any number of arguments and any number of results. |
(i: I, n: I) -> I | The arguments may be passed by keyword. |
(i: I, n: I == 0) -> I | The arguments may be passed by keyword, and the second one has a default value. |
(I, n: I)-> IntegerMod n | The return type depends on an argument value. |
(n: I, IntegerMod n)-> I | One argument type depends on another argument value. |
13.3 : Tuple T
Cross
" of values of the same type to be converted to a Tuple.
For example,
()
, (1)
, (1, 2)
, and (1,3,7,8)
may all be
used where a Tuple(Integer)
is expected.
Both the base Aldor library and the AXIOM library extend Tuple
to provide operations to count the elements or extract particular ones:
#
" and "apply
" to avoid ambiguity between
values and singleton tuples.
The "element
" operation uses 1-based indexing.
Tuple values are not updatable.
13.4 : Cross(T1,...,Tn)
-- Conversion of multiple values to a cross product: ij: Cross(Integer, Integer) := (1, 2); -- Conversion of a cross product to multiple values: (i, j) := ij; -- This gives the same result as n := i + j. n := + ij;There are no operations for counting or selecting product components, and product values are not updatable. The products may be cartesian or dependent:
Cross(Integer, Integer) -- cartesian product Cross(n: Integer, IntegerMod(n)) -- dependent product
13.5 : Enumeration(x1,...,xn)
Enumeration
.
For example,
Colour == 'red, green, blue'; x: Colour := redOne of the common uses of enumerations is for selector functions. For example, in the base Aldor library, the type
List(S)
exports the following operations:
apply: (%, 'first') -> S set!: (%, 'first', S) -> S apply: (%, 'rest') -> % set!: (%, 'rest', %) -> %This allows expressions of the form:
l.first; l.first := s l.rest; l.rest := lSeparate types
'first'
and 'rest'
are used,
rather than one 'first, rest'
, to allow strong type checking.
The precise way in which enumerations work may seem a bit strange at first:
the form
'red, green, blue'is actually a short-hand for the call
Enumeration(red: Type, green: Type, blue: Type)Notice that this has the same form as a typical call to
Record
or a call to "->
" with keyword arguments.
This provides enumerations without introducing
any extra fundamental ideas into the language.
13.6 : Record(T1,...,Tn)
Record
may be given in any of the following forms:==
v .Record(T1,...,Tn)
exports the following operations:bracket
: (T1,...,Tn) -> %record
: (T1,...,Tn) -> %explode
: % -> (T1,...,Tn)dispose!
: % -> ()==
v ,
the record type also exports the operations:
apply
: (%, 'idi') -> Tiset!
: (%, 'idi', Ti) -> Tibracket
" and "record
" operations have the same function
and construct new record values.
The "bracket
" operation allows
records to be constructed with the syntax [1,1]
, which is nice
and concise.
More importantly, it allows record types to be used generically --- that
is, without disclosing that the heterogeneous aggregate
is a record type, as opposed to a table or other structure.record
" operation allows documented construction
of record values.
This is convenient if there are many aggregate types in scope
(lists, lists of records, records of lists etc.)
and the bracket operation becomes too heavily overloaded for clarity.explode
" operation allows record values to be deconstructed
into their constitutent parts. This is convenient for use with
multiple assignment or passing all the components to a function of
an equal number of arguments.dispose!
" operation promises that its argument will
no longer be referenced,
and on certain platforms permits the memory to be reused immediately.
It is not necessary to use this function:
if you don't, storage will be garbage collected periodically.
The choice often boils down to debugability versus speed.apply
" and "set!
" operations allow the record
fields to be extracted and reset. This may be done with either
explicit calls to these functions or implicit calls arising
from the forms "r.i
" or "r.i := 7
".Record
is to call it with simple type expressions, giving neither
field names nor default values with the arguments.
An example would be Record(Integer, DoubleFloat)
.
In this case no "apply
" or "set!
" operations are exported,
and the behaviour of
the record type is very similar to that of a corresponding call
to Cross
.bracket
: (Integer, DoubleFloat) -> %record
: (Integer, DoubleFloat) -> %explode
: % -> (Integer, DoubleFloat)dispose!
: % -> ()Record(i: Integer, x: DoubleFloat)
provides a record type with the following operations:bracket
: (i: Integer, x: DoubleFloat) -> %record
: (i: Integer, x: DoubleFloat) -> %explode
: % -> (Integer, DoubleFloat)dispose!
: % -> ()apply
: (%, 'i') -> Integerapply
: (%, 'x') -> DoubleFloatset!
: (%, 'i', Integer) -> Integerset!
: (%, 'x', DoubleFloat) -> DoubleFloatr := [3, 4.0]; r := [i == 3, x == 4.0] r := [x == 4.0, i == 3] (vi, vx) := explode r r.i := 7 r.x := 32.0To be painstakingly correct, the exports are not precisely as shown above. The actual exports are:
bracket
: (i: Integer, x: DoubleFloat) -> %record
: (i: Integer, x: DoubleFloat) -> %explode
: % -> (i: Integer, x: DoubleFloat)dispose!
: % -> ()apply
: (%, 'i') -> (i: Integer)apply
: (%, 'x') -> (x: DoubleFloat)set!
: (%, 'i', i: Integer)) -> (i: Integer)set!
: (%, 'x', x: DoubleFloat) -> (x: DoubleFloat)bracket
" and "record
"
operations, but it is cleaner to be completely uniform.Record
are given with default values,
for example
Record(i: Integer == 7, x: DoubleFloat == 0)
,
then the uniform substitution yields the following construction operations:
bracket
: (i: Integer == 7, x: DoubleFloat == 0) -> %record
: (i: Integer == 7, x: DoubleFloat == 0) -> %r := [5]; -- Same as r := [5, 0] r := [.01]; -- Same as r := [7, .01] r := []; -- Same as r := [7, 0]
13.7 : TrailingArray((U1,...,Un),(V1,...,Vm))
TrailingArray
: (Tuple Type,Tuple Type) -> TypeTrailingArray( sz: Integer , (coef: R, deg: NNI))
This would create a data structure looking like:
sz | coef | deg | coef | deg | coef | deg | ... |
Record(sz: Integer, tail: PrimArray Record(r: R, deg: NNI))
contains exactly the same information, but looks like:
Usage:
TrailingArray( U, V )
Record(U)
:apply
: (%, 'un') -> Unset!
: (%, 'un', Un) -> Unapply
: (%, SingleInteger, 'vn') -> Vnset!
: (%, SingleInteger, 'vn', Vn) -> Vnbracket
: (SingleInteger, Cross U, Cross V) -> %trailing
: (SingleInteger, Cross U, Cross V) -> %dispose!
: % -> ()13.8 : Union(T1,...,Tn)
Union
: Tuple Type -> TypeUnion
constructor provides types which can be used
to represent values belonging to any one of several alternative types.Union(int: Integer, flo: Float)
could be used.
This type would then provide the following operations:bracket
: (int: Integer) -> %bracket
: (flo: Float) -> %union
: (int: Integer) -> %union
: (flo: Float) -> %case
: (%, 'int') -> Booleancase
: (%, 'flo') -> Booleanapply
: (%, 'int') -> Integerapply
: (%, 'flo') -> Floatset!
: (%, 'int', Integer) -> Integerset!
: (%, 'flo', Float) -> Floatdispose!
: % -> ()bracket
: (id: T) -> %union
: (id: T) -> %case
: (%, 'id') -> Booleanapply
: (%, 'id') -> Tset!
: (%, 'id', T) -> %bracket
")
and a type-specific name ("union
").
These form union values from values belonging to the branch types.case
" operation tests whether the union value is in the given
branch. The "apply
" operation extracts the value and the "set!
"
operation modifies the value of an existing union.import from Union(num: Integer, rec: Record(c: Character, s: String)); -- Generic constructors u := [3]; u := [[char "c", "hello"]]; -- Specially named constructors u := union 3; u := union record(char "c", "hello");
-- Construction using field names u := [num == 3] u := union(rec == record(char "c", "bye")); -- Testing the case of a union. u case num; -- This returns false now. u case rec; -- This returns true now. -- Access the union value as a record. h := u.rec.s;Using the constructors with keyword arguments is particularly useful when more than one branch of a union has the same type:
MyType(R: Type, E: Type): with { fun: Union(coef: R, expon: E) -> %; } == ... -- This import causes both union branches to be `Integer'. import from MyType(Integer, Integer); fun [coef == 7]; fun [expon == 7];
13.9 : Category
Type
in Aldor is a value which, itself,
belongs to the type "Category
".
Categories allow parameterized constructions to specify the
requirements on type-valued parameters.
A type satisfies a category if it provides all the required exports.
The keyword with
is used to form basic Category
values
and it is possible to test whether a type satisfies a category at evaluation
time, using has
.
For more discussion on categories, see section 7.9
13.10 : Join(C1,...,Cn)
Join(C1,...,Cn)
" is a category which has
the union of all the exports of the argument categories C1, ... , Cn.
Conditional exports have their conditions or
ed.
13.11 : Boolean
Boolean
is used for the logical values "true
" and
"false
".
Values of this type are expected in the conditions of
"if
" and "while
", and in other forms.
If a value in one of these contexts is not of type "Boolean
", then an
implicit call is made to a Boolean
-producing function called "test
".
See Chapter 11.
Both ``#include "aldor"
'' and
``#include "axiom"
''
extend the basic Boolean
type.
13.12 : Literal
Literal
" is used to provide the textual form of
literal constants to the constant forming operations
"integer
", "float
" and "string
".
The result is in a form suitable for use with the exported conversion
operations from the "Machine
" package.
This allows constant forming functions to be evaluated at compile time.
See Chapter 11.
13.13 : Generator T
Generator T
is used to provide values of type T
serially to a "for
" iterator.
The following program shows a function to consume the
values of a generator:
consume(gg: Generator T, f: T -> ()): () == for t in gg repeat f t;Values of type
Generator T
are formed by "generate
" expressions.
T is the unified type of the arguments of the yield
s within the
generate
.
For more discussion on generators, see chapter 9.
The base Aldor library provides an extension which
allows generators to be formed and manipulated using functions.
See section 26.14.
13.14 : Exit
Exit
does not return to the invoking
context, and hence does not produce a local result.
Expressions formed with "break
", "goto
", "iterate
",
"never
", "return
" and "yield
"
all have type Exit
since they do not return directly to the
expression containing them.
The unification of type Exit
with any other type T is T.
This allows a function to end with a return
, e.g.:
#include "aldor" f(x: Integer): Integer == { if x < 0 then x := -x; return x }Variables and defined constants may not have type
Exit
, but functions may have Exit
as their
return type.
A function with return type Exit
promises
to never return to the caller.
error
: String -> Exit#include "aldor"
'' and
``#include "axiom"
'' provide this function.
Having Exit
as the return type allows this function
to be used in writing programmer-defined error functions. Note that a
call to error
will terminate the program.
Example:
#include "aldor" import from FormattedOutput; errDenom(d: T): Exit == error string."The denominator ~a cannot be used."(<< d); f(n: T, d: T): Ratio T == { d > 0 => n/d; d < 0 => (-n)/(-d); errDenom d; }(See section 26.13 for the use of ``
~a
''
in formatted output.)Exit
may appear in a value context.
A consequence of this is that expressions such as the following are
completely legitimate, but odd:
#include "aldor" l: List Integer := [2, 3, 4, if n < 10 then 5 else error "Too big"]; f(): Integer == return { return { return 7 } }
13.15 : Foreign I
Foreign
" allows programs to receive values from
or provide values to programs which are not written in Aldor.
The result of the function "Foreign
" is similar,
but uses an interface for a particular language or environment. import
" statement such as the following is used:
import { ISQRT: Integer -> Integer } from Foreign LispTo provide values to another environment, a qualified "
export
" statement is used:
export { J0: DoubleFloat -> DoubleFloat } to Foreign C;The environments which are understood, are:
Builtin: Type
Builtin
" can be used as an interface to abstract machine
level operations.
The Aldor compiler, for instance, generates calls to functions for
some of the run-time support. These functions are, themselves,
written in Aldor and made available to the abstract machine with
export declarations such as these:
export { rtCacheMake: () -> PtrCache; rtCacheCheck: (PtrCache, Tuple Ptr) -> (Ptr, Boolean); rtCacheAdd: (PtrCache, Tuple Ptr, Ptr) -> (); } to Foreign Builtin;
C: Type
C: Literal -> Type
C
" is used as an interface to functions written in
(or call-compatible with) the C programming language.
The type "C filename
" is used as an interface to
C functions or macros provided by a particular header file.
For details, see chapter 19.
Lisp: Type
Lisp
" is used as an interface to functions written in Lisp.
This is most useful when Aldor programs are compiled to Lisp code, and
allows access to all the operations of the underlying Lisp system (see
"import
" example above).
Fortran: Type
Fortran
" is used as an interface to functions written in Fortran.
For details, see chapter 32.
13.16 : Machine
Machine
".
This is the basic vocabulary of hardware-oriented data types out of which
all data values in Aldor are composed. Machine
do not themselves export any operations.
Instead, the operations are exported by Machine
at the same level
as the types. This is an example of what is known as a
multi-sorted algebra in the literature on type systems.Machine
" or its exports.
In practice, these types and operations are used within low-level libraries
to build a set of richer basic types,
which themselves provide relevant operations.Machine
are described below.
The exported operations are listed in section 14.1.
XByte, HInt, SInt, BInt
: TypeXByte
, HInt
, SInt
are
unsigned byte, half-precision and single-precision integer types,
capable of representing values in at least the ranges
0 to 255, -32767 to 32767 and -2147483647 to 2147483647, respectively.
The type BInt
provides a ``big'' integer type, which, in principle,
may be of any size. In practice, the size will be limited by considerations
such as the amount of memory available to a program.
SFlo, DFlo
: TypeBool
: TypeChar
: TypeNil, Arr, Rec, Ptr
: TypeNil
, Arr
, and Rec
may
be converted to type Ptr
as desired.
Word
: TypeSInt
, SFlo
and Ptr
may be converted to this type.