Navigation
Synopsis Declare a function.
Syntax
  1. Modifiers Type Name( Type1 Var1, ..., Typen Varn ) Body
  2. Modifiers Type Name( Type1 Var1, ..., Typen Varn Type0 Name0... ) Body
  3. Modifiers Type Name( Pattern1, ..., Patternn) Body
  4. Modifiers Type Name( Pattern1, ..., Patternn, Type0 Name0...) Body
where Body is one of:
  • { Statements }
  • throws Exception1, Exception2, ... { Statements }
  • = Expression;
and where Modifiers may be:
  • ("public" | "private")? ("java" | "test" | "default")?
Description

Variant 1

A function declaration introduces a new function with name Name, typed formal parameters Type1 Var1, 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[Type0].

Variant 3 and 4

All formal parameter of a function can be Patterns. There are some restrictions however:
  • Patterns 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 or fail 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 TypeParameters. 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 AlgebraicDataType or a SyntaxDefinition.

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) and int f(real a), or
    • They range over different alternatives of an AlgebraicDataType, as in int f(and(Bool a, Bool b)) and int f(or(Bool a, Bool b))
    • They range over different alternatives of a SyntaxDefinition
    • And note that deep matches using the / alternative are considered to be of type value and therefore overlap with all other patterns.
  • 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 a default alternative defined which can handle the general case. An AlgebraicDataType or a SyntaxDefinition with the same name and return type counts as a default 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, private is assumed.
  • Special Behaviour:
  • java declares that the body of the function is implemented in Java. The function should have a javaClass 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 AlgebraicDataTypes and SyntaxDefinitions implicitly define default 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]): rel[int,int] invert(rel[int,int]);
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 };
>>>>>>>}
rel[&T2,&T1] (rel[&T1,&T2]): rel[&T2,&T1] invert2(rel[&T1,&T2]);
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 Tuple/Subscription):
rascal>tuple[&T2, &T1] swap(tuple[&T1, &T2] TP) { return <TP[1], TP[0]>;}
tuple[&T2,&T1] (tuple[&T1,&T2]): tuple[&T2,&T1] swap(tuple[&T1,&T2]);
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): int f(int);
rascal>int f(real r) = 2;
int (real): int f(real);
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): int f(int);
rascal>default int f(int n) = n * f(n - 1);
int (int): int f(int);
rascal>f(0);
int: 1
rascal>f(2);
int: 2
In combination with an AlgebraicDataType, which defines default functions implicitly for every alternative, we can define canonicalization functions. The same holds for SyntaxDefinitions, 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): B neg(B);
rascal>neg(t());
B: neg(t())
rascal>neg(neg(f()));
B: f()
Is this page unclear, or have you spotted an error? Please add a comment below and help us to improve it. For all other questions and remarks, visit ask.rascal-mpl.org.