Certainly, and moreover you can have full pattern matching as parameters and you don't necessarily have to mention the full path:
void foo(class(str a, str b, str c, list[Decl] d)) { ... }
or to also bind e
to the full class declaration:
void foo(Decl e:class(str a, str b, str c, list[Decl] d)) { ... }
Note that the open variables in a pattern need to be typed (unlike when the pattern is nested inside the body of the function), this is because we do not wish to propagate type errors beyond function boundaries.
Or, you might want to match more deeply and select only certain declarations:
void foo(class("MyClass", str _, str _, list[Decl] _)) { ... }
Or, a nested deep match matching all classes which have an equals method:
void foo(class(str name, str _, str _, /method("equals",_,_)) { ... }
The essence of Rascal function definitions is that they may be overloaded and that dispatch is done dynamically based on pattern matching. You are required as the programmer to make the patterns mutually exclusive, or if that is impossible you can use a default
modifier to force a partial ordering.
For example, this is illegal because the pattern 0
overlaps with int n
:
int f(0) = 1;
int f(int n) = f(n - 1) * n;
You can write this however:
int f(0) = 1;
default f(int n) = f(n - 1) * n;
Next to mutually exclusive, overloaded functions also need to be complete.
This means for your class case that you will need a default definition for the other cases you don't match:
default void foo(Decl d) {
throw "did not implement foo for <d>";
}
More information can be found here: