Previous Home Contents Next

Chapter 23: Sample programs

23.1 Hello

23.2 Factorial

23.3 Greetings

23.4 Cycle

23.5 Generators

23.6 Symbol

23.7 Stack

23.8 Recursive structures

23.9 Swap

23.10 Objects

23.11 Aldor libraries

23.12 Tables

23.13 Floating point

23.14 Mandel

23.15 Grobner bases

23.16 Integers mod n

23.17 Extensions

23.18 Text input

23.19 Quanc8

23.20 X Window System R11

23.21 AXIOM library

In this chapter we show several examples of Aldor programs. The first few give a brief introduction to the language, followed by some examples using more advanced features of the language. The final few examples show how Aldor can variously emulate or interact with other languages.

This chapter supplements the material in chapter 2 and in part II, the language description.


23.1 : Hello

This program prints out a familiar greeting, and then exits. Line 1 allows the use of the base Aldor library in this program. Line 3 prints the greeting. The program shows how a simple program is written, and the syntax for printing objects.

Line 1
Include the file aldor.as. This allows all the domains and categories in the base library to be used (see chapters 25 and 26).
Line 3
Print the message. print is an identifier referring to the console output stream. ``<<'' is an infix operator that prints an object to a given stream, and returns the stream as a value. This allows a cascade of ``<<'' calls.

23.2 : Fatorial

The next example shows how to define and call functions in Aldor, and a simple form of iteration.

This program defines two functions for calculating the factorial of an integer. The first shows a simple recursive version, the second is an iterative version.

Line 3
Defines a function called rfact, which takes an Integer and returns the result as an Integer
Line 5
Defines a function ifact with the same signature as rfact.
Line 6
Assigns the Integer 1 to i. Note that no declaration is needed if the compiler can infer the type of i. In this example, 1 must have been exported from the domain Integer. If, for example, SingleInteger was also imported, a declaration would have been necessary.
Line 7
The repeat keyword is used to indicate a loop, and the while keyword gives the test for the loop, in this case the loop will exit once n is less than or equal to one.
Line 8,9
The loop body -- multiply i and decrement n by one. Note that it is legal to assign to a function's parameters.
Line 11
The last statement in the function body gives the value to be returned.



23.3 : Greetings

Most operations in Aldor are defined in and exported by domains. In order to import from a domain, the import statement is used. Imports implicitly happen for the types of parameters of a function, and its return value. The example program below shows the use of the import statement, and also how to read user input.

This program declares functions to read a name from the console, and then print a personalized note for that name.

Line 1
include the base library
Line 5 and 6
import operations from the given domains, allowing us to use operations. Note that the import from statement is only needed when operations have not been implicitly imported.
Line 8
Defines a function, readName. Note that the () indicates that the function takes no parameters.
Line 14
Defines a function, greet. The return type of the function, () indicates that the function does not return a value.
Line 19
Call the function.

The program produces the following output (user input is in italic):

What is your name?
Ethel T Aardvark
Hallo Ethel T Aardvark, and goodbye...



23.4 : Cycle

This example demonstrates the manipulation of functions as first-class values, creating new closures over the course of the computation and multiple valued returns.

Line 8-10
Define some macros as shorthand for some common types used in the program. MapIII is a map type for functions that take three integers and return three integers.
Line 12
The identity function for MapIII. Note that +-> produces a closure (otherwise known as a ``lambda expression'', ``anonymous function'' or just ``function'').
Line 15
Define a function * (which is an infix operator) to be a function that given two functions (of type MapIII) returns a third function (in this case the composition of the two functions).
Line 18-23
Define a function that returns The new function is computed by repeated squaring.
Line 26-37
A test routine.
Line 27, 29
One can define constants inside functions. These have scope local to the function.

The program produces the following output:


23.5 : Generators

Iteration in Aldor is mainly achieved through the use of generators. These are objects representing the state of an iteration, and may be passed around as first-class values. There are two ways of creating generators in Aldor: The generate keyword, and the collect form, created by iterators.  

