User:Ashalkhakov/Functional Programming

Chapter one: programming with functions.

Outline:
 * arithmetic: constants and operations
 * closing over constants to define functions
 * combining functions

The notion of a function, a description of relationship between inputs and outputs of processes, predates modern digital computing by at least four centuries. This notion was found to be very useful by mathematicians, physicists and engineers of the past. Computer programs are also descriptions of how a result can be obtained from some inputs. So a natural question to ask is: can we define programs with (mathematical) functions? The answer is "yes", and the corresponding style of programming is called "functional programming".

In fact, there is no need to constrain ourselves with numeric functions. We can as well define functions that have some compound structures (e.g. sequences of numbers) as argument or result. Functions can also be arguments and results of other functions.

"Main" function
Let us write a simple expression the result of which is to be computed:

Unfortunately, to get a working program you must supply the "main" function, and we haven't even talked about functions in ATS yet! At this point, it can be helpful to not pay attention to what the expression  actually does. The whole program will print the result of evaluating,  , to the terminal. Other functions have to be applied in order to evaluate this expression: in this case, the operators  and. These operators are actually ordinary functions (with special notation) which have been predefined in the prelude (the standard library) which is part of the ATS system.

The prelude defines some commonly used functions and operators. For example,  is one such function. When the following expression

is evaluated, the value 1.414214 is displayed to the user.

It should also be noted that application of a function  to an argument   can be written as juxtaposition   instead of a more familiar. The reason for this digression is perhaps that function application is more frequent than multiplication in functional programming, so omission of some braces helps to reduce the notational burden. The operation of function application binds tighter than anything else, so  stands for   rather than. Multiplication is written explicitly using.

Here is an example involving application:

As expected, several applications can be combined in a single expression. means that  should be applied to the result of evaluation of. Parenthesis around  are necessary to indicate that   as a whole is an argument to.

Modules
It is common in programming to solve big problems by dividing them into smaller constituent parts, solving each in isolation, then combining your solutions into the one that solves your original problem. For example, if you write a calculator with command-line interface, then it makes sense to treat input (lexical and syntactic analysis) separately from expression evaluation and output. This program organization makes it easier to understand, develop, test, and change your program as you can do so incrementally, on a part-by-part basis.

ATS has a concept of "modules" which helps to organize a program in files. Each module contains the definition of a collection of functions, constants and operators that can be related to each other in some way. Modules are also indispensable for separate compilation. As a matter of fact, the prelude is nothing but a variety of modules. The program you write yourself is a kind of a module as well. Therefore, it should have a name, say  and should be stored in a file which in that case must have a name.

Defining variables
It is sometimes the case that some expression is very long and complicated, which makes it unwieldy to handle. We can break it up into smaller subexpressions and refer to each by its name. A variable declaration consists of a name and an expression that it stands for. This is how one can introduce a variable:

To the left of the equals sign we have a special keyword  and can be read as "the value of variable   is". At the right-hand side we write an expression. We say that " is bound to  " (or that   is a binding) in this compound expression. We may also say that  introduces , or that   is the binding occurrence of  .

You might write the following in Pascal:

or C:

There is an important difference however. In Pascal and C  is a (mutable) variable that is assigned the result of. In the ATS program above,  is merely a shorthand for. So variables in C and Pascal are representations for memory cells of the underlying machine, whereas variables in ATS are representations of mathematical variables. Variables in ATS are assigned to exactly once, at the point of definition, in contrast to variables in imperative programming.

Right now, what we call variables are actually constants. Don't let this terminological struggle confuse you. Variables have two roles in mathematics (and in functional programming as well). For example, in algebra, a variable can stand for some known or unknown (arbitrary) quantity. When it stands for a known quantity, we also call it a constant.

How variables interact with each other? For example, we write

and now that  is defined, we can use it in other expressions that follow, thus:

Following our intuition that variables are abbreviations of expressions, this program is equivalent the one below, where occurrences of variables,  ,   are replaced with their corresponding expressions:

In other words, to evaluate a program containing variables, replace every variable with what it stands for and then proceed as usual (since there will be no variables left).

"Let" expressions
Expressions we discussed previously (constants, arithmetic operators and some functions) have a property that they can nest into each other. Variable definitions as discussed in the previous section can nest as well, and the  construct is for doing exactly that.

Here is an example expression:

