A function is generic if any of the following conditions hold:
Some formal argument is specified with an intent of type or param.
Some formal argument has no specified type and no default value.
Some formal argument is specified with a queried type.
The type of some formal argument is a generic type, e.g., List. Queries may be inlined in generic types, e.g., List(?eltType).
The type of some formal argument is an array type where either the element type is queried or omitted or the domain is queried or omitted.
In this issue I want to talk about this part specifically:
The type of some formal argument is a generic type.
and I am asking, is that clear enough?
Here is a simple example:
proc h(arg: numeric) { }
Besides being important for the compiler, whether a function is generic or not forms part of the API for that function. Additionally generic functions can resolves calls in their body using point-of-instantiation.
Now, you might think, "When programming, we know which types are generic". But, do you?
Historically, type functions could not return generic types. But, today, they can. So, we can have the situation that a declaration uses a type from a function call (and the function might be defined in another module etc):
proc getArgType() type {
return numeric;
}
proc g(arg:getArgType()) { ... } // this works today to declare a generic function
Additionally, it is not necessarily clear from the use of a user-defined type, like R in proc h(arg: R), whether or not R is generic. (Issue #19120 discusses how to make it clearer, when looking at the definition of R, whether R is generic or not).
In the details, I have an example that explores whether or not f is generic based on its argument types. The main conclusion from the example is that in something like proc g(arg:getArgType()), today getArgType cannot come from the point-of-instantiation.
module Lib {
proc getFirstArgType() type {
return numeric;
}
proc f(firstArg, arg) {
assert(firstArg.type <= getFirstArgType()); // checks for subtype
assert(arg.type <= computeArgType()); // checks for subtype
writeln("In Lib.f, arg: ", arg.type:string, " = ", arg);
}
}
module A {
use Lib;
proc computeArgType() type {
return int;
}
proc runA() {
f(firstArg=0, arg=1);
}
}
module B {
use Lib;
proc computeArgType() type {
return real;
}
proc runB() {
f(firstArg=0.0, arg=2.0);
}
}
module Program {
use A, B;
proc main() {
runA();
runB();
}
}
Now, what if we modify the declaration of f?
proc f(firstArg: numeric, arg) // works the same as above
proc f(firstArg:getFirstArgType(), arg) // works the same as above
proc f(firstArg:getFirstArgType(), arg:getFirstArgType()) // works the same as above
proc f(firstArg, arg: computeArgType()) // does not find computeArgType() from POI. Should it?
proc f(firstArg, arg: doComputeArgType()) // same problem as above. doComputeArgType is shown below
proc doComputeArgType() type { return computeArgType(); }
What could we do about this?
Approach 0
Don't change how one knows if a function is generic or not.
Approach 1
Building on the proposal in #18665, we could require that generic types be decorated with `(?). As in:
For a class type C, that has generic management by default. So, arguably, this approach would require us to write:
proc foo(arg: C(?))
even if C is concrete (because it has generic management). If C is generic-with-defaults, that would change the meaning of foo (because now it can accept non-default instantiations of C as well as any management).
Approach 2
Require one to use syntax like proc f(arg:?t) where t:numeric instead of proc f(arg:numeric). Historically where clauses have significantly increased compilation time. But, we could certainly adjust the compiler to handle such simple where clauses in an accelerated manner.