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
  1. identifier denotes any valid OQL identifier, except a keyword
  2. 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)
  3. 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'

Arguments in, out and inout

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:
  1. cond: a boolean expression,
  2. then_expr: expression of any type; is evaluated and returned if and only if the condition is evaluated to true
  3. 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 $\vert$ 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)'':
  1. the value of cond in the body of the function will be true or false,
  2. the value of then_expr in the body of the function will be "::a:=2"
  3. 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 Variable

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