Functions and Procedures

There are two slightly different syntactic forms provided for the definition of a user function (as opposed to an intrinsic function). For the case of a function whose definition can be expressed as a single expression, an abbreviated form is provided. The syntax for the definition of user procedures is similar. Names for functions and procedures are ordinary identifiers and so obey the rules as given in Chapter 1 for other variables.

Contents

Functions

f := function(x1, ..., xn : parameters)
      statements
end function;
function f(x1, ..., xn : parameters)
      statements
end function;
This creates a function taking n≥0 arguments, and assigns it to f. The statements may comprise any number of valid Magma statements, but at least one of them must be of the form return expression;. The value of that expression (possibly dependent on the values of the arguments x1, ..., xn) will be the return value for the function; failure to return a value will lead to a run-time error when the function is invoked. (In fact, a return statement is also required for every additional `branch' of the function that has been created using an if ... then ... else ... construction.)

The function may return multiple values. Usually one uses the form return expression, ..., expression;. If one wishes to make some return value(s) undefined (so that the number of return values for the function is the same in all `branches' of the function) the underscore symbol (_) may be used. (The undefined symbol may only be used for final values of the list.) This construct allows behaviour similar to the intrinsic function IsSquare, say, which returns true and the square root of its argument if that exists, and false and the undefined value otherwise. See also the example below.

If there are parameters given, they must consist of a comma-separated list of clauses each of the form identifier := value. The identifier gives the name of the parameter, which can then be treated as a normal value argument within the statements. The value gives a default value for the parameter, and may depend on any of the arguments or preceding parameters; if, when the function is called, the parameter is not assigned a value, this default value will be assigned to the parameter. Thus parameters are always initialized. If no parameters are desired, the colon following the last argument, together with parameters, may be omitted.

The only difference between the two forms of function declaration lies in recursion. Functions may invoke themselves recursively since their name is part of the syntax; if the first of the above declarations is used, the identifier f cannot be used inside the definition of f (and $$ will have to be used to refer to f itself instead), while the second form makes it possible to refer to f within its definition.

An invocation of the user function f takes the form f(m1, ..., mn), where m1, ..., mn are the actual arguments.

f := func< x1, ..., xn : parameters | expression >;
This is a short form of the function constructor designed for the situation in which the value of the function can be defined by a single expression. A function f is created which returns the value of the expression (possibly involving the function arguments x1, ..., xn). Optional parameters are permitted as above.
f := function(x_1, ..., x_n, ... : parameters)
      statements
end function;
function f(x_1, ..., x_n, ... : parameters)
      statements
end function;
This creates a variadic function, which can take n or more arguments. The semantics are identical to the standard function definition described above, with the exception of function invocation. An invocation of a variadic function f takes the form f(y1, ..., ym), where y1, ..., ym are the arguments to the function, and m ≥n. These arguments get bound to the parameters as follows: for i < n, the argument yi is bound to the parameter xi. For i ≥n, the arguments yi are bound to the last parameter xn as a list [ * yn, ..., ym * ].
f := func< x1, ..., xn, ... : parameters | expression >;
This is a short form of the function constructor for variadic functions, otherwise identical to the short form described above.

Example Func_Recursion (H2E1)

This example illustrates recursive functions.
> fibonacci := function(n)
>    if n le 2 then
>       return 1;
>    else
>       return $$(n-1) + $$(n-2);
>    end if;
> end function;
>
> fibonacci(10)+fibonacci(12);
199
> function Lucas(n)
>    if n eq 1 then
>       return 1;
>    elif n eq 2 then
>       return 3;
>    else
>       return Lucas(n-1)+Lucas(n-2);
>    end if;
> end function;
>
> Lucas(11);
199
> fibo := func< n | n le 2 select 1 else $$(n-1) + $$(n-2) >;
> fibo(10)+fibo(12);
199

Example Func_Parameters (H2E2)

This example illustrates the use of parameters.
> f := function(x, y: Proof := true, Al := "Simple")
>    return <x, y, Proof, Al>;
> end function;
>
> f(1, 2);
<1, 2, true, Simple>
> f(1, 2: Proof := false);
<1, 2, false, Simple>
> f(1, 2: Al := "abc", Proof := false);
<1, 2, false, abc>

Example Func_Underscore (H2E3)

This example illustrates the returning of undefined values.
> f := function(x)
>    if IsOdd(x) then
>        return true, x;
>    else
>        return false, _;
>    end if;
> end function;
>
> f(1);
true 1
> f(2);
false
> a, b := f(1);
> b;
1
> a, b := f(2);
> b;   // Undefined value
>> b;
   ^
User error: Identifier 'b' has not been assigned

Example Func_Variadic (H2E4)

This example illustrates the use of variadic functions.
> f := function(x, y, ...)
>     print "x: ", x;
>     print "y: ", y;
>     return [x + z : z in y];
> end function;
>
> f(1, 2);
x:  1
y:  [* 2*]
[ 3 ]
> f(1, 2, 3);
x:  1
y:  [* 2, 3*]
[ 3, 4 ]
> f(1, 2, 3, 4);
x:  1
y:  [* 2, 3, 4*]
[ 3, 4, 5 ]

Procedures

p := procedure(x1, ..., xn : parameters)
      statements
end procedure;
procedure p(x1, ..., xn : parameters)
      statements
end procedure;
The procedure, taking n≥0 arguments and defined by the statements is created and assigned to p. Each of the arguments may be either a variable (yi) or a referenced variable (~yi). Inside the procedure only referenced variables (and local variables) may be (re-)assigned to. The procedure p is invoked by typing p(x1, ..., xn), where the same succession of variables and referenced variables is used (see the example below). Procedures cannot return values.

If there are parameters given, they must consist of a comma-separated list of clauses each of the form identifier := value. The identifier gives the name of the parameter, which can then be treated as a normal value argument within the statements. The value gives a default value for the parameter, and may depend on any of the arguments or preceding parameters; if, when the function is called, the parameter is not assigned a value, this default value will be assigned to the parameter. Thus parameters are always initialized. If no parameters are desired, the colon following the last argument, together with parameters, may be omitted.

As in the case of function, the only difference between the two declarations lies in the fact that the second version allows recursive calls to the procedure within itself using the identifier (p in this case).

p := proc< x1, ..., xn : parameters | expression >;
This is a short form of the procedure constructor designed for the situation in which the action of the procedure may be accomplished by a single statement. A procedure p is defined which calls the procedure given by the expression. This expression must be a simple procedure call (possibly involving the procedure arguments x1, ..., xn). Optional parameters are permitted as in the main procedure constructor.
p := procedure(x1, ..., xn, ... : parameters)
      statements
end procedure;
procedure p(x1, ..., xn, ... : parameters)
      statements
end procedure;
Creates and assigns a new variadic procedure to p. The use of a variadic procedure is identical to that of a variadic function, described previously.
p := proc< x1, ..., xn, ... : parameters | expression >;
This is a short form of the procedure constructor for variadic procedures.

Example Func_Procedures (H2E5)

By way of simple example, the following (rather silly) procedure assigns a Boolean to the variable holds, according to whether or not the first three arguments x, y, z satisfy x2 + y2=z2. Note that the fourth argument is referenced, and hence can be assigned to; the first three arguments cannot be changed inside the procedure.
> procedure CheckPythagoras(x, y, z, ~h)
>     if x^2+y^2 eq z^2 then
>         h := true;
>     else
>         h := false;
>     end if;
> end procedure;
We use this to find some Pythagorean triples (in a particularly inefficient way):
> for x, y, z in { 1..15 } do
>     CheckPythagoras(x, y, z, ~h);
>     if h then
>       "Yes, Pythagorean triple!", x, y, z;
>     end if;
> end for;
Yes, Pythagorean triple! 3 4 5
Yes, Pythagorean triple! 4 3 5
Yes, Pythagorean triple! 5 12 13
Yes, Pythagorean triple! 6 8 10
Yes, Pythagorean triple! 8 6 10
Yes, Pythagorean triple! 9 12 15
Yes, Pythagorean triple! 12 5 13
Yes, Pythagorean triple! 12 9 15

The forward Declaration

forward f;
The forward declaration of a function or procedure f; although the assignment of a value to f is deferred, f may be called from within another function or procedure already.

The forward statement must occur on the `main' level, that is, outside other functions or procedures. (See also Chapter MAGMA SEMANTICS.)

Example Func_forward (H2E6)

We give an example of mutual recursion using the forward declaration. In this example we define a primality testing function which uses the factorization of n - 1, where n is the number to be tested. To obtain the complete factorization we need to test whether or not factors found are prime. Thus the prime divisor function and the primality tester call each other.

First we define a simple function that proves primality of n by finding an integer of multiplicative order n - 1 modulo n.

> function strongTest(primdiv, n)
>     return exists{ x : x in [2..n-1] | \
>       Modexp(x, n-1, n) eq 1 and
>       forall{ p : p in primdiv | Modexp(x, (n-1) div p, n) ne 1 }
>     };
> end function;
Next we define a rather crude isPrime function: for odd n>3 it first checks for a few (3) random values of a that an - 1 ≡ 1 mod n, and if so, it applies the above primality prover. For that we need the not yet defined function for finding the prime divisors of an integer.
> forward primeDivisors;
> function isPrime(n)
>    if n in { 2, 3 } or
>       IsOdd(n) and
>       forall{ a : a in { Random(2, n-2): i in [1..3] } |
>          Modexp(a, n-1, n) eq 1 } and
>          strongTest( primeDivisors(n-1), n )
>    then
>       return true;
>    else
>       return false;
>    end if;
> end function;
Finally, we define a function that finds the prime divisors. Note that it calls the isPrime function. Note also that this function is recursive, and that it calls a function upon its definition, in the form func< ..> ( .. ).
> primeDivisors := function(n)
>    if isPrime(n) then
>       return { n };
>    else
>       return func< d | primeDivisors(d) join primeDivisors(n div d) >
>          ( rep{ d : d in [2..Isqrt(n)] | n mod d eq 0 });
>    end if;
> end function;
> isPrime(1087);
true;
V2.28, 13 July 2023