Operator parameter passing semantics
Operator parameters in SPL behave similarly to macro expansion, but the semantics are designed to prevent macro mistakes common in languages like C.
In particular, SPL defines a name resolution rule to prevent accidental name capture and a syntactic confinement rule to prevent unintended reassociation. This macro expansion approach for composite operator customization was chosen because it makes operators concise, provides flexible typing, and naturally generalizes to primitive operators. It also simplifies the implementation, since certain entities (operator, type, and function parameters) are fully resolved at compile time, and need not be treated as first-class values at run time.
The name resolution rule for names in actual parameters is that the SPL compiler resolves them in the context of the operator invocation, not in the context of the operator definition. The following example illustrates how SPL resolves names when there are multiple choices:
composite Comp1 {
type T = int32;
graph () as Op = Comp2() { param U : T; }
}
composite Comp2 {
param type $U;
type T = boolean;
graph stream<int32 x> A = Beacon() { }
stream<int32 x> B = Functor(A) {
logic state : $U v = 0;
}
}
The actual value for parameter U
in Line 3 is T
. The name is
resolved in the context of the operator invocation in Line 3, where T
is
int32
. If the name was resolved in the context of the operator definition,
then, since $U
is used in Line 10, T
would be Boolean. By
resolving T
to Comp1.T
, the language shields the author of
Comp1
from implementation details of Comp2
. The rule
"resolve names in the invocation, not the definition" supports encapsulation, because it hides
implementation details of the operator, rather than allowing them to leak out by capturing
names.
The syntactic confinement rule for SPL operator parameters is that argument
expressions are implicitly parenthesized. The implicit parentheses "confine" them from
associating in unexpected ways when they get used. A corollary of this rule is that you cannot
pass something that does not start out confined; for example, an incomplete expression like
x +
is not valid as an actual parameter to an operator. The following
example illustrates the syntactic confinement rule:
composite M(output Out; input In) {
param expression<int32> $q;
graph stream<float32 percent> Out = Functor(In) {
output Out: percent = (float32)(100 * $q);
}
}
composite Main {
graph stream<int32 x> A = Beacon() {}
stream<float32 percent> B = M(A) { param q: x - 1; }
}
The actual parameter x - 1
on Line 9 is substituted for the parameter $q
on Line 4. Because of the syntactic confinement rule, 100 * $q
associates like 100 * (x - 1)
, not like (100 * x) - 1
. As with the name resolution rule, the syntactic confinement rule leads to better encapsulation when an operator is invoked. You need not worry about precedence context in the operator definition. This logic prevents unintended results.