Line 5
Define a function which creates a generator of DoubleFloats.
Line 7
The generate keyword introduces the generator body. This is evaluated when a value from the generator is needed, up to the first yield statement, the value of which is returned as the next value for the generator.
Line 8-13
The body of the generator. In this case we yield the value of e-x2 for increasing values of x.
Line 16
Define a function to calculate the running mean of a sequence.
Line 19-25
The generator body. The value is calculated by maintaining a sum of values so far, and dividing by the number of values.
Line 30
create a generator and iterate over it. In this case the generator is the running average of 0, 0.1, ..., 1.0.
Line 33-34
where more than one iterator (formed by for and while) precedes the repeat, iteration is in parallel and terminates when one of the iterators reaches its end condition. In this case we want the first few values of a generator, so the parallel iteration ensures that the loop will complete at or before 10 iterations.

There is a further example on the use of generators in section 23.8


23.6 : Symbol

Most programming in Aldor is done by defining domains and packages. Here we give a small example of a domain. A package is simply a domain which does not export any operations involving values of type %.

Typically, writing a domain is done in four stages:

1.
decide what operations a domain will provide, and from which
categories it should inherit,
2.
decide how to represent the domain,
3.
implement the operations,
4.
test the domain.
In this example we show how to define a domain. The example is a symbol datatype which ensures that only a single instance of a given symbol is created.
Line 3
Define symbol to be a constant with the given type -- in this case Symbol is a Domain which implements BasicType and provides 3 additional operations.
Line 3-6
Signatures for operations on the domain. % in the type refers to ``this Domain''.
Line 7
The add expression creates a domain from a sequence of definitions.
Line 8
Define how Symbols, our new type are represented. In this case, we just use a string. If the signature required that additional information was stored on the symbol we might want to use a different representation.
Line 11
Allow operations from the representation domain, i.e. String to be used.
Line 11
% can be used inside the add body to refer to the current domain.
Line 13
define a local variable for storing the symbol table.
Line 17-31
Define functions exported by the domain.
Line 17, 20, 23
The operation rep allows a value of type % to be treated as if it were of type Rep. per is the inverse operation, i.e. it converts an object of type Rep into type %. These two operations are currently macros. See section 2.4.

23.7 : Stack

It is possible to define a function whose return type is a domain. In this case, the result is called a parameterized domain. The example is a simple stack with a few operations for creating new stacks, as well as pushing and popping values from an existing stack.

The chosen representation type is a list over the same domain as the stack. This allows us to implement the operations with minimum complications. A better representation might be a linked list of arrays, but this would clutter the example more than necessary.

