Procedures¶
A function is a code abstraction that can be invoked by a call expression. Throughout this specification the term “function” is used in this programming-languages sense, rather than in the mathematical sense. A function has zero or more formal arguments, or simply formals. Upon a function call each formal is associated with the corresponding actual argument, or simply actual. Actual arguments are provided as part of the call expression, or at the call site. Direct and indirect recursion is supported.
A function can be a procedure, which completes and returns to the call site exactly once, returning no result, a single result, or multiple results aggregated in a tuple. A function can also be an iterator, which can generate, or yield, multiple results (in sequence and/or in parallel). A function (either a procedure or an iterator) can be a method if it is bound to a type (often a class). An operator in this chapter is a procedure with a special name, which can be invoked using infix notation, i.e., via a unary or binary expression. This chapter defines procedures, but most of its contents apply to iterators and methods as well.
Functions are presented as follows:
procedures (this chapter)
operators Procedure Definitions, Operator Expressions
iterators Iterators
methods (when bound to a class) Class Methods
function calls Function Calls
various aspects of defining a procedure Procedure Definitions–Nested Functions
calling external functions from Chapel Calling External Functions
calling Chapel functions from external functions Calling Chapel Functions
determining the function to invoke for a given call site: function and operator overloading Function and Operator Overloading, function resolution Function Resolution
Function Calls¶
The syntax to call a non-method function is given by:
call-expression:
lvalue-expression ( named-expression-list )
lvalue-expression [ named-expression-list ]
parenthesesless-function-identifier
named-expression-list:
named-expression
named-expression , named-expression-list
named-expression:
expression
identifier = expression
parenthesesless-function-identifier:
identifier
A call-expression
is resolved to a particular function according to
the algorithm for function resolution described
in Function Resolution.
Functions can be called using either parentheses or brackets.
Rationale.
This provides an opportunity to blur the distinction between an array access and a function call and thereby exploit a possible space/time tradeoff.
Functions that are defined without parentheses must be called without parentheses. Functions without parentheses are discussed in Functions without Parentheses.
A named-expression
is an expression that may be optionally named. It
provides an actual argument to the function being called. The optional
identifier
refers to a named formal argument described
in Named Arguments.
Calls to methods are defined in Section Class Method Calls.
Procedure Definitions¶
Procedures are defined with the following syntax:
procedure-declaration-statement:
privacy-specifier[OPT] procedure-kind[OPT] 'proc' identifier argument-list[OPT] return-intent[OPT] return-type[OPT] where-clause[OPT] function-body
privacy-specifier[OPT] procedure-kind[OPT] 'operator' operator-name argument-list return-intent[OPT] return-type[OPT] where-clause[OPT] function-body
procedure-kind:
'inline'
'export'
'extern'
'override'
operator-name: one of
'align' 'by'
+ - * / % ** : ! == != <= >= < > << >> & | ^ ~
= += -= *= /= %= **= &= |= ^= <<= >>= <=> #
argument-list:
( formals[OPT] )
formals:
formal
formal , formals
formal:
formal-intent[OPT] identifier formal-type[OPT] default-expression[OPT]
formal-intent[OPT] identifier formal-type[OPT] variable-argument-expression
formal-intent[OPT] tuple-grouped-identifier-list formal-type[OPT] default-expression[OPT]
formal-intent[OPT] tuple-grouped-identifier-list formal-type[OPT] variable-argument-expression
formal-type:
: type-expression
default-expression:
= expression
variable-argument-expression:
... expression
...
formal-intent:
'const'
'const in'
'const ref'
'in'
'out'
'inout'
'ref'
'param'
'type'
return-intent:
'const'
'const ref'
'out'
'ref'
'param'
'type'
return-type:
: type-expression
where-clause:
'where' expression
function-body:
'do' statement
block-statement
Functions do not require parentheses if they have no arguments. Such functions are described in Functions without Parentheses.
Formal arguments can be grouped together using a tuple notation as described in Splitting a Tuple into Multiple Formal Arguments in a Function Call.
Default expressions allow for the omission of actual arguments at the call site, resulting in the implicit passing of a default value. Default values are discussed in Default Values.
The intents const
, const in
, const ref
, in
, out
,
inout
and ref
are discussed in Argument Intents.
The intents param
and type
make a function generic and are
discussed in Generic Functions. If the formal argument’s
type is omitted, generic, or prefixed with a question mark, the function
is also generic and is discussed in Generic Functions.
Functions can take a variable number of arguments. Such functions are discussed in Variable Number of Arguments.
The return-intent
can be used to indicate how the value is returned
from a function. return-intent
is described further in
Return Intents.
Open issue.
Parameter and type procedures are supported. Parameter and type iterators are currently not supported.
The return-type
is optional and is discussed
in Return Types. A type function may not specify a return
type.
The where-clause
is optional and is discussed
in Where Clauses.
The function-body
defines the function’s behavior and is defined
in Function Bodies. Function bodies may contain return
statements (see The Return Statement).
Function and operator overloading is supported in Chapel and is
discussed in Function and Operator Overloading. Operator overloading
is supported on the operators listed above (see operator-name
).
The optional privacy-specifier
keywords indicate the visibility of
module level procedures to outside modules. By default, procedures are
publicly visible. More details on visibility can be found in
Visibility Of A Module’s Symbols.
The linkage specifier inline
indicates that the function body must
be inlined at every call site.
Rationale.
A Chapel compiler is permitted to inline any function if it determines there is likely to be a performance benefit to do so. Hence an error must be reported if the compiler is unable to inline a procedure with this specifier. One example of a preventable inlining error is to define a sequence of inlined calls that includes a cycle back to an inlined procedure.
See the chapter on interoperability (Interoperability) for details on exported and imported functions.
Functions without Parentheses¶
Functions do not require parentheses if they have empty argument lists. Functions declared without parentheses around empty argument lists must be called without parentheses.
Example (function-no-parens.chpl).
Given the definitions
proc foo { writeln("In foo"); } proc bar() { writeln("In bar"); }the procedure
foo
can be called by writingfoo
and the procedurebar
can be called by writingbar()
. It is an error to use parentheses when callingfoo
or omit them when callingbar
.
Note that non-method functions called without parentheses cannot resolve to a function defined in a generic function’s point of instantiation (see Function Visibility in Generic Functions).
Formal Arguments¶
A formal argument’s intent (Argument Intents) specifies how the actual argument is passed to the function. If no intent is specified, the default intent (The Default Intent) is applied, resulting in type-dependent behavior.
A formal argument can include a type (e.g. arg: int
) and possibly a
default value (e.g. arg = ""
).
Named Arguments¶
A formal argument can be named at the call site to explicitly map an actual argument to a formal argument.
Example (named-args.chpl).
Running the code
proc foo(x: int, y: int) { writeln(x); writeln(y); } foo(x=2, y=3); foo(y=3, x=2);will produce the output
2 3 2 3named argument passing is used to map the actual arguments to the formal arguments. The two function calls are equivalent.
Named arguments are sometimes necessary to disambiguate calls or ignore arguments with default values. For a function that has many arguments, it is sometimes good practice to name the arguments at the call site for compiler-checked documentation.
Default Values¶
Default values can be specified for a formal argument by appending the assignment operator and a default expression to the declaration of the formal argument. If the actual argument is omitted from the function call, the default expression is evaluated when the function call is made and the evaluated result is passed to the formal argument as if it were passed from the call site. While the default expression is evaluated at the time of the function call, it is resolved in the scope of the definition of the called function, immediately before the called function is resolved. As a result, a default value expression can refer to previous formal arguments.
When a default value is provided for a formal argument without a type, the argument type will be inferred to match the type of the default value. This inference is similar to the type inference for variable declarations (see Local Type Inference). However, there is one difference: when the call provides a corresponding actual argument, and the actual argument is of a type that includes a runtime component (see Types with Runtime Components), the runtime component of the formal argument’s type will come from the actual argument, rather than from the default value expression.
Example (default-values.chpl).
The code
proc foo(x: int = 5, y: int = 7) { writeln(x); writeln(y); } foo(); foo(7); foo(y=5);writes out
5 7 7 7 5 5Default values are specified for the formal arguments
x
andy
. The three calls tofoo
are equivalent to the following three calls where the actual arguments are explicit:foo(5, 7)
,foo(7, 7)
, andfoo(5, 5)
. The examplefoo(y=5)
shows how to use a named argument fory
in order to use the default value forx
in the case whenx
appears earlier thany
in the formal argument list.Example (default-array-runtime-type.chpl).
This example shows that the runtime type of the default expression does not impact the runtime type of the formal argument in the case that an actual argument was provided.
var D = {1..4}; proc createArrayOverD() { var A:[D] int; return A; } proc bar(arg = createArrayOverD()) { writeln(arg.domain); } bar(); // arg uses the default, so outputs {1..4} var B:[0..2] int; bar(B); // arg refers to B and so has the runtime type from B // so outputs {0..2}
Argument Intents¶
Argument intents specify how an actual argument is passed to a function where it is represented by the corresponding formal argument.
- The argument intents are:
The Out Intent
out
The Inout Intent
inout
The Ref Intent
ref
The Const In Intent
const in
The Const Ref Intent
const ref
The Const Intent
const
The Default Intent (when no intent is given explicitly)
How should programmers choose among these intents?
The default intent and the
const
intent are designed to take the most natural/least surprising action. The two are the same, except for with synchronization types and tuples.The
in
andconst in
intents pass by value and are important for transferring a value in to a function, for example, when storing a value into a data structure. Using these intents for that use case can help to avoid unnecessary copies.The
ref
andconst ref
intents pass by reference. Theref
intent allows modifications of the referred-to value within the function and can be important when the function is intended to mutate its actual argument. Whileconst ref
does not allow the function itself to modify the referred-to value, the formal can be relied upon to track updates to the referred-to value through other variables or references, unlike theconst
or default intent.The
out
transfers a value out of the function. Theinout
intent is a combination ofin
andout
.
Summary of Intents¶
The following table summarizes the differences between the intents:
|
|
|
|
|
|
|
default |
|
---|---|---|---|---|---|---|---|---|
initializes formal from actual? |
yes |
no |
yes |
no |
yes |
no |
maybe |
maybe |
sets actual from formal? |
no |
yes |
yes |
no |
no |
no |
no |
no |
refers to actual argument? |
no |
no |
no |
yes |
no |
yes |
maybe |
maybe |
formal can be read? |
yes |
yes |
yes |
yes |
yes |
yes |
yes |
yes |
formal can be modified? |
yes |
yes |
yes |
yes |
no |
no |
no |
no or yes |
local changes affect the actual? |
no |
on return |
on return |
at once |
N/A |
N/A |
N/A |
N/A or yes |
See the sections on each intent for further details.
The In Intent¶
When in
is specified as the intent, the formal argument represents a
variable that is initialized from the value of the actual argument (see
Copy and Move Initialization). Note that a copy is not necessarily
created for an in
intent (see Copy Elision for details).
For example, for integer arguments, the formal argument will store a copy of the actual argument.
An implicit conversion for a function call occurs from the actual argument to the type of the formal.
The formal can be modified within the function, but such changes are local to the function and not reflected back to the call site.
This example shows a case where the in
intent accepts a record
but the code does not copy initialize:
Example (copy-elision-in.chpl)
proc makeRecord() { return new R(); // creates a new R record } proc acceptWithIn(in arg) { } proc elideCopy() { var x = makeRecord(); acceptWithIn(x); // copy to 'in arg' is elided // because 'x' is not used again writeln("block ending"); } elideCopy(); proc noElideCopy() { var x = makeRecord(); acceptWithIn(x); // copy to 'in arg' remains because 'x' is used again writeln(x); // 'x' used here writeln("block ending"); } noElideCopy();
The Out Intent¶
The out
intent on a formal argument supports return-like behavior.
As such, the type of an out
formal is not considered when determining
candidate functions or choosing the best candidate (see
Function Resolution).
When a function with the out
intent returns, the actual argument is
set to the formal argument using assignment or possibly initialized
from the formal argument according to Split Initialization.
Within the function body, an out
formal argument is initialized
according Split Initialization. It will start with its default
value if one is supplied and can use the default value for the declared
type if no initialization point is found. The formal argument can be
modified within the function.
Note that the way that type inference works with generic out
formal
arguments is very different from other formal arguments. In particular,
the type of a generic out
formal argument is inferred from the
function body rather than from the call site.
Note
If the type of an out
argument needs to be inferred based upon the
call site, there are currently two approaches available:
use a separate
type
argument to pass the typeuse the
ref
intent instead of theout
intent
There is proposal that including a type query (e.g. ?t
in an
out
argument will cause the type to be inferred based upon the
call site. However this is not yet implemented, at the time of this
writing.
The Inout Intent¶
When inout
is specified as the intent, the actual argument is
copy-initialized into the formal argument, the called function body is
run, and then the actual argument is set to the formal argument with
assignment. As a result the behavior of the inout
intent is a
combination of the in
and out
intents.
inout
intent formals behave the same as in
formals for the
purposes of determining candidate functions and choosing the best
candidate (see Function Resolution).
The actual argument must be a valid lvalue. The formal argument can be modified within the function.
The Ref Intent¶
When ref
is specified as the intent, the actual argument is passed
by reference. Any reads of, or modifications to, the formal argument are
performed directly on the corresponding actual argument at the call
site. The actual argument must be a valid lvalue. The type of the actual
argument must be the same as the type of the formal.
The ref
intent differs from the inout
intent in that the
inout
intent requires copying from/to the actual argument on the way
in/out of the function, while ref
allows direct access to the actual
argument through the formal argument without copies. Note that
concurrent modifications to the ref
actual argument by other tasks
may be visible within the function, subject to the memory consistency
model.
The Const In Intent¶
The const in
intent is identical to the in
intent, except that
modifications to the formal argument are prohibited within the function.
The Const Ref Intent¶
The const ref
intent is identical to the ref
intent, except that
modifications to the formal argument are prohibited within the function.
The referred-to value can still be modified by other means during the
execution of the routine. Such modifications are observable
by the formal argument, subject to the memory consistency model.
The following example shows such modification by means other than the
const ref
formal. This program would be invalid if c
used
const
intent instead of const ref
(see The Const Intent).
Example (const-ref-aliasing.chpl).
var a: int = 0; proc f(ref b, const ref c) { writeln(c); // outputs 0 a = 1; writeln(c); // outputs 1, because 'c' refers to 'a' b = 2; // modifies 'a' because 'b' refers to 'a' writeln(c); // outputs 2, because 'c' refers to 'a' } f(a, a);
The Const Intent¶
The const
intent is broadly similar to the const ref
intent;
however it allows the implementation more freedom to optimize. Generally
speaking, the compiler will implement a const
intent formal by
choosing between const in
and const ref
in an
implementation-defined manner. As such, it is appropriate when the actual
value will not be modified anywhere within the function and additionally
the actual value will not be modified by other means during the call.
See the example in The Const Ref Intent for an example of
modification through other means.
The const
intent indicates that the function will not and cannot
modify the formal argument within its dynamic scope.
Unlike const in
/ in
, the const
intent cannot be relied upon
to transfer the ownership of a value. For record types, passing to a
const
intent will not invoke a user-defined copy initializer.
For synchronization types sync
and atomic
, the const
intent is const ref
and the remainder of this section does not apply.
Otherwise, the const
intent communicates to the compiler that it
can make several assumptions to aid optimization within the function using
a const
formal:
the value referred to by such a formal argument will not be modified while the function is running
if the formal is an array, the domain that the array is declared over will not be modified while the function is running
if the formal is an
owned
orshared
, the actual will not be modified to point to a different object ornil
while the function is running.
In the cases where these assumptions are not appropriate, code should use
const ref
or const in
as appropriate.
Implementation Notes.
In the current compiler implementation,
const
meansconst in
for the following types:
for
bool
,int
,uint
,real
, andimag
with widths less than or equal to 64 bitsfor
complex
with widths less than or equal to 128 bitsfor
borrowed
andunmanaged
class typesfor values of enumerated type
The Default Intent¶
When no intent is specified for a formal argument, the default intent is applied. The default intent is designed to take the most natural/least surprising action for the argument.
For most types, the default intent is the same as the const
intent.
See the description of the const
intent in The Const Intent
for details including assumptions that the compiler can make about
default intent and const
formals when optimizing.
However, the default intent for atomic
and sync
is ref
because
modification is considered part of their nature.
Additionally, tuples have a special default argument intent. See Tuple Argument Intents for details.
Variable Number of Arguments¶
Functions can be defined to take a variable number of arguments where those arguments can have any intent or can be types. A variable number of parameters is not supported. This allows the call site to pass a different number of actual arguments. There must be at least one actual argument.
If the variable argument expression contains an identifier prepended by a question mark, the number of actual arguments can vary, and the identifier will be bound to an integer parameter value indicating the number of arguments at a given call site. If the variable argument expression contains an expression without a question mark, that expression must evaluate to an integer parameter value requiring the call site to pass that number of arguments to the function.
Within the function, the formal argument that is marked with a variable argument expression is a tuple of the actual arguments. If the actual arguments all have the same type, the formal will be a homogeneous tuple, otherwise it will be a heterogeneous tuple.
Example (varargs.chpl).
The code
proc mywriteln(xs ...?k) { for x in xs do writeln(x); }defines a generic procedure called
mywriteln
that takes a variable number of arguments of any type and then writes them out on separate lines. The type ofxs
can also be constrained in the formal argument list to require that the actuals all have the same type. For examplexs: string...?k
would accept a variable number of string arguments.
Example (varargs-with-type.chpl).
Either or both the number of variable arguments and their types can be specified. For example, a basic procedure to sum the values of three integers can be written as
proc sum(x: int...3) do return x(0) + x(1) + x(2);Specifying the type is useful if it is important that each argument have the same type. Specifying the number is useful in, for example, defining a method on a class that is instantiated over a rank parameter.
Example (varargs-returns-tuples.chpl).
The code
proc tuple(x ...) do return x;defines a generic procedure that is equivalent to building a tuple. Therefore the expressions
tuple(1, 2)
and(1,2)
are equivalent, as are the expressionstuple(1)
and(1,)
.
Return Intents¶
The return-intent
determines how the value is returned from a
function and in what contexts that function is allowed to be used.
The rules for returning tuples are specified in Tuple Return Behavior.
The Default Return Intent¶
When no return-intent
is specified explicitly, the function returns
a value in the same way as the out
return intent,
see The Out Return Intent.
The Out Return Intent¶
A function with the out
return intent returns
a value that cannot be used as an lvalue. This value is obtained
by copy-initialization from the returned expression,
see Copy and Move Initialization.
It is an error to return a sync
or atomic
by out
intent.
The Ref Return Intent¶
When using a ref
return intent, the function call is an lvalue
(specifically, a call expression for a procedure and an iterator
variable for an iterator).
The ref
return intent is specified by following the argument list
with the ref
keyword. The function must return an lvalue that
exists outside of the function’s scope.
Example (ref-return-intent.chpl).
The following code defines a procedure that can be interpreted as a simple two-element array where the elements are actually module level variables:
var x, y = 0; proc A(i: int) ref { if i < 0 || i > 1 then halt("array access out of bounds"); if i == 0 then return x; else return y; }Calls to this procedure can be assigned to in order to write to the “elements” of the array as in
A(0) = 1; A(1) = 2;It can be called as an expression to access the “elements” as in
writeln(A(0) + A(1));This code outputs the number
3
.
The Const Ref Return Intent¶
The const ref
return intent is also available. It is a restricted
form of the ref
return intent. Calls to functions marked with the
const ref
return intent are not lvalue expressions.
The Const Return Intent¶
The const
return intent makes it up to the implementation to return a
const
reference or to return a value. Calls to functions marked with
the const
return intent are not lvalue expressions.
Return Intent Overloads¶
In some situations, it is useful to choose the function called based upon how the returned value is used. In particular, suppose that there are two functions that have the same formal arguments and differ only in their return intent. One might expect such a situation to result in an error indicating that it is ambiguous which function is called. However, the Chapel language includes a special rule for determining which function to call when the candidate functions are otherwise ambiguous except for their return intent. This rule enables data structures such as sparse arrays.
See Choosing Return Intent Overloads Based on Calling Context for a detailed description of how return intent overloads are chosen based upon calling context.
Example (ref-return-intent-pair.chpl).
Return intent overload can be used to ensure, for example, that the second element in the pseudo-array is only assigned a value if the first argument is positive. The following is an example:
var x, y = 0; proc doA(param setter, i: int) ref { if i < 0 || i > 1 then halt("array access out of bounds"); if setter && i == 1 && x <= 0 then halt("cannot assign value to A(1) if A(0) <= 0"); if i == 0 then return x; else return y; } proc A(i: int) ref { return doA(true, i); } proc A(i: int) { return doA(false, i); } A(0) = 0; A(1) = 1;
The Param Return Intent¶
A parameter function, or a param function, is a function that
returns a parameter expression. It is specified by following the
function’s argument list by the keyword param
. It is often, but not
necessarily, generic.
It is a compile-time error if a parameter function does not return a parameter expression. The result of a parameter function is computed during compilation and substituted for the call expression.
Example (param-functions.chpl).
In the code
proc sumOfSquares(param a: int, param b: int) param do return a**2 + b**2; var x: sumOfSquares(2, 3)*int;
sumOfSquares
is a parameter procedure that takes two parameters as arguments. Calls to this procedure can be used in places where a parameter expression is required. In this example, the call is used in the declaration of a homogeneous tuple and so is required to be a parameter.
Parameter functions may not contain control flow that is not resolved at compile-time. This includes loops other than the parameter for loop Parameter For Loops and conditionals with a conditional expressions that is not a parameter.
The Type Return Intent¶
A type function is a function that returns a type, not a value. It is
specified by following the function’s argument list by the keyword
type
, without the subsequent return type. It is often, but not
necessarily, generic.
It is a compile-time error if a type function does not return a type. The result of a type function is computed during compilation.
As with parameter functions, type functions may not contain control flow that is not resolved at compile-time. This includes loops other than the parameter for loop Parameter For Loops and conditionals with a conditional expression that is not a parameter.
Example (type-functions.chpl).
In the code
proc myType(x) type { if numBits(x.type) <= 32 then return int(32); else return int(64); }
myType
is a type procedure that takes a single argumentx
and returnsint(32)
if the number of bits used to representx
is less than or equal to 32, otherwise it returnsint(64)
.numBits
is a param procedure defined in the standard Types module.
Function Bodies¶
The body of a procedure or iterator is made up of one or more statements that are executed when a call to the function is made. Function bodies can always be specified using a compound or _block_ statement (Blocks), set off by curly brackets. When a function’s body is just a single statement, the do keyword can be used as a shorthand for defining the body instead, similar to other forms of control flow.
The Return Statement¶
The return statement can only appear in a function body. It causes control to exit that function, returning it to the point at which that function was called.
A procedure can return a value by executing a return statement that includes an expression. If it does, that expression’s value becomes the value of the invoking call expression.
A return statement in a procedure of a void
return type
(Return Types) or in an iterator must not include an
expression. A return statement in a procedure of a non-void
return type must include an expression. For procedures with ref
or const ref
return intent, the expression must have storage
associated with it that will outlive the procedure itself.
The statements following a return statement in the same block are ignored by the compiler because they cannot be executed.
The syntax of the return statement is given by
return-statement:
'return' expression[OPT] ;
Example (return.chpl).
The following code defines a procedure that returns the sum of three integers:
proc sum(i1: int, i2: int, i3: int) do return i1 + i2 + i3;
Return Types¶
Every procedure has a return type. The return type is either specified
explicitly via return-type
in the procedure declaration, or is
inferred implicitly.
Explicit Return Types¶
If a return type is specified and is not void
, each return statement
of the procedure must include an expression. For a non-ref
return
intent, an implicit conversion occurs from each return expression to the
specified return type. For a ref
return
intent (The Ref Return Intent), the return type must match
the type returned in all of the return statements exactly, when checked
after generic instantiation and parameter folding (if applicable).
Implicit Return Types¶
If a return type is not specified, it is inferred from the return
statements. It is illegal for a procedure to have a return statement
with an expression and a return statement without an expression. For
procedures without any return statements, or when none of the return
statements include an expression, the return type is void
.
Otherwise, the types of the expressions in all of the procedure’s return
statements are considered. If a function has a ref
return intent
(The Ref Return Intent), they all must be the same exact
type, which becomes the inferred return type. Otherwise, there must
exist exactly one type such that an implicit conversion is allowed
between every other type and that type, and that type becomes the
inferred return type. If the above requirements are not satisfied, it is
an error.
Where Clauses¶
The list of function candidates can be constrained by where clauses. A
where clause is specified in the definition of a
function (Procedure Definitions). The expression in the
where clause must be a boolean parameter expression that evaluates to
either true
or false
. If it evaluates to false
, the function
is rejected and thus is not a possible candidate for function
resolution.
Example (whereClause.chpl).
Given two overloaded function definitions
proc foo(x) where x.type == int { writeln("int"); } proc foo(x) where x.type == real { writeln("real"); }the call foo(3) resolves to the first definition because the where clause on the second function evaluates to false.
Nested Functions¶
A function defined in another function is called a nested function. Nesting of functions may be done to arbitrary degrees, i.e., a function can be nested in a nested function.
Nested functions are only visible to function calls within the lexical scope in which they are defined.
Nested functions may refer to variables defined in the function(s) in which they are nested.
Function and Operator Overloading¶
Functions that have the same name but different argument lists are called overloaded functions. Function calls to overloaded functions are resolved according to the function resolution algorithm in Function Resolution.
To define an overloaded operator, use the operator
keyword to define a
function with the same name as the operator. The operators that may be
overloaded are listed in the following table:
arity |
operators |
---|---|
unary |
|
binary |
|
binary |
|
binary |
|
binary |
|
binary |
|
The arity and precedence of the operator must be maintained when it is overloaded. Operator resolution follows the same algorithm as function resolution.
Assignment overloads are not supported for class types.
Function Resolution¶
Function resolution is the algorithm that determines which target function to invoke for a given call expression. Function resolution is defined as follows.
Identify the set of visible functions for the function call. A visible function is any function that satisfies the criteria in Determining Visible Functions. If no visible function can be found, the compiler will issue an error stating that the call cannot be resolved.
From the set of visible functions for the function call, determine the set of candidate functions for the function call. A candidate function is any function that satisfies the criteria in Determining Candidate Functions. If no candidate function can be found and the call is within a generic function, its point of instantiation(s) are visited searching for candidates as defined in Function Visibility in Generic Functions. If still no candidate functions are found, the compiler will issue an error stating that the call cannot be resolved. If exactly one candidate function is found, this is determined to be the target function.
From the set of candidate functions, determine the set of most specific functions as described in Determining Most Specific Functions. In most cases, if the set of most specific functions contains more than one element, it will result in an ambiguity error. However, there can be several if they differ only in return intent.
From the set of most specific functions, the compiler determines a best function for each return intent as described in Determining Best Functions. If there is more than one best function for a given return intent, the compiler will issue an error stating that the call is ambiguous. Otherwise, it will choose the target function from these best functions based on the calling context as described in Choosing Return Intent Overloads Based on Calling Context.
Notation¶
This section uses the following notation:
\(X\) is a function under consideration
An actual argument under consideration is \(A_i\) of type \(T(A_i)\)
The formal argument in function \(X\) that will receive \(A_i\) is \(X_i\) and it has type \(T(X_i)\). When \(X\) is a generic function, \(X_i\) refers to the possibly generic argument and \(T(X_i)\) refers to the instantiated type.
When needed in the exposition, \(Y\) is another function under consideration, where \(A_i\) is passed to the formal \(Y_i\) of type \(T(Y_i)\).
Determining Visible Functions¶
Given a function call, a function \(X\) is determined to be a visible function if its name is the same as the name of the function call and one of the following conditions is met:
\(X\) is defined in the same scope as the function call or in a lexical outer scope of the function call, or
\(X\) is
public
and is declared in a module that is used from the same scope as the function call or from its lexical outer scope, see also Using Modules, or\(X\) is
public
and is declared in a module that is imported from the same scope as the function call or from its lexical outer scope, and the call qualifies the function name with the module name, see also Importing Modules.\(X\) is a method and it is visible in the module that defines the receiver type.
Determining Candidate Functions¶
Given a function call, a function is determined to be a candidate function if there is a valid mapping from the function call to the function where each actual argument is mapped to a formal argument with a legal argument mapping.
Valid Mapping¶
The following algorithm determines a valid mapping from a function call to a function if one exists:
Each actual argument that is passed by name is matched to the formal argument with that name. If there is no formal argument with that name, there is no valid mapping.
The remaining actual arguments are mapped in order to the remaining formal arguments in order. If there are more actual arguments then formal arguments, there is no valid mapping. If any formal argument that is not mapped to by an actual argument does not have a default value, there is no valid mapping.
The valid mapping is the mapping of actual arguments to formal arguments plus default values to formal arguments that are not mapped to by actual arguments.
Legal Argument Mapping¶
An actual argument \(A_i\) of type \(T(A_i)\) can be legally mapped to a formal argument \(X_i\) according to the following rules.
First, if \(A_i\) is a type
but \(X_i\) does not use the
type
intent, then it is not a legal argument mapping.
Then, if \(X_i\) is a generic argument:
if \(X_i\) uses
param
intent, then \(A_i\) must also be aparam
if \(X_i\) uses
type
intent, then \(A_i\) must also be atype
there must exist an instantiation \(T(X_i)\) of the generic declared type of \(X_i\), if any, that is compatible with the type \(T(A_i)\) according to the rules below.
Next, the type \(T(X_i)\) - which is either the declared type of the formal argument \(X_i\) if it is concrete or the instantiated type if \(X_i\) is generic - must be compatible with the type \(T(A_i)\) according to the concrete intent of \(X_i\):
if \(X_i\) uses
ref
intent, then \(T(A_i)\) must be the same type as \(T(X_i)\)if \(X_i\) uses
const ref
intent, then \(T(A_i)\) and \(T(X_i)\) must be the same type or a subtype of \(T(X_i)\) (see Implicit Subtype Conversions) or aparam
(see Implicit Compile-Time Constant Conversions)if \(X_i\) uses
in
,const in
,inout
,const
, or default intent, then \(T(A_i)\) must be the same type, a subtype of, or implicitly convertible to \(T(X_i)\).if \(X_i\) uses the
out
intent, it is always a legal argument mapping regardless of the type of the actual and formal. In the event that setting \(T(A_i)\) from \(X_i\) is not possible then a compilation error will be emitted if this function is chosen as the best candidate.if \(X_i\) uses the
type
intent, then \(T(A_i)\) must be the same type or a subtype of \(T(X_i)\) (see Implicit Subtype Conversions).
Finally, if the above compatibility cannot be established, the mapping is
checked for promotion. If \(T(A_i)\) is scalar promotable to \(T(X_i)\)
(see Promotion), then the above rules are checked with the element
type, index type, or yielded type. For example, if \(T(A_i)\) is an
array of int
and \(T(X_i)\) is int
, then promotion occurs and
the above rules will be checked with \(T(A_i)\) == int
.
Determining Most Specific Functions¶
Given a set of candidate functions, several steps are applied to remove candidates from the set. After the process completes, the remaining candidates in the set are the most specific functions.
Before applying these steps, if there both non-operator method candidates and non-operator non-method candidates, issue an ambiguity error without doing further candidate selection.
If any non-method candidate is more visible (or shadows) another non-method candidate, discard all non-method candidates that are less visible than (or shadowed by) another non-method candidate.
If at least one candidate requires promotion and at least one candidate does not use promotion, discard all candidates that use promotion.
Discard any function that has a less specific argument mapping than any other function. See More Specific Argument Mappings below for details on the more specific argument mapping relation.
Discard any candidates that have more formals that require implicit conversion than other candidates. For this step, implicit conversions between
real(w)
,imag(w)
, andcomplex(2*w)
are not considered.Discard any candidates that have more formals that require a negative
param
value is converted to an unsigned integral type of any width (e.g.,uint(64)
).Discard any candidates that have more formals that require
param
narrowing conversions than another candidate. Aparam
narrowing conversion is an implicit conversion that is allowed for aparam
value according to Implicit Compile-Time Constant Conversions however would not be allowed between the same types for a non-param
value. For example, a conversion from theparam
value1
of the typeint
to the typeint(8)
is aparam
narrowing conversion because it is allowed while an implicit conversion from a non-param value of the typeint
to the typeint(8)
is not allowed.
Note that where
clauses are also considered but that happens in a
later step. See Determining Best Functions.
More Specific Argument Mappings¶
Given candidate functions \(X\) and \(Y\) with formal arguments \(X_1\) \(X_2\) … and \(Y_1\) \(Y_2\) … that correspond to actual arguments \(A_1\) \(A_2\) …, which candidate function is more specific is determined in two steps. First, the non-param actual arguments and their corresponding formal arguments are considered. Then, any param actual arguments and their corresponding formal arguments are considered.
Within each of those steps, the candidate function \(X\) has a more specific argument mapping if:
for each argument \(i\) considered, the argument mapping from \(A_i\) to \(Y_i\) is not better than the argument mapping for the argument \(A_i\) to \(X_i\), and
for at least one argument \(j\) considered, the argument mapping from \(A_j\) to \(X_j\) is better than the argument mapping from \(A_j\) to \(Y_j\).
Better Argument Mapping¶
Given an actual argument \(A_i\) and the corresponding formal arguments \(X_i\) and \(Y_i\) (in the functions \(X\) and \(Y\) being considered), the following rules are applied in order to determine whether \(X_i\) or \(Y_i\) is a better argument mapping:
If one of the formals requires promotion and the other does not, the formal not requiring promotion is better
If both of the formals have the same type after instantiation and one of the formals is less generic than the other formal, the less-generic formal is better
If one of the formals is
param
and the other is not, theparam
formal is betterIf one of the formals requires a param narrowing conversion and the other is not, the one not requiring such narrowing is better
If the actual and both formals are numeric types and one formal is a preferred numeric conversion target, that formal is better
If one of the formals matches the actual type exactly and the other does not, the matching formal is better
If the actual’s scalar type for promotion matches one of the formals but not the other, the matching formal is better
If an implicit conversion is possible from the type of one formal to the other, but not vice versa, then the formal that can be converted from is better. (I.e. if the type of \(X_i\) can implicitly convert to the type of \(Y_i\), then \(X_i\) is better). Similarly, if the type of one formal can be instantiated to produce the type of another formal, the type of the more-instantiated formal is better.
Preferred Numeric Conversion Target¶
To compute if a formal is a preferred numeric conversion target, apply the following rules in order:
Classify the actual and formals by numeric kind. If one formal has the same kind as the actual but the other does not, the formal with the same kind is better. Each of the following bullets represents a different numeric kind for this rule:
bool
int(?w)
oruint(?w)
, that is, a signed or unsigned integral type of any widthreal(?w)
imag(?w)
complex(?w)
all other types
Classify the actual and formals by numeric width. If one formal has the same numeric width as the actual but the other does not, the formal with the same width is better. Each of the following bullets represents a different numeric width for this rule:
All numeric types that match the default width. This includes
bool
,int
,uint
,real
,imag
, andcomplex
as well as their more specific namesint(64)
,uint(64)
,real(64)
,imag(64)
,complex(128)
All numeric types with 32-bit width:
int(32)
,uint(32)
,real(32)
,imag(32)
,complex(64)
.complex(64)
is in this category because the real element width is 32 bits.All numeric types with 16-bit width:
int(16)
,uint(16)
All numeric types with 8-bit width:
int(8)
,uint(8)
Determining Best Functions¶
Given the set of most specific functions for a given return intent, only the following function(s) are selected as best functions:
all functions, if none of them contain a
where
clause;only those functions that have a
where
clause, otherwise.
Choosing Return Intent Overloads Based on Calling Context¶
See also Return Intent Overloads.
The compiler can choose between overloads differing in return intent when:
there are zero or one best functions for each of
ref
,const ref
, or the default (blank) return intentat least two of the above return intents have a best function.
Note
It is currently undefined how return intent overloading interacts with
the const
return or yield intent.
In that case, the compiler is able to choose between ref
return,
const ref
return, and value return functions based upon the context
of the call. The compiler chooses between these return intent overloads
as follows:
If present, a ref
return version will be chosen when:
the call appears on the left-hand side of a variable initialization or assignment statement
the call is passed to another function as a formal argument with
out
,inout
, orref
intentthe call is captured into a
ref
variablethe call is returned from a function with
ref
return intent
Otherwise, the const ref
return or value return version will be
chosen. If only one of these is in the set of most specific functions,
it will be chosen. If both are present in the set, the choice will be
made as follows:
The const ref
version will be chosen when:
the call is passed to another function as a formal argument with
const ref
intentthe call is captured into a
const ref
variablethe call is returned from a function with
const ref
return intent
Otherwise, the value version will be chosen.