It is a compound expression. First,  is bound to. After that,  is evaluated, keeping in mind that   is defined to be the same as. So the result of evaluation of this expression is what we write between  and   keywords.

Here is another example:

To evaluate this expression is the same as to evaluate  with   substituted for. In other words, we substitute  for all occurrences of   in.

Here's a more realistic example:

In this program,  stands for calculated area of a circle of radius. As you can see, a  expression can introduce more than one variable.

The program above can also be rewritten thus:

Defining new functions
In a functional programming language you can surely define new functions by yourself. User-defined functions can be used exactly as the predefined ones.

A  keyword is for introducing functions. For example, we define a function  for calculating an area of a circle as follows:

So, a function definition consists of a name for a function, a list of arguments, and a body (the expression to the right of the equals sign).

The arguments appearing within parenthesis on the left-hand side of a function definition, such as  in our example above, are called the formal arguments (or formal parameters) of the function. In order to use a function, one applies it to actual arguments (also called actual parameters). For example, in  the function   is applied to the actual argument. The actual argument corresponding to  is. Function arguments are variables the value of which we will only get to know when we apply a function to an actual argument: they stand for arbitrary expressions.

The function  takes radius as an argument. Both radius and result must be of the type, that is to say,   cannot be applied to anything other than a   (double-precision floating-point numbers, actually a machine representation for "ideal" real numbers) and it is guaranteed that an application like   will also produce a. Like Pascal, ATS is very strict about types. Unlike C, ATS does not support implicit coercions of numeric types. For example,  is not a well-typed expression in ATS (whereas it is in C).

can be used just like any predefined function. For instance:

When run, this program prints half the area of a circle of radius 10.

It should be noted that a function without argument is just a constant.

Program evaluation with functions
We learned that a functional program generally consists of a collection of function definitions and one special expression (namely, the  expression). The execution of such program begins with the evaluation of. This initial expression is repeatedly replaced by its result after evaluating a function application. This process is also called reduction. One step of the process, evaluating a single function application, is called a reduction step. To evaluate an expression  (called a redex, from reducible expression), take the body of   and replace all occurrences of a formal argument of   in it with. When the expression contains no reducible sub-expressions, the reduction stops, and we say that such an expression is in a normal form. Evaluating an expression can be seen as searching for its normal form.

A simple example illustrating function evaluation follows. Suppose we have a program:

The task is to evaluate (reduce). It is of the form   mentioned above (to be sure, note that   can be identified with   and   with , respectively) and consists of two applications: first   and   are supplied as actual arguments to  , and the result is the argument to. Thus we have two redexes here. First we obtain  from   by arithmetic. Our initial expression  becomes. Since it is still a redex, we substitute  for all occurrences of   in the body of   to obtain , in other words, our expression   becomes. This can be reduced further by rules of arithmetic, and the expression becomes  which is a constant that we cannot simplify any more. Hence  is a normal form of.

We can represent the above description with formulae. We write $$\rightarrow$$ between two expressions to show that the expression on the left-hand side is reduced to the one on the right-hand side (that is, we denote a reduction step with an arrow), and underline redexes in an expression to show what are we going to do next. The $$\rightarrow$$ is left-associative, that is to say, reduction steps are carried from left to right, and $$a \rightarrow b \rightarrow c$$ should be read $$(a \rightarrow b) \rightarrow c$$ (we omit braces for clarity and write each step on a new line).

$$ \begin{align} \underline{\operatorname{double\_succ}\ \underline{(3 * 4)}} & \rightarrow \underline{\operatorname{double\_succ}\ 12} \\ & \rightarrow \underline {\underline{2 * 12} + 1} \\ & \rightarrow \underline{24 + 1} \\ & \rightarrow 25 \end{align} $$

Names of functions and operators
Many standard functions are predefined in the prelude, and we will discuss some of them in the following sections.

Here we give informal lexical rules of function naming. Function names start with a letter and are followed by zero or more letters, digits, or the symbol _ (underscore). Both lower and upper case letters are allowed: these are treated as distinct (thus A and a are not the same). For example, the following names are accepted by ATS:

,,  ,

To make long names easier to read, either underscore or the "camel case" are used. However, the style adopted by the prelude is to use underscores.

Carefully chosen names are helping to convey the intended meaning behind functions and variables. People are more willing to understand programs with well-chosen names. However, in ATS we also use various abbreviations; names of function arguments may as well be very short. If you are troubled with this, don't worry as the necessary skill is acquired very quickly.

