Functions

1.Function declarations

1.1.Basic function declaration and application

The most basic form of a function declaration in Mathemagix is

fun_name (arg_1: Type_1, ..., arg_n: Type_n): Ret_Type == fun_body

The function name fun_name can be an arbitrary identifier, such as hello, test? or infix +. Using the keywords :-> or lambda, it is also possible to construct function expressions. For instance, the following three declarations are equivalent:

cube (x: Int): Int == x*x*x;
cube: Int -> Int == (x: Int) :-> (x*x*x: Int);
cube: Int -> Int == lambda (x: Int): Int do x*x*x;

We recall that Mathemagix provides two syntaxes for function application: the classical syntax f(x) and the operator syntax f x. In the case of nested applications, these two syntaxes use a different grouping: whereas f(x)(y) parses as (f(x))(y), the expression f g x should be parsed as f(g(x)).

1.2.Tuple and generator arguments

Functions with an arbitrary number of arguments of a given type can be formed by using so called tuple types. More precisely, assume a function declaration of the form

fun_name (arg_1: Type_1, ..., arg_n: Type_n, x: Tuple X): Ret_Type == fun_body

Then the function fun_name applies to first arguments of types Type_1 until Type_n and optional arguments which are all of type X. For instance, the routine

extract (v: Vector Double, t: Tuple Int): Vector Double ==
  [ v[i] | i: Int in [t] ];

can be used to extract the vector of all entries with given indices from a given vector. Hence,

v: Vector Double == [ 1.0, 2.0, 6.0, 3.14, 2012.0 ];
mmout << extract (v, 1, 2, 3, 3, 0) << lf;

prints the vector .

In a similar way, one may pass a generator instead of a tuple as a last argument of a function.

1.3.Recursive functions

Functions definitions are allowed to be recursive, such as the following routine for computing the factorial of an integer:

postfix ! (n: Integer): Integer ==
  if n <= 1 then 1 else n * (n-1)!;

Functions which are defined in the same scope are also allowed to be mutually recursive. An example of mutually recursive sequences are Hofstadter's female and male sequences and defined by , and

for . They can be implemented in Mathemagix as follows:

F (n: Integer): Integer == if n = 0 then 1 else n - M F (n-1);
M (n: Integer): Integer == if n = 0 then 0 else n - F M (n-1);

In large multiple file programs, it sometimes happens that the various mutually recursive functions are defined in different files, and thus in different scopes. In that case, prototypes of the mutually recursive functions should be defined in a file which is included by all files where the actual functions are defined. Protypes of functions are declared using the syntax

fun (arg_1: Src_1, ..., arg_n: Src_n): Dest;

In case of the above example, we might define prototypes for F and M in a file common.mmx:

F (n: Integer): Integer;
M (n: Integer): Integer;

Next, a first file female.mmx would include common.mmx and define F:

include "common.mmx";
F (n: Integer): Integer == if n = 0 then 1 else n - M F (n-1);

In a second file male.mmx, we again include common.mmx and define M:

include "common.mmx";
M (n: Integer): Integer == if n = 0 then 0 else n - F M (n-1);

1.4.Dependent arguments and return values

Besides mutually recursive function definitions, Mathemagix also allows for dependencies among the arguments of a function and dependencies of the return type on the arguments. Although the dependencies among the arguments may occur in any order, mutual dependencies are not allowed.

For instance, the following routine takes a ring together with an element of this ring on input and displays the first powers of this element:

first_powers (x: R, R: Ring, n: Int): Void == {
  p: R := x;
  for i: Int in 1 to n do {
    mmout << i << " -> " << p;
    p := x * p;
  }
}

In this example, the type Ring of R is a category. We refer to section ? and the chapter about categories for a declaration of this category and more details on how to use them.

The following code defines a container Fixed_Size_Vector (T, n) for vectors with entries of type T and a fixed size n: Int.

class Fixed_Size_Vector (T: Type, n: Int) == {
  mutable rep: Vector T;
  constructor fixed_size_vector (c: T, n: Int) == {
    rep == [ c | i: Int in 0..n ];
  }
}

The return type Fixed_Size_Vector (T, n) of the constructor fixed_size_vector depends on the argument n to the same constructor.

2.Functional programming

Functions are first class objects in Mathemagix, so they can consistently be used as arguments or return values of other functions. They can also be declared locally inside other functions or used as constant or mutable fields of user defined classes.

2.1.Functions as arguments

A simple example of a function which takes a function predicate as an argument is

filter (v: Vector Int, pred?: Int -> Boolean): Vector Int ==
  [ x: Int in v | pred? x ];

The map construct systematically exploits this possibility to use functions as arguments. For instance, the following instruction prints the vector :

mmout << map (infix *, [1, 2, 3], [4, 5, 6]) << lf;

2.2.Functions as return values

A typical example of a function which returns another function is

shift (x: Int): Int -> Int == (y: Int) :-> (x+y: Int);

This kind of functions are so common that Mathemagix provides a special syntax for it, which generalizes the syntax of basic function declarations:

fun_name (arg_11: Type_11, ..., arg_1n1: Type_1n1)
         ...
         (arg_k1: Type_k1, ..., arg_knk: Type_knk): Ret_Type == fun_body

This syntax allows to simplify the definition of shift into

shift (x: Int) (y: Int): Int == x+y;

2.3.Functions as local variables

In a similar way that functions can be used as arguments or return values of other functions, it is possible to locally define functions inside other functions. One typical example is

shift_all (v: Vector Int, delta: Int): Vector Int == {
  shift (x: Int): Int == x + delta;
  return map (shift, v);
}

Recursion and mutual recursion are still allowed for such local function declarations. For instance, we may generalize Hofstadter's example of female and male sequences by allowing the user to choose arbitrary initial conditions:

FM (n: Int, init_F: Int, init_M: Int): Int == {
  F (n: Integer): Integer == if n = 0 then init_F else n - M F (n-1);
  M (n: Integer): Integer == if n = 0 then init_M else n - F M (n-1);
  return F n;
}

2.4.Mutable functions

As in the case of ordinary variables, functions can be declared to be mutable, using the syntax

fun_name (arg_1: Type_1, ..., arg_n: Type_n): Ret_Type := fun_body

In that case, the function can be replaced by another function during the execution of the program:

foo (n: Int): Int := n*n;
foo := (n: Int) :-> (n*n*n: Int);

In section ? below, we will how the mechanism of conditional overloading can exploit this possibility to dynamically replace functions by others.

3.Discrete overloading

In classical mathematics, operators such as are heavily overloaded, in the sense that the same notation can be used in order to add numbers, polynomials, matrices, and so on. One important feature of Mathemagix is that it has powerful support for overloaded notations, which allows the programmer to mimick classical mathematical notations in a faithful way.

The simplest mechanism of discrete overloading allows the user to redefine the same symbol several times with different types. For instance,

dual: Int    == 123;
dual: String == "abc";

In this case, the variable dual can both be interpreted as a machine integer and as a string. For instance, assuming the above declaration, the following code is correct:

hop : Int    == dual + 1;
hola: String == dual >< "def";

Indeed, in the definition of hop (and similarly for hola), the code dual + 1 only makes sense when dual is of type Int, so the correct disambiguation can be deduced from the context. On the other hand, the instruction

mmout << dual << lf;

is ambiguous and will provoke an error message of the compiler. In such cases, we may explicitly disambiguate dual using the operator :>. Both the following two lines are correct:

mmout << dual :> Int << lf;
mmout << dual :> String << lf;

In case of doubt, successions of disambiguations, such as 123 :> Integer :> Rational can be useful in order to make the meaning of an expression clear.

Of course, overloading is most useful in the case of functions, and the mechanism described above applies in particular to this case. For instance, we may define

twice (x: Int): Int == 2*x;
twice (s: String): String == s >< s;

Then we may write

mmout << twice 111 << lf;
mmout << twice "hello" << lf;

Overloaded functions can very well be applied to overloaded expressions. For instance, the expression twice dual admits both the types Int and String. We may thus write

plok: Int == twice dual;
mmout << twice dual :> String << lf;

It should also be noticed that both the arguments and the return values of functions can be overloaded. For instance, we may overload twice a third time

twice (x: Int): String == twice as_string x;

After this, the expression twice 111 can both be interpreted as the number 222 and as the string "111111".

4.Parametric overloading

4.1.The forall construct

Besides discrete overloading, Mathemagix also features parametric overloading, based on the forall construct. In this case, the overloaded value no longer takes a finite number of possible types, but rather an infinite number of possible types which depend on one or more parameters.

The general syntax for making one or more parametrically overloaded declarations is

forall (param_1: Type_1, ..., param_n: Type_n) declarations

The parameters param_1, ..., param_n are usually types themselves, in which case their types are so called categories. For instance, consider the following declaration:

forall (R: Ring) cube (x: R): R == x*x*x;

It states that for any type R which has the structure of a ring, we have a function cube: R -> R. Parametrically overloaded functions such as cube are also called templates. The conditions for R to be a ring are stated by declaring the category Ring. One possible such declaration is the following:

category Ring == {
  convert: Int -> This;
  prefix -: This -> This;
  infix +: (This, This) -> This;
  infix -: (This, This) -> This;
  infix *: (This, This) -> This;
}

In this case, any type R with the usual operations and a converter Int -> R will be considered to be a ring. The mere presence of these operations in the current context is sufficient: the compiler does not check any of mathematical ring axioms.

Assuming a context in which both the types Int and Double are present, we may apply the template cube as follows:

mmout << cube 123 << lf;       // applies cube: Int -> Int
mmout << cube 1.0e100 << lf;   // applies cube: Double -> Double

Although this is usually not necessary, it is sometimes useful to make the values of the parameters explicit, for instance in order to make expressions less ambiguous. This can be done using the # infix operator. For instance,

mmout << cube#Int (123) << lf;
mmout << cube#Double (1.0e100) << lf;

4.2.Grouping forall statements

It often happends that several generic routines share the same parameters. In that case, they can be grouped together in a common forall block. For instance, given a ring , assume that we are developing a container Tangent R for arithmetic in (see also the section on container classes):

class Tangent (R: Ring) == {
  b: R;  // base point (coefficient of 1)
  s: R;  // slope      (coefficient of epsilon)
  constructor tangent (b2: R) == { b == b2;  == 0; }
  constructor tangent (b2: R, s2: R) == { b == b2; s == s2; }
}

Then we may define a ring structure on Tangent R using

forall (R: Ring) {
  convert (i: Int): Tangent R ==
    tangent (i :> R);
  prefix - (x: Tangent R): Tangent R ==
    tangent (-x.b, -x.s);
  infix + (x: Tangent R, y: Tangent R): Tangent R ==
    tangent (x.b + y.b, x.s + y.s);
  infix - (x: Tangent R, y: Tangent R): Tangent R ==
    tangent (x.b - y.b, x.s - y.s);
  infix * (x: Tangent R, y: Tangent R): Tangent R ==
    tangent (x.b * y.b, x.b * y.s + x.s * y.b);
}

4.3.Additional assumptions on parameters

It often happens that template parameters need to fulfill several requirements, such as being a ring and an ordering at the same time. Mathemagix provides the keyword assume for this purpose. For example:

forall (R: Ring)
assume (R: Ordering)
operator <= (x: Tangent R, y: Tangent R): Boolean ==
  x = y or x.b < y.b;

Such additional assumptions can naturally be included in forall blocks:

forall (R: Ring) {
  ...
  infix * (x: Tangent R, y: Tangent R): Tangent R ==
    tangent (x.b * y.b, x.b * y.s + x.s * y.b);

  assume (R: Ordering)
  operator <= (x: Tangent R, y: Tangent R): Boolean ==
    x = y or x.b < y.b;
}

4.4.Partial specialization

It frequently occurs that for specific values of the template parameters, the generic implementation of the template can be further improved. For instance, consider the following generic implementation of a power operations on field elements:

forall (F: Field)
infix ^ (x: F, i: Int): F == {
  if i = 0 then return 1;
  else if i > 0 then {
    s: F == square (x^(i quo 2));
    if i rem 2 = 0 then return s;
    else return x * s;
  }
  else return (1 :> F) / x^(-i);
}

This implementation uses binary powering and is more or less as efficient as it can get for elements in a generic field. However, in the “field” Floating of arbitrary precision floating point numbers, we have fast implementations of the operations exp and log, so the following implementation is even more efficient in this specific case:

infix ^ (x: Floating, i: Int): Floating == {
  if x > 0 then
    return exp (i * log x);
  else if x < 0 then {
    if i rem 2 = 0 then return (abs x)^i;
    else return -(abs x)^i;
  }
  else {
    if i >= 0 then return 0;
    else return 1.0 / 0.0;
  }
}

Mathemagix allows the above two implementations to happily coexist, thanks to the mechanism of partial specialization. Without this mechanism, any expression of the form x^i with x: Floating and i: Int would be ambiguous, since both implementations of infix ^ allow for the interpretation of x^i as an expresson of type Floating. The idea behind partial specialization is that we always prefer the most particular (i.e. “best”) implementation, in this case the second one.

In general, a first template (or function) is more particular than a second one, if any possible type (i.e. by substituting concrete values for the parameters) of the first template is also a possible type of the second one. For instance, the first above implementation of infix ^ is not more particular than the second one, since the the type (Rational, Int) -> Rational of (infix ^) # Rational is not a possible type of the second implement of infix ^.

It should be noticed that the relation “is more particular than” is only a partial ordering. For instance, none of the two following routines is more particular than the other one:

forall (R: Ring) mul (i: Int, x: R): R == (i :> R) * x;
forall (R: Ring) mul (x: R, i: Int): R == x * (i :> R);

Applying the function mul to two elements of type Int would therefore be ambiguous. This ambiguity can be removed by implementing the routine

mul (i: Int, j: Int): Int == i * j;

Indeed, this routine is more particular than each of the two generic implementations of mul, so it will be the preferred implementation whenever mul is applied to two elements of type Int.

It should be noticed that the relation “is more particular than” does not necessarily mean that some of the parameters have to be substituted by actual values in order to become “more particular”. For instance, consider the prototypes of two templates for the computation of determinants:

forall (R: Ring)  det: Matrix R -> R;
forall (F: Field) det: Matrix F -> F;

Then the second template is more particular than the first one, so it will be the preferred implementation when computing the determinant of a matrix with entries in a field.

5.Type conversions

5.1.Implicit conversions

In Mathemagix, the special operator convert is used for type conversions. For instance, given an integer x: Integer and a converter

convert: Integer -> Rational;

we may use the expression x :> Rational in order to explicitly convert x into a rational number.

The operator convert is used for implicit type conversions only under the following particular circumstances:

5.2.Explicit conversions

Except for the above special cases, Mathemagix does not perform any implicit conversions. For instance, even if we have an implicit converter, then application the following function cannot be applied to an expression of type Integer:

foo (x: Rational): Rational == x + 1/x;

Nevertheless, using the mechanism of parametric overloading, we may define foo in the following way so as to make this possible:

forall (F: To Rational)
foo (x_orig: F): Rational == {
  x: Rational == x_orig :> Rational;
  x + 1/x;
}

Here To T stands for the following parameterized category:

category To (T: Type) == {
  convert: This -> T;
}

The new version of foo cannot only be applied to expressions of type Integer, but to any expression of a type F with a converter convert: F -> Rational.

The above way of adapting function declarations so as to accept convertable arguments is so common that Mathemagix provides a special syntax for it. This syntax allows us to simplify the second declaration of foo into

foo (x :> Rational): Rational == x + 1/x;

We call this mechanism a priori type conversion of function arguments.

A similar syntax may be used for a posteriori type conversion of the return value:

bar (i: Integer) :> Integer == 1 - i;

This definition is equivalent to

forall (T: From Integer)
bar (i: Integer): T == (1 - i) :> T;

where

category From (F: Type) == {
  convert: F -> This;
}

5.3.On the careful use of type conversions

The mechanisms of a priori and a posteriori type conversions are powerful, but one should be careful not to abuse them. For instance, at a first sight, it may be tempting to allow for a priori type conversions for all routines on rational numbers:

infix + (x :> Rational, y :> Rational): Rational == ...;
infix - (x :> Rational, y :> Rational): Rational == ...;
infix * (x :> Rational, y :> Rational): Rational == ...;
infix / (x :> Rational, y :> Rational): Rational == ...;
...

Indeed, this would immediately give us support for the notation x + 1 whenever x is a rational number. However, this kind of abuse would quickly lead to ambiguities, since it also allows the addition on rational numbers to be applied to two integers. Although many of these ambiguities are automatically resolved by the partial specialization mechanism, they tend to become a serious source of problems in more voluminous mathematical libraries with many types and heavily overloaded notaions.

Besides the semantic correctness issue, there is also a performance issue: the compiler has to examine all possible meanings of ambiguous expressions and then determine the preferred ones among them. It is therefore better to reduce potential ambiguities as much as possible beforehand. In the above case, this can for instance be achieved by using the following declarations instead:

infix + (x: Rational, y :> Rational): Rational == ...;
infix - (x: Rational, y :> Rational): Rational == ...;
infix * (x: Rational, y :> Rational): Rational == ...;
infix / (x: Rational, y :> Rational): Rational == ...;
...
infix + (x :> Rational, y: Rational): Rational == ...;
infix - (x :> Rational, y: Rational): Rational == ...;
infix * (x :> Rational, y: Rational): Rational == ...;
infix / (x :> Rational, y: Rational): Rational == ...;
...

In order to avoid the same kind of ambiguity as in the mul example from section ?, we will also have to provide the routines

infix + (x: Rational, y: Rational): Rational == ...;
infix - (x: Rational, y: Rational): Rational == ...;
infix * (x: Rational, y: Rational): Rational == ...;
infix / (x: Rational, y: Rational): Rational == ...;
...

In fact, most converters of a type T into Rational are actually compositions of a converter of T into Integer and the standard converter of Integer into Rational. Therefore, an even better idea is to replace the block of declarations with a priori conversions by

infix + (x: Rational, y :> Integer): Rational == ...;
infix - (x: Rational, y :> Integer): Rational == ...;
infix * (x: Rational, y :> Integer): Rational == ...;
infix / (x: Rational, y :> Integer): Rational == ...;
...
infix + (x :> Integer, y: Rational): Rational == ...;
infix - (x :> Integer, y: Rational): Rational == ...;
infix * (x :> Integer, y: Rational): Rational == ...;
infix / (x :> Integer, y: Rational): Rational == ...;
...

Indeed, besides the fact that we eliminate all possible ambiguities in this way, the above routines also admit more efficient implementations. In a similar way, for container types such as Polynomial R, we usually have special implementations for scalar operations:

forall (R: Ring) {
  infix + (p: Polynomial R, c :> R): Polynomial R == ...;
  infix - (p: Polynomial R, c :> R): Polynomial R == ...;
  infix * (p: Polynomial R, c :> R): Polynomial R == ...;
  ...
  infix + (c :> R, p: Polynomial R): Polynomial R == ...;
  infix - (c :> R, p: Polynomial R): Polynomial R == ...;
  infix * (c :> R, p: Polynomial R): Polynomial R == ...;
  ...
}

The compiler has been optimized so as to take advantage of the reduced amount of ambiguities when overloading operations in this way. This should lead to an appreciable acceleration of the compilation speed, provided that the programmer adopts a similar style when using the mechanism of a priori type conversions.

6.Conditional overloading

6.1.Conditional overloading of constant functions

Until now, we have only considered overloading based on the types of expressions. The mechanism of conditional overloaded allows us to overload functions based on dynamically evaluated conditions on values. Let us start with the simple example of Fibonacci numbers:

fib (n: Int): Int ==
  if n <= 1 then 1 else fib (n-1) + fib (n-2);

This example can be reimplemented using the mechanism of conditional overloading as follows:

fib (n: Int): Int == fib (n-1) + fib (n-2);  // general implementation
fib (n: Int | n <= 1): Int == 1;             // overloaded version

The idea here is to first specify a general implementation of the function, which can later be adapted to special cases. The overloaded versions of the function are potentially parts of other files.

In general, the syntax for a conditionally overloaded function declaration is

fun (arg_1: T_1, ..., arg_n: T_n | cond_1, ..., cond_k): R == body

where cond_1, , cond_k are conditions in arg_1, , arg_n. Inside the body of the overloaded function (or template), the special identifier former may be used as a name for the previous (fallback) version of the function (or template), before the overloading took place. In particular, the above overloaded declaration of fun is equivalent to

fun (arg_1: T_1, ..., arg_n: T_n): R ==
  if cond_1 and ... and cond_k then body
  else former (arg_1, ..., arg_n);

In the case when the function was not declared before, the function former simply raises an error whenever we call it. Hence, the first definition of the function is recommended to be an unconditional one, as in the introductory example fib.

Remark 1. The conditionally overloaded declarations are processed exactly in the same order as the declarations appear in the source file. In the case when these declarations are spread over several files, only the ones which explicitly occur in the inclusions of the current file matter, and they will be processed in the same order as we did the inclusions. In a given context, let

fun (arg_1: T_1, ..., arg_n: T_n |
     conds_11, ..., conds_1k1): R == body_1
...
fun (arg_1: T_1, ..., arg_n: T_n |
     conds_q1, ..., conds_qkq): R == body_q