Line 6
Define Stack to be a function that takes a BasicType (i.e. most of the domains in Aldor -- see chapter 25), and returns an object which satisfies BasicType, and additionally provides some stack operations. The interface is specified by the with construct.
Line 7
Declare an operation `empty?' which takes a value from the current domain as an argument, and returns a Boolean value. The line starting `++' is a description comment, and is saved along with the declaration of empty? in the intermediate file.
Line 14
The `export from' statement indicates that all operations exported by S should be imported when Stream S is imported.
Line 21
Define the representation of the Stack. This form of a define statement (without a declaration) implies that the type of Rep is exactly that of the type of the right hand side of the expression, i.e. Record(values: List S).
Line 22
Allow operations from Rep to be used. We do not need to say import from List S, as Record exports its arguments.
Line 25
Define a function which returns the internal list of values maintained by the stack.
Line 26-41
Define the operations required explicitly by the category.
Line 44-45
Define the operations needed to satisfy inherited operations.

Once the domain is defined, it may be tested. Aldor's interactive loop, aldor -G loop is useful here.

% aldor -G loop
%1 >> #include "stack.as"
()@
with
== add
%2 >> import from Stack SingleInteger, SingleInteger
%3 >> s: Stack SingleInteger := empty()
<stack> @ Stack(SingleInteger)
%4 >> push!(12, s)
<stack> @ Stack(SingleInteger)
%4 >> top s
12 @ SingleInteger
%5 >> pop! s
12 @ SingleInteger

We should probably test it a little further (e.g. boundary conditions), but this gives the general idea.


23.8 : Recursive structures

This program shows a recursively defined data type: the type of binary trees parameterized with respect to the type of data placed on the interior nodes. This tree type provides several generators which allow the trees to be traversed in different ways.

When compiled and run, this program gives the following output:


23.9 : Swaps

Higher order functions which construct types are first-class values. This example shows how to swap structure layers in a data type by using higher order functions as parameters to a generic program.

Line 5
Define a macro Ag as a shorthand for the type which takes a BasicType as an argument, and retuns a second type which is a Linear Aggregate over it.
Line 10
Define the swap function. This curried definition exchanges the X and Y layers in a structure. This function is written generically to use
  • generator from X Y S for the outer iteration,
  • generator from Y S for the inner iteration,
  • bracket from X S as the inner constructor,
  • bracket from Y X S as the outer constructor.
Line 21
Call swap to exchange the Array and List layers.

When executed via ``Aldorcmd -G run swap.as,'' the following output is produced.


23.10 : Objects

In Aldor, values are not self-identifying -- there is no way of retrieving a given value's type from the value itself.

The Aldor library provides this functionality in the object datatype, which holds both a value and its type.

The following example shows a use of this library.

Line 7
Define a function, bobfun, to take object arguments. The arguments are objects whose type slots satisfy the category BasicType.
Line 8
Use the avail operation to split an object into its type and value components, then call the local function f on the dependent type/value pair.
Line 10
Print the object value. The << operation is available, because each object's type satisfies BasicType.
Line 16
Form a list of BasicType objects. Each is formed with the object function from Object(BasicType).
Line 22
Call bobfun on each of the objects in the list.

When run with ``Aldorcmd -G run objectb.as'' this program

The richer the category argument to Object, the more interesting operations may be performed on the object values. A second example of using Object is shown below. In this case each object value belongs to some ring, and this fact is used in the arithmetic calculation.

Line 7
Define a function, robfun, to take object arguments. This time the arguments are objects whose type slots satisfy the category Ring. Again avail is used to split an object into its component parts (type and value).
Line 11-14
Perform various arithmetic operations on the value. All of + - * ``^'' and 1 are provided by the particular object.
Line 16-20
Print the results of the arithmetic. This is possible because each Ring provides a << operation.
Line 23
The has operator tests whether a given domain satisfies a particular category. This test is made at run-time.
Line 25-26
Inside an if statement, if it can be deduced that an imported domain satisfies an additional category (using the information in the evaluation of the if expression), then the additional operations are made available within the then branch of the if statement. In this case, < and > are available because T is seen to also satisfy Order.

The output of running this program with ``Aldorcmd -G run objectb.as'' is shown below.


23.11 : Aldor libraries

Here we give some examples of code which uses a number of domains from the libraries provided with Aldor. Four libraries are provided in the Aldor distribution:

The first two libraries are documented elsewhere (see
chapter 25 and chapter 26 for libaldor and chapter 18 for libaxiom).

Source code for libaldordemo is provided in the distribution. It includes the following types: DirectProduct, GroebnerPackage, IndexedBits, MultiDictionary, ListMultiDictionary, MatrixOpDom, Matrix, NonNegativeInteger, PolynomialCategory, Polynomial, IntegerPrimesPackage, RandomNumberSource, SmallPrimeField and Vector. To use this library, use #include"aldordemo".

The libaldorX11 library contains declarations for the data structures and functions found in the Xlib library. This library provides an interface to the low level X Window System R11 functions, on top of which can be built various toolkits.


23.12 : Tables

The next example shows a possible implementation of a Table datatype using a hash table (this file is included in the aldor library).


23.13 : Floating point

The next example demonstrates the use of arbitrary precision floating point numbers

When executed via ``Aldorcmd -G run float1.as,'' the following output is produced.


23.14 : Mandel

The next example shows the use of machine-level floating point in Aldor.

Machine level operations are done inline when the optimizer is used while compiling (use the options ``-Q3 -Qinline-limit=10''). This has the result that the generated code speed is comparable with that of the equivalent code in languages such as C.


23.15 : Gröbner bases

Some symbolic algebra packages are supplied in the Aldor demonstration library. For example, GroebnerPackage provides a mechanism for computing the Gröbner basis of a list of polynomials. (These may be used to solve polynomial systems of equations, among other things.) Compile this file with the -laldordemo option to link with the appropriate library.

When executed via ``aldor -G run gbtest1.as -L aldordemo'' the program produces the following output:


23.16 : Integers mod n

This example shows how add-inheritance can be used in the implementation of integers modulo a particular number. This file is part of the aldor library.


23.17 : Extensions

Aldor allows the library types to be extended with new operations. For example, one may wish to add a DifferentialRing category to the language. The extension mechanism allows existing domains, such as Integer and Polynomial to include these new categories. The extension mechanism operates via the extend keyword.

The following example allows Segments to be formed over Ratio types. This does not work in the base library as it stands because the parameter of Segment is declared to be of type Steppable (amongst other things). Currently only Integer datatypes satisfy this category. extend

Line 3
Extend Ratio. In order to satisfy Steppable we need to provide one operation -- stepsBetween which returns the number of datapoints in a segment specified by start and end points, and a step length.
Line 14
Test the extended domain constructor.

23.18 : Text input

In the next example, the Aldor TextReader and TextWriter datatypes are used to provide a number of useful text processing operations.

Line 6
Define a predicate for testing whether a given character is a vowel
Line 11
Define a function to print the non-vowel characters in a file.
Line 12
TextReader provides a generator on a reader which returns the sequence of characters in the reader.
Line 14
write! puts a single character onto a stream. print is a stream which is attached to the default output device of the process.
Line 23-74
Define some functions which parse a stream as groups of columns. Such a facility is used in the awk command under UNIX, for example.
Line 23-36
Define SubString, which is used to hold subparts of a string, without copying the contents of the string itself. This datatype allows us to avoid copying the substring until it is unavoidable, at the cost of an additional record allocation. It is likely that the record allocation will be elided by the optimiser, whereas it is much harder for the optimiser to avoid the copy.
Line 34
Explode returns multiple values -- in this case the contents of the record. The values are split when passed to the substring function (from String).
Line 38
Split a line into its constituent columns, given a separator, sep.
Line 61
lines creates a generator which returns the contents of a file as a sequence of lines terminated by newline.
Line 66
<< may be used on any TextWriter.
Line 70
We could use the write! operation on streams in order to save copying the string while printing (the coercion does a copy). See basic.as
Line 82
Define a function, capitalize which constructs a stream that capitalizes the letters in a stream and then prints them on its argument.

23.19 : Quanc8

The next example gives a Fortran-style program for numeric integration. The program demonstrates how an algorithm described in the pre-structured programming era may be transcribed without introducing errors by reworking its logic. The program was transcribed from the textbook described in the first comment, and produced correct values on its first run. Of course, if you have access to a callable library containing the routines it should be possible to import the operations directly into Aldor.

The goto construct in Aldor takes the name of a label, and transfers control to that label. Labels are introduced by the @ symbol.


23.20 : X Window System R11

Aldor provides an interface to X Window System R11. The following example draws a Mandelbrot fractal set in a window and allows it to be resized, recolored, etc.


23.21 : AXIOM library

Aldor programs can run within the AXIOM environment, and can use all the categories, domains and packages supplied by AXIOM. The following example computes the Hilbert polynomial for a set of monomials.

Note that AXIOM domains and categories are used freely within the program, for example, OrderedSet, Monomial and List.

As well as providing all the AXIOM domains, libaxiom also extends a few so that features new in Aldor are (apparently) provided in the old AXIOM domain. As an example, in the function variables, the List M is iterated using a generator, the definition of which came from the Aldor extension of List.


Previous Home Contents Next