Function Declaration
Synopsis
Declare a function.
Syntax
Modifiers Type Name( Type₁ Var₁, ..., Typeₙ Varₙ ) Body
Modifiers Type Name( Type₁ Var₁, ..., Typeₙ Varₙ Type₀ Name₀... ) Body
Modifiers Type Name( Pattern₁, ..., Patternₙ) Body
Modifiers Type Name( Pattern₁, ..., Patternₙ, Type₀ Name₀...) Body
where Body
is one of:
{
Statements
}
throws Exception₁, ..., Exceptionₙ
= Expression;
and where Modifiers
may be:
public
private
default
java
test
public java
private java
public test
private test
public default
private default
Variant 1
A function declaration introduces a new function with name name, typed formal parameters Type₁ Var₁
, a return type Type
and a Statement that forms the function body.
The type of Statement should be equal to Type.
The formal parameters may be used in Statement and get their value when function Name is invoked.
Variant 2
A function may have a variable list of arguments, this has as syntax variant 2 given above.
The last parameter of a function may be followed by ...
and this has as effect that all remaining actual parameters
that occur in a call to this function are collected as list value of the last formal parameter.
Inside the function body, the type of this parameter will therefore be list[Type₀]
.
Variant 3 and 4
All formal parameter of a function can be Patterns. There are some restrictions however:
- A Pattern in formal parameter positions may not refer to variables in the scope.
- Patterns in formal parameter positions may not introduce fresh variables without an explicit type.
- The last parameter, if followed by
...
can only be a normal typed parameters, not just any pattern.
Body types
- Functions with list of statements as bodies must eventually use
return
orfail
on every control flow path. - The declarations to
throw
an exception are documentation only - Single expressions can be bodies of functions, the return value is the value of the expression.
Parameterized types in function declaration
The types that occur in function declarations may also contain Type Parameters. In this way functions can be defined for arbitrary types. The type variable is bound (statically) at by the types of the parameters given at location of the call. The result type must be used at least once in any of the parameters.
Overloading
Function definitions may be overloaded, i.e. a function with the same name may be defined twice and a function may redefine a constructor of an Algebraic Data Type or a Syntax Definition.
There are some restrictions however:
- Overloaded alternatives for the same function name but with different patterns must return the same type.
- Overloaded alternatives for the same function name must have mutually exclusive patterns, unless one alternative is labeled
default
and the other is not. The patterns of formal parameters are mutually exclusive if for at least one parameter position:- They range over incomparable types, as in
int f(int a)
andint f(real a)
, or - They range over different alternatives of an Algebraic Data Type, as in
int f(and(Bool a, Bool b))
andint f(or(Bool a, Bool b))
- They range over different alternatives of a Syntax Definition
- And note that deep matches using the
/
alternative are considered to be of typevalue
and therefore overlap with all other patterns.
- They range over incomparable types, as in
- Overlapping patterns are allowed if the one alternative has the
default
modified while the other does not. - If a function is fallible, it uses the
fail
statement to back-track to a different alternative, then there must be adefault
alternative defined which can handle the general case. An Algebraic Data Type or a Syntax Definition with the same name and return type counts as adefault
alternative. default
functions may not fail.
Modifiers
The Modifiers affect visibility and special behaviour of functions:
- Visibility:
private
declares that a function is only visible in the current module.public
declares that it is visible outside the module as well. When visibility is not specified,public
is assumed. - Special Behaviour:
java
declares that the body of the function is implemented in Java. The function should have ajavaClass
annotation that determines where the Java implementation can be found.test
declares that this is a test function. A test function is a boolean function (currently) without arguments. It can be called as any other function. However, it can also be called automatically by the unit test framework, by typing:test
at the command line, see Help.default
declares an alternative for an overloaded function that will only be tried after all non-default alternatives have been tried. Note that Algebraic Data Types and Syntax Definitions implicitly definedefault
functions that may be overloaded by normal Functions.
Examples
Declare a function
rascal>rel[int, int] invert(rel[int,int] R){
>>>>>>> return {<Y, X> | <int X, int Y> <- R };
>>>>>>>}
rel[int,int] (rel[int,int]): function(|prompt:///|(0,82,<1,0>,<3,1>))
Call it
rascal>invert({<1,10>, <2,20>});
rel[int,int]: {
<10,1>,
<20,2>
}
In the following example we illustrate the use of type variables in function declarations. Declare an inversion function that is applicable to any binary relation:
rascal>rel[&T2, &T1] invert2(rel[&T1,&T2] R){
>>>>>>> return {<Y, X> | <&T1 X, &T2 Y> <- R };
>>>>>>>}
set[tuple[&T2,&T1]] (set[tuple[&T1,&T2]]): function(|prompt:///|(0,85,<1,0>,<3,1>))
Now apply it to relations with different types:
rascal>invert2({<1,10>, <2,20>});
rel[int,int]: {
<10,1>,
<20,2>
}
rascal>invert2({<"mon", 1>, <"tue", 2>});
rel[int,str]: {
<1,"mon">,
<2,"tue">
}
As another example declare a function that can be used to swap the elements of pairs of arbitrary types (also see Subscription):
rascal>tuple[&T2, &T1] swap(tuple[&T1, &T2] TP) { return <TP[1], TP[0]>;}
tuple[&T2,&T1] (tuple[&T1,&T2]): function(|prompt:///|(0,66,<1,0>,<1,66>))
rascal>swap(<1, 2>);
tuple[int,int]: <2,1>
rascal>swap(<"wed", 3>);
tuple[int,str]: <3,"wed">
Here we use an overloaded definition with incomparable patterns:
rascal>int f(int i) = 1;
int (int): function(|prompt:///|(0,17,<1,0>,<1,17>))
rascal>int f(real r) = 2;
int (real): function(|prompt:///|(0,18,<1,0>,<1,18>))
rascal>f(0);
int: 1
rascal>f(0.0);
int: 2
And we may use default
, as in:
rascal>int f(0) = 1;
int (int): function(|prompt:///|(0,13,<1,0>,<1,13>))
rascal>default int f(int n) = n * f(n - 1);
int (int): function(|prompt:///|(0,36,<1,0>,<1,36>))
rascal>f(0);
int: 1
rascal>f(2);
int: 2
In combination with an Algebraic Data Type, which defines default
functions implicitly for every alternative,
we can define canonicalization functions. The same holds for Syntax Definitions, see Actions.
This definition implies a default function for t(), f() and neg(B):
rascal>data B = t() | f() | neg(B);
ok
The following definition will remove any nested neg before it is even constructed:
rascal>B neg(neg(B b)) = b;
B (B): function(|prompt:///|(0,20,<1,0>,<1,20>))
rascal>neg(t());
B: neg(t())
rascal>neg(neg(f()));
B: f()
Benefits
- Overloaded functions can be extended by other modules that extend the current module, and even be "fused" by modules that extend different modules that define the same overloaded signature. In this way you can write language processors modularly. The open extensibility of overloaded functions in Rascal is a way to circumvent the classes "Expression Problem".
- Overloaded functions including complex pattern matching at the parameter positions are as powerful as term rewriting systems; they are a kind of term rewriting system. Together with the Syntax Definition and Algebraic Data Type features (serving as many-sorted algebraic signatures), the overloaded functions allow for "algebraic specification" as a style of programming in Rascal.
Pitfalls
- In case of overlapping function definitions, the order in which the functions are tried is left undefined. The only exceptions are functions marked
default
, those will be tried after non-default
functions. - Neither the Rascal compiler not its static checker, or the interpreter, check for the completeness of overloaded function definitions. The question: is do they handle all the cases that the grammar of the input data-types provide for? And if so, is every choice of prioritizing the different alternatives leading to the same answer? This problem is akin to the properties of termination, unique normal forms and confluence of term rewriting systems. A set of overloaded alternative functions, including a data constructor with the same name, are comparable to a term rewriting system for a many-sorted algebraic signature.