First-class Procedures in Chapel¶
Warning
The lambda
keyword (used to construct an anonymous procedure) has been
deprecated as of the 1.31 release. The func()
procedure type constructor
has also been deprecated. Instead, users should make use of the proc()
syntax described in this document.
Note
First-class procedures are under active development and are provided in their prototype form as a preview and to gather user feedback.
The 1.31 release of Chapel introduced proc()
syntax for constructing
anonymous procedures and procedure types. The next few sections provide
details about this syntax.
The section Capturing First-Class Procedures describes when a procedure may be used as a value. The restrictions in this section may weaken as development of first-class procedures continues.
Procedure Types¶
Procedure types may be constructed using syntax which mirrors the syntax for named procedure definition.
To construct the type of a procedure which takes two integers and returns an integer, users may write the following:
type T = proc(x: int, y: int): int;
writeln(T:string); // 'proc(x: int, y: int): int'
Note
Currently proc(
must be written without any spaces occurring between
proc
and the open parenthesis (
. Otherwise the compiler will
report a syntax error. This is an implementation restriction and may be
removed in the future.
Notice that the formals of the procedure type have specified names,
x
and y
. Currently, the intention is for formal names to participate
in typing such that two procedures that vary only by their formal names
have different types. In the below example, assignment of two procedure
values will fail because the formal names are different:
proc foo(x: int, y: int): int { return x + y; }
proc bar(a: int, b: int): int { return a + b; }
writeln(foo.type:string); // 'proc(x: int, y: int): int'
var x = foo; // 'x' is typed 'proc(x: int, y: int): int'
writeln(bar.type:string); // 'proc(a: int, b: int): int'
x = bar; // Error!
There are scenarios where a user may want to pass around procedures
without regard for their formal names. Anonymous _
formals are a
feature under active development that is intended to support this
behavior.
Anonymous Procedures¶
The syntax for constructing anonymous procedures also mirrors named procedure definition.
// The procedure named 'foo' is not an anonymous procedure.
proc foo(x: int, y: int): int { return x + y; }
// Define an anonymous procedure bound to the constant variable 'bar'.
const bar = proc(a: int, b: int): int { return a + b; };
The Formals of Procedure Types May Be Anonymous¶
Note
Currently, anonymous formals are not implemented for either procedure types or procedure definitions. These features may be added in the future.
A formal in a procedure type may be anonymous. This can be done by
naming the formal _
, similar to what is written to discard
tuple elements when de-tupling.
If a procedure type T declares a formal at position N to be anonymous, then a value of type T may use any name for its formal at position N.
proc foo(x: int, y: int): int { return x + y; }
proc bar(a: int, b: int): int { return a + b; }
// Here the formals of 'T' are anonymous.
type T = proc(_: int, _: int): int;
var x: T = foo; // OK, T's formals are anonymous '_'.
writeln(foo.type:string); // 'proc(x: int, y: int): int'
writeln(x.type:string); // 'proc(_: int, _: int): int'
writeln(bar.type:string); // 'proc(a: int, b: int): int'
x = bar; // OK!
In the above example, two procedures are declared with different formal names, but otherwise identical types. A local variable x is declared with a procedure type that has anonymous formals. This enables the procedures foo and bar to be assigned freely to x.
Note
It is undecided as to whether a procedure type may mix anonymous formal names with named formals, or if all formals must be either named or anonymous.
Procedure definitions may declare anonymous formals as well. When a formal is declared anonymous, it cannot be referenced within the body of the procedure.
// The second formal of 'baz' is anonymous and cannot be used.
proc baz(x: int, _: int): int { return x + 1; }
Capturing First-Class Procedures¶
Procedures may be captured as values by referring to them by name.
For example:
proc myfunc(x:int) { return x + 1; }
const p = myfunc;
writeln(p(3)); // outputs: 4
Anonymous procedures may be captured as though they are named procedures, by substituting them in places where a procedure name may also appear:
const p = proc(x: int) { return x + 1; };
writeln(p(3)); // outputs: 4
Today, only procedures (defined with the proc
keyword) may be
captured. Additionally, a captured procedure must not:
Refer to any outer variable that is not at module scope
Have a type or param return type
Accept type or param formals
Be a method
Be overloaded
Be generic
Be parenless
Note
These restrictions may be weakened or removed as development of first-class procedures continues. Explanation for them follows.
Capturing iterators, operators, and methods falls beyond the scope of the current design.
Capturing a procedure that refers to outer scope variables necessitates the creation of a closure. Closure support will be added at a later date, at which point this restriction will be removed.
Procedures with type or param formals or return types cannot be captured because such procedures must be evaluatable at compile-time. The notion of a param first-class procedure has been considered as a way to circumvent this restriction, but has not been fully explored at this time.
Overloaded procedures cannot be captured without further disambiguation. This is left as future work.
Future Directions¶
Chapel’s first-class procedure story is under active development. If you have specific feature requests or suggestions, please let us know on the Chapel GitHub issues page or community forums.