ATS: Programming with Theorem-Proving/Language Basics

Primitive Types
Primitive types in ATS are basically a representation of primitive C types. Every type in ATS can be mapped to a type in C. The mapping is as follows: The mapping of these types can be found in the file.

Note that type literals work in the same way as C, so  is a double, whereas   is a float.

Functions
Functions are declared by the keyword. The generic format of a function is:

A function doesn't have a return statement, but rather the function body is an expression that evaluates to the return value. So for example, a function to add two values would be something like:

Recursive Functions
A functions that calls itself is a recursive functions. Recursive functions have the same structure as generic functions, with the only difference that the keyword  replaces the keyword. An example of recursive function is a function to compute the Fibonacci value:

Tail Recursion
A special case of recursion is tail recursion. This happens when the return value of the recursive call is used as return value of the calling function. For example:

The function  tells if a number is even. The return value of  can be ,  , or the value returned by the call to. Because the return value of the call is returned without further processing, the recursive call to  is a tail recursive call.

Tail recursion is particularly important, because it allows, thanks to an approach known as tail call optimization, to be converted to loops. This means that there is no performance loss due to function calls, and tail recursion can be as fast as a for or while loop.

Improving Tail Recursive Functions with Accumulators
Recursive functions offer an elegant programming style, but they have a problem: for every call some data is put into the stack. This means that potentially, when the recursive function calls itself a lot of times, a stack overflow exception occurs. Tail recursion has theoretically the same problem, however, because of tail call optimization, the problem doesn't occur in practice; tail recursive functions are stack overflow free.

Considering the privileged status of tail recursion, an obvious question is whether it is possible to always use recursive functions instead of non-recursive ones. It turns out that any algorithm is implementable with tail recursion by adding extra parameters, called accumulators, to the recursive function. The purpose of these parameters is to accumulate information from the previous calls (hence the name), and carry it across different recursive calls.

For example, considering the code to implement the factorial:

This is an obvious implementation of the factorial, and it's recursive, but not tail recursive. To create a tail recursive version we add an accumulator to our function; this accumulator will contain the value of the factorial calculated so far:

Now, because we need our factorial function to have only one parameter, we add an extra function, which will be our factorial function, to initialize the accumulator and start the recursion:

With the help of accumulators any non-tail recursive function can be converted to tail recursive.

Simple Control Flow:
Control Flow in ATS, like in other functional programming languages, is mostly implemented in a different way from languages that follow structured programming. But there is a classic construct that has exactly the same structure in ATS as in imperative languages:. The general structure is:

Bindings
A binding is a constant value that is defined using the result of an expression. It can be compared to  in C/C++ or   in Java. A binding in ATS is declared using the keyword. Example of bindings could be  or. The name binding comes from the fact that we bind the names  and  to the expressions   and. A binding can also be defined using other bindings:

In this case  will obviously be initialized before.

Note that the bindings declared don't have a type. In fact, a binding declared with  will take its type from the expression it is bound to. It is possible to be more strict, and explicitly set a type for a binding. For example, the binding  defines a double, as the expression   is by default a double. If we write  then   will be a float. It is possible to say that  must be a float in any case, by saying. If you assign an incompatible type, for example, in, a compilation error will occur.

A similar, but slightly different, situation, is the assignment of a type not to a binding but to an expression. This can be achieved by writing something like. In this case the float type is given to the expression. So, while at the end our binding will always be a float, the process is slightly different.

Binding Scopes
Bindings are visible within some specific boundaries. These boundaries define the scope of the binding. Within the scope, the binding can be declared, initialized, and used.

The highest level for a scope is the top level: at this level the binding is not located within a function, and it is visible in all points of a file, starting from the point where the binding is declared:

We can't use  from , so our implementation is an error; but we can use it from.

At a lower level we have local bindings, which are defined within a segment of code. Local bindings can be used in two ways.

The first way is as a help to evaluate expressions. In this case the name used in the binding can be used to form other expressions. The generic structure in this case is:

What we have in this case it that the different names ,...,  are used to evaluate the expression. That is, the bindings are valid between the  and the. For example:

A different construct but with similar results is the following:

In this case we first define the expression and the declare the bindings. The bindings are effective between the ' ' and the. So the example above becomes:

The second way is to define one or more toplevel bindings:

Although the local bindings can be used only between the ' ' and the, the binding of the resulting expression is a toplevel binding. So, for example: