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
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; s == 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:
-
During the declaration of variables. With x:
Integer, we may thus write
a: Rational == x;
b: Rational := square x + 3; |
-
During assignments:
b: Rational := x;
b := x * x; |
-
When returning from a function:
f (x: Integer): Rational == x + 1;
g (x: Integer): Rational == {
if x >= 0 then return x;
else return 1/x;
} |
-
Inside the for-in construct:
for x: Rational in 1 to 10 do
mmout << x << " -> " << 1/x << lf; |
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:
-
Nothing prevents the user to modify the value of fun elsewhere in the program; after
all, fun is a
mutable variable.
-
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
-
-
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
If we replace == by := in all overloaded
declarations of f, then
we obtain the output
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.
© 2012 Joris van der Hoeven
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.