Subsections
Function Definition Statements
As introduced previously, OQL supports functions. There are two sorts of
functions definition syntax:
function definition expression and function definition statements.
The first ones, exposed in Section 5.26 can contain only one
expressions. That means that they cannot include neither selection,
neither statements, neither jump statements.
Furthermore, as only one expression is allowed, functions that need
several expressions, one must use the comma sequencing operaot to
seperate the expressions, thus making this code not always readable.
We introduce here the more general form of function definitions which
overrides the limitations of the previous form.
The general form of function defintion statements is:
function identifier ([arglist])
compound_statement
- identifier denotes any valid OQL identifier, except a keyword
- arglist is an optional comma-separated list
of identifiers
optionally followed, for default arguments, by a ``?''
and an expr, for instance:
(var1, var2, var3?
expr, var4? expr)
- compound_statement is a optionnal semicolon-separated
list of statements surrounded by braces.
For instance:
function f(x, y, z ? oql$maxint) {
if (x > y)
throw "error #1";
return x - y * 2 / z;
}
Argument Types/Return Type
Functions are not typed. That means that neither the
return type nor the argument types may be given.
It is why there is no function overloading mechanisms. To take
benefit of the overloading mechanisms, one must use methods.
Nevertheless, it is possible to add type checking by using
library functions such as is_int, is_string...
combined with the assert or the assert_msg
library functions.
For instance, to check that the first argument is an integer
and the second one a collection:
function doit(n, coll) {
assert_msg(is_int(n), "doit: argument #1: integer expected");
assert_msg(is_coll(coll), "doit: argument #2: collection expected");
// body of the function
}
The assert_msg check that its first argument is equal to
true, otherwiser an exception containing the second argument
string is thrown:
doit(1, list(1, 2, 3)); // ok
doit(1.2, list(1)); // raises the error:
// assertion failed: 'doit: argument #1: integer expected'
Furthermore, one cannot specify that an argument is an input
argument (in), an output argument (out) or
an input/output argument (inout).
In a function call, expressions and variables are always passed
by value not by reference, this means that the call to
``perform(x, y)'' cannot modify neither x nor y.
(In fact, yes it can! It is explained below. But forget it for now).
So, to modify variable through a function call, one needs to give the
reference (or address) of this variable, not its value.
In this case, the function must execute specific code dealing with
address variables instead of their values.
The refof operator, introduced in a previous section, gives
the reference of an identifier.
Remember that the expression refof x returns
the identifier x.
To make a function call using references one must do:
swap(refof x, refof y) or
the equivalent more compact form swap(&x, &y).
Contrary to C++, reference manipulation is not transparent in OQL:
to access the value of a reference, one must use the valof operator
(i.e. * operator).
The swap function which swaps its two inout arguments
has already been introduced:
function swap(rx, ry) {
v := *rx;
*rx := *ry;
*ry := v;
}
The arguments have been prefixed by r to
indicate that they are references.
So, the function call swap(&x, &y) will swap properly
the variables x and y.
One can add type checking in the swap function, as follows:
function swap(rx, ry) {
assert_msg(is_ident(rx), "swap: argument #1 identifier expected");
assert_msg(is_ident(ry), "swap: argument #2 identifier expected");
v := *rx;
*rx := *ry;
*ry := v;
}
Return Value
By default, a statement-oriented function returns no atom. To make a
function returning an atom, one must use the return statement
previously introduced. As a function has no specified returned
type, it may contained several return statements returning
atom of different types:
function perform(x) {
if (x == 1)
return "hello";
if (x == 2)
return list(1, 2, 3) + list(4, 20);
if (x == 3)
return 2;
if (x == 4)
return 'a';
}
alpha := perform(1); // alpha is equal to "hello"
alpha := perform(3); // alpha is equal to 2
alpha := perform(8); // alpha is equal to nil
Default Arguments
OQL provides support for default arguments in a function definition
statement.
The syntax for a default argument is: ``var ? expr''
or ``var := expr''.
As in C and C++, the arguments with a default value must not followed
by any argument with default values.
For instance, function f(x, y, z := "alpha") is valid
while function f(x, y, z := "alpha", t) is not valid.
Unval Arguments
Sometimes, it is interesting to prevent the evaluation of some input
arguments. For instance, let the function if_then_else which takes
three arguments:
- cond: a boolean expression,
- then_expr: expression of any type; is evaluated and returned
if and only if the condition is evaluated to true
- else_expr: expression of any type; is evaluated and returned
if and only if the condition is evaluated to false
It is clear that the following function definition:
function if_then_else(cond, then_expr, else_expr) {
if (cond)
return then_expr;
return else_expr;
is not correct as, although it returns the correct expression,
the then_expr and the else_expr will be evaluated.
For instance, if_then_else(x < 10, ::a := 2, ::b := 3) will
return 2 if x is less than 10, otherwise
it will return 3, but in any case, a will be assigned
to 2 and b will be assigned to 3.
So, one needs a way to tell the interpreter that we do not want to evaluate
the second and the third argument. The special character
before an argument means that this argument must not be evaluated.
In this case, this argument is substitued by the string representation of
the expression.
For instance, let the function if_then_else:
function if_then_else(cond, |then_expr, |else_expr) {
// ...
}
when performing the call ``if_then_else(x < 10, ::a := 2, ::b := 3)'':
- the value of cond in the body of the function will be
true or false,
- the value of then_expr in the body of the function will be
"::a:=2"
- the value of else_expr in the body of the function will be
"::b:=3"
The correct implementation of this function is as follows:
function if_then_else(cond, |then_expr, |else_expr) {
if (cond)
return eval then_expr;
return eval else_expr;
}
Scope of Variables
In the body of a function defintion, every variable on the left side of an
assignment has a local scope except if this variable is prefixed by
the global scope operator ::.
That means, that after the following statement sequence:
a := 2;
function doit() {
a := 1;
}
the variable a is still equal to 2. While after:
Recursivity
a := 2;
function doit() {
::a := 1;
}
the variable a is equal to 1.
Particularity
One can define a statement-oriented function inside the body of
a statement-oriented function, for instance:
function compute(amount_in_euro, usdollar_per_euro) {
function euro2usdollar(euro, usd ? usdollar_per_euro) {
return euro * usd;
}
x := euro2usdollar(euro * 1.24);
x += euro2usdollar(1000);
return x * .120;
}
Note that the function defined in the body of the function compute
has a global scope, that means that after one execution of compute
the function is available at the global level of the OQL session.
It is possible, that in a future version, the functions defined in
the body of a function definition will have a local scope.
The oql$functions value is a list whose elements are the name of all
the OQL functions
defined in the current OQL session. Each you add a user function,
this variable is updated.
At the beginning of a session,
the value of
textttoql$functions is:
list(is_int, is_char, is_double, is_string, is_oid, is_num, is_bool, is_bag,
is_set, is_array, is_list, is_coll, is_struct, is_empty, void, assert,
assert_msg, min, max, first, last, cdr, count, interval, sum, avg, is_in,
distinct, flatten, flatten1, tolower, toupper, tocap, toset, tolist,
tobag, toarray, listtoset, bagtoset, arraytoset, listtobag, settobag,
arraytobag, bagtolist, settolist, arraytolist, bagtoarray, settoarray,
listtoarray, strlen, substring, forone, forall, delete_from, get_from,
ifempty, null_ifempty, getone)
For instance, to put the complete definition of all
these functions into the variable functionString:
functionString := "";
for (x in oql$functions)
functionString += "FUNCTION " x + ": " + bodyof x + "\n";
The next section provides a few statement-oriented and
expression-oriented function definitions.
EyeDB manual