As is common in mathematics, we may also use various symbols instead of names: this is very useful for defining your own operators, such as  and. Application of functions taking one argument can be written in postfix as well as prefix form, while application of functions of two arguments can be written infix. This kind of syntax extensibility is very uncommon in languages such as Pascal and C. We should note that, for example, the syntax for  is not that hard-coded into the ATS compiler as it is in C. The mechanisms of symbol overloading and fixity declarations are used instead; we will cover these later.

Predefined functions on numbers
Numbers in ATS are of two kinds: integers (such as,   and  ) and floating-point (such as  ,  ,  ,   and  ). The character  in floating-point numbers means `times ten to the power of'. Thus,  is read "0.7 times ten to the power of 2", that is, $$0.7*10^2 = 70$$. The number  is in fact $$0.1*10^{-3} = 0.0001$$. The prelude modules  and   define some common functions on integers and floating-point numbers, respectively. The four arithmetic operators,  ,  ,   are defined for both integers and floating-point numbers, for instance:

and

Arguments to an arithmetic operator must both be of the same type: either integer or floating-point. For example, the expression  is not accepted by the compiler. You must use the standard (overloaded) functions that convert numbers. is a function that converts an integer to a double precision floating-point number;  is for doing the reverse. Thus the following is acceptable:

Some standard functions on integers are:


 * : the absolute value of a number
 * : negation of a number (also written )
 * : the successor of a number
 * : the predecessor of a number
 * : the remainder of division of one number by another (also called modulo)
 * : the greatest common divisor of two numbers
 * : raise the first number into n-th integer power (where n is greater than or equal to 0)

These functions (excluding  and  ) will also work on float-point numbers. We say that these names are overloaded to mean that they may stand for different functions in different contexts. For example, consider  and  : these are two expressions that apply   to numbers of different type. In both cases, we have that different functions have the same name. Which is which, then? In the first case the argument is an integer, thus   is a function on integers in this context. This is similar to mathematics, where we write $$2 + 2$$ and $$\frac{1}{2} + \frac{3}{4}$$ but mean different operations in each case.

Predefined functions on booleans
Booleans can be regarded as truth values, we write  to denote truth and   for falsity. We may also say that  and   are introducing values of boolean type,. For example, the operator  is for determining whether a number is smaller than another number:   evaluates to   iff   is strictly less than , and to   otherwise. To wit, the value of  is.

Operators for comparing numbers also include  (greater than),   (less than or equal to),   (greater than or equal to),   (equality) and   (inequality).

Standard logic operations such as conjunction (logical "and") and disjunction (logical "or") are available and written as  and , respectively. These can be used for combining boolean expressions. The operator  evaluates to   iff both of its operands evaluate to , and the operator   evaluates to true iff at least one of its operands evaluates to. For example,  evaluates to   because one of the operands (namely,  ) evaluates to. On the other hand,  evaluates to. An operator  negates a boolean: given ,   is the same as  , and   evaluates to   (you can of course substitute any boolean expression for boolean constants in the example). We can also compare booleans using the  operator, it works analogously to that of numbers, although we stress again that   for booleans is an operation different from   for integers despite the name.

Suppose we are given a boolean expression the value of which we do not know (that is, we do not know precisely if it is  or  ). We need an operation that can do something useful with such values by examining them. In ATS, there is a built-in operation  which takes three arguments. It's called the conditional function, and its first argument is called "precondition". Let's demonstrate its use by example. The result of evaluating the following program:

is  printed in terminal. The general rule is that if precondition ( in our example) evaluates to , then the result of evaluation of the conditional is the result of evaluation if its second argument. If, however, precondition is evaluates to, then the result of evaluation of the conditional is the result of evaluation of its third argument. The types of second and fourth arguments must match (in the same way that types of operands to  must match).

It should be pointed out that in contrary to "normal" functions, a conditional doesn't evaluate both second and third argument (and this follows from rules outlined in the previous paragraph). Let's try to sketch an evaluation of, assuming that   and  :


 * we evaluate  to
 * since the precondition is, we evaluate  , which is just

Thus the value of expression is. This minor detail is not very useful to know at this point, and we will revisit it later, after introducing other features. This process can also be illustrated with the notation we introduced in Program evaluation with functions:

$$ \begin{align} \underline{\operatorname{if}\ \underline{x < 5}\ \operatorname{then}\ \underline{x+y}\ \operatorname{else}\ \underline{x} } & \rightarrow \underline{\operatorname{if}\ \operatorname{false}\ \operatorname{then}\ \underline{x+y}\ \operatorname{else}\ \underline{x} } \\ & \rightarrow \underline{x} \\ & \rightarrow 6 \end{align} $$

Operators analogous to that of numbers are also available. You can think of booleans as of integers:  is the same as   and   is the same as. Here are some operations on booleans that you

Defining functions
In this section, we'll take a closer look at how a function is defined. There are many ways to do so, for example using a graph to visually illustrate dependency of a result upon an argument. As another example, a table can be used instead: in the simplest case, you simply enumerate arguments and the intended result. However, this is quickly getting tedious and impractical, so you might want to try something else, such as finding rules (laws) that turn argument into the result. Since we're doing programming here, we'll focus on this latter method. It is by no means the only one, neither it is the best for all circumstances. We leave it up to the reader to decide.

Definition by cases
It is sometimes necessary to distinguish a number of cases in a function definition. As an example, consider a function to take the absolute value of a number: for negative arguments the result is calculated differently than for positive ones. We can use an -expression to achieve that in ATS:

If you want to distinguish more than two cases, that can be obtained by nesting -expressions. For example, the following is a function  that we want to return   if its argument is negative,   if it is zero, and   otherwise:

We have enclosed the second  in parenthesis to stress the order of evaluation, it's perfectly legal to omit them because this is also the order used by convention. In such a compound expression, we first check if input is negative, then see if it equals zero. We leave it as an exercise for the reader to make sure that this is consistent with the rules for evaluating -expressions we have outlined in one of the previous sections. For clarity, you may also write this function as follows:

Definition using patterns
Most programmers having an exposure to either C or Pascal are well accustomed to the fact that variable names can only be introduced (bound) in two ways: through function definitions (variables also become formal arguments), or through variable definitions (such as the  we discussed earlier). Now, there is a mechanism in ATS called pattern matching the meaning of which is twofold: it allows you to *deconstruct* a value (to see if it happens to be of the form you anticipate), and it can also bind the parts of the value to some variables. An example should make things clearer.

We'd like to implement a function on booleans that returns true iff both of its arguments are true (that is, we are going to implement the operation of conjunction). Here's one way, using a  expression:

The body of the function  consists of a single   expression, which lists two clauses. Each clause consists of a pattern expression to the left of the  symbol and another expression (also called the body of the clause) to the right.

This seems strange to a new-timer, so we will try to make sense of this new  construct. Booleans can be seen as values that can only be constructed (introduced) in two ways: you either use  or you use   and it's safe to think that there are no more values of the type   except the two mentioned. What pattern matching allows you to do in this case, is to discriminate the two cases, just like an  expression. The code written above can be interpreted as "if the value of the first argument is, then the result of   is just whatever   is, otherwise it is  ". We find it's best to think of a  expression as a generalization of   expression. Whereas an  expression only works for booleans, a   expression will work on any datatype that you define (we will get to datatypes a bit later). For now, it suffices to know that an  expression can be replaced by the corresponding. Thus the function given above is the same as the one below:

We say that  and   are constructors of the type bool. In a  expression, you can enumerate all possible constructors of a datatype, and what is to be done if the value matches the pattern. In our case, a pattern is simply a constructor written with two parentheses:. Why the parentheses, then? This is because a pattern can also be a variable. For example, here is an example involving variables introduced in a pattern:

Here we implemented a disjunction with the help of a  expression. As you see,  and   are actually the same thing in an expression. However, when used in a pattern,  is the variable binding (and by the way, the   aka underscore we used in the previous example, is also a variable, but it is hidden: you can't use it an expression).

In the first branch, we know that  is the same as , whereas in the second branch   is renamed to. However, we don't use that binding.

Generally, a  expression follows this syntax:   (as you can see, the leading pipe   can be dropped, this is to make the life of a programmer simpler).

Let us try to find the rules for evaluating  expressions. First off, a  expression operates on some expression: after evaluating that expression to its normal form, we try the patterns one by one, top to bottom (or left-to-right, if you write the clauses in one line), to find the first pattern that matches the value of the expression.

When we find a clause with a matching pattern, the result of evaluating the body of the clause is the result of evaluating the whole  expression. If there is no match, it is a runtime error and the program evaluation must terminate. So, even if the program goes wrong, it will be stopped. We will cover the topic of mitigating failures of pattern matching at some other point.

You will certainly appreciate this feature after we introduce datatypes. For now, here's another example:

As you see, we need not account for  being   in the last clause of the   expression, because that case is already covered by pattern matching. This intuition is based on the fact that this function is equivalent to the one under the same name that we discussed in one of the previous sections.

To recap, the evaluation of a  expression involves the following:
 * evaluate what you will match your patterns against
 * try to match against the pattern of each clause, starting from the first one (in the program text)
 * on finding a match, evaluate the body of the pattern -- this is the intended result of evaluation

Definition by induction or recursion
So far, all the definitions we gave have a characteristic common to all of them. They are non-recursive. Syntactically, a non-recursive function never ever refers to itself, directly or indirectly. In the definition of a recursive function, the name of the function can 're-(oc)cur' in its body. Here is an example of a recursive definition:

The name of this function occurs in the body of the second clause of the  expression. Another example could be a solution to the problem stated as "given two integers  and , compute   raised to the power of  ". We present it as follows:

Here is another curious function:

Can you say what is the result of evaluating this function? There is no result! Thus we see that not all expressions in ATS have a meaningful normal form, some, such as, do not. We also say that evaluation of  does not terminate.

This leads us to think that we need to place some restrictions on the definition of recursive functions. For now, take a look at the overall structure of the two "valid" recursive functions we introduced in this section and try to notice what is common among them. Both operate on one of their arguments. Both handle two cases for such an argument, distinguishing  from everything else. The body of the case that handles  is not recursive at all (we call it the base case). The actual argument for the recursive call is getting closer to the base case (in our case, smaller than the corresponding formal argument of the function being defined).

In the definition of  given above, the base case is covered by the first clause of the   expression; in this case, the result can be determined directly (in other words, without a recursive call). In the second clause the following should be noted:


 * we know that the formal argument  is different from zero
 * in the recursive call, namely,   is smaller than

Definition by combination
By far the easiest way to define functions is by making use of other, previously defined functions and operators. For example, consider:

The function  depends upon a prior definition of. Moreover, we shall stress that many functions are partial, that is, not defined for all arguments, but only for some. For example, the evaluation of  (as defined in the previous section) will not produce the wanted result (it will simply evaluate forever!). We leave it as an exercise for the reader to figure out the conditions on inputs necessary for  to produce the intended result.

Some examples on predicates follow:

Note that, in the last example, the two occurrences of  mean different things: the same symbol is used for both defining functions and performing an equality test.

Scope
Intuitively, a variable must be defined before it can be used. We have as of yet glossed over the rules for determining where a variable can be used. Every variable is associated with some set of expressions where its occurrence is legal. This set is also called the scope of variable.

Consider this program:

Here, the scope of  is any expression after the binding site. To put it differently,  can occur in any expression whatsoever, provided that it follows the   expression in the program source code.

The scope of a variable introduced using the  expression is confined to expressions within. For example, in this expression:

is always, no matter what the surrounding context may be. For example,  always evaluates to.

Likewise for, the scope of formal arguments to a function is the function body (that is, an expression to the right of  ).

So, the following (contrived) function simply returns its argument:

The rule is that every time you see a variable, the "nearest" (innermost) binding takes precedence over others. In the example above,  is bound by a function (in other words,   is a formal argument), but in the body of the function it is rebound by a   expression. In the first non-binding occurrence,,   stands for whatever is passed to the function, in the second non-binding occurrence,   stands for   passed to the function and incremented by.

Comments
Sometimes it is necessary to elucidate the program text for human reader. We use comments for that, a comment is a part of the program text that is ignored by the compiler as it is only for people to rea. There are two ways to mark a section of program text as a comment:


 * with symbols  a comment is marked that extends to the end of the line
 * with symbols  a comment is marked that extends to the matching symbols
 * with symbols  a comment is marked that extends to the end of the file (essentially,   marks the end of file for the compiler that doesn't quite match the "real" one)

Comments marked the second way may be nested, that is, contain a comment themselves. For example, the following does not define a function as everything is considered a comment:

The comment is finished only when every  is closed with a matching. This is analogous to the habit of closing any opening parenthesis.

Finally, a  comment is useful when, say, you are experimenting with some code.