be the sequence of all conditionally overloaded declarations of the same function symbol fun with a fixed type, ordered in the above way. Then the above sequence of overloaded declarations is equivalent in that context to the single declaration

bar (arg_1: T_1, ..., arg_n): R == {
  if      conds_q1 and ... and conds_qkq then body_q
  else if ...
  else if conds_11 and ... and conds_1k1 then body_1
  else    raise some_error;
}

A similar remark applies in the case of function templates.

6.2.Conditional overloading of mutable functions

The mechanism of conditional overloading also applies in the case of mutable functions, but with a slightly different semantics. The general syntax for a conditionally overloaded mutable function declaration is

fun (arg_1: T_1, ..., arg_n: T_n | cond_1, ..., cond_k): R := body

In the present case, such a declaration is equivalent to

fun := lambda (former: (T_1, ..., T_n) -> R): (T_1, ..., T_n) -> R do
         (lambda (arg_1: T_1, ..., arg_n: T_n): R do
            if arg_1 and ... and arg_n then body
            else former (arg_1, ..., arg_n)) (fun);

At initialization, fun contains a function which throws an error message for all inputs. There are two important difference with the semantics described in the previous section:

  1. Nothing prevents the user to modify the value of fun elsewhere in the program; after all, fun is a mutable variable.

  2. In the case when the conditionally overloaded mutable function declarations are spread over several files, the current value of the mutable function is stored in a unique global variable which is common to all files. In particular, its value does not depend on the context, and only depends on the order in which the various files in the project are initialized (and on any other assignments of the mutable function that might occur; see the previous point).

Let us illustrate this difference with an example. Assume that we have four files a.mmx, b.mmx, c.mmx and d.mmx with the following contents:

a.mmx

f (i: Int): Int == 1;

b.mmx

include "a.mmx";
f (i: Int | i = 2): Int == 2;
b (): Void == mmout << [ f 1, f 2, f 3 ];

c.mmx

include "a.mmx";
f (i: Int | i = 3): Int == 3;
c (): Void == mmout << [ f 1, f 2, f 3 ];

d.mmx

include "b.mmx";
include "c.mmx";
b();
c();

Execution of the program d.mmx yields

[ 1, 2, 1 ]
[ 1, 1, 3 ]

If we replace == by := in all overloaded declarations of f, then we obtain the output

[ 1, 2, 3 ]
[ 1, 2, 3 ]

In other words, in the first case, each individual file is only aware of the overloaded declarations that occurred in the file itself and in all recursively included files. In the second case, f is a global mutable variable which is shared by all files.

In an interactive editor such as TeXmacs, conditional overloading of mutable functions is very useful, because we may use it to customize the behaviour of common editing actions as a function of the context. For instance, key presses might be handled by a global function

key_press (key: String) := insert_character (key);

Handlers for particular keys may then be defined whereever appropriate

key_press (key: String | key = "enter") := insert_newline ();

Similarly, special behaviour may be defined inside particular contexts. For instance, in a computer algebra session, pressing “enter” should evaluate the current input:

key_press (key: String | key = "enter", inside_shell_input? ()) :=
  evaluate_current_input ();

Of course, attention should be paid to the declaration order: the most general routines should be declared first if we don't want them to be overridden.

6.3.Conditional overloading of function templates

The conditional overloading mechanism also applies to (constant) function templates. The syntax for a conditionally overloaded function template declaration is

forall (P_1: C_1, ..., P_p: C_p)
tmpl (arg_1: T_1, ..., arg_n: T_n | cond_1, ..., cond_k): R == body

Mutable function templates are not supported.

6.4.Performance issues

In case of the mechanisms of discrete and parametric overloading, the actual resolution of the overloaded expressions (that is, the process of assigning disambiguous meanings to all subexpressions) is done during the compilation phase. This makes this kind of overloading very efficient: no matter how many times a function is overloaded, applying the function to actual values is as efficient as if the function were overloaded only once.

The mechanism of conditional overloading is more dynamic: the conditions under which a particular code gets executed are tested only at run time. Although the mechanism offers some flexibility that cannot be provided by purely static overloading mechanisms, the programmer has to be aware of this potential performance penalty. We also notice that some of the mechanisms for pattern matching to be described in the chapter on abstract data types rely on conditional overloading, and may thus suffer from a similar performance penalty.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License. If you don't have this file, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.