Skip to content

Instantly share code, notes, and snippets.

@mppf
Created September 23, 2019 19:31
Show Gist options
  • Save mppf/856e3b96b9e01cd840642f2eecacfd60 to your computer and use it in GitHub Desktop.
Save mppf/856e3b96b9e01cd840642f2eecacfd60 to your computer and use it in GitHub Desktop.
©2019 Cray Inc.
Scope
=====
[Scope]
Chapel is a new parallel programming language that is under development
at Cray Inc. in the context of the DARPA High Productivity Computing
Systems initiative.
This document is ultimately intended to be the definitive specification
of the Chapel language. The current draft is a work-in-progress and
therefore incomplete.
Notation
========
[Notation]
Special notations are used in this specification to denote Chapel code
and to denote Chapel syntax.
Chapel code is represented with a fixed-width font where keywords are
bold and comments are italicized.
*Example*.
::
for i in D do // iterate over domain D
writeln(i); // output indices in D
Chapel syntax is represented with standard syntax notation in which
productions define the syntax of the language. A production is defined
in terms of non-terminal (*italicized*) and terminal (non-italicized)
symbols. The complete syntax defines all of the non-terminal symbols in
terms of one another and terminal symbols.
A definition of a non-terminal symbol is a multi-line construct. The
first line shows the name of the non-terminal that is being defined
followed by a colon. The next lines before an empty line define the
alternative productions to define the non-terminal.
*Example*.
The production
bool-literal: ‘true’ ‘false’
defines ``bool-literal`` to be either the symbol :literal:`\`true'`
or :literal:`\`false'`.
In the event that a single line of a definition needs to break across
multiple lines of text, more indentation is used to indicate that it is
a continuation of the same alternative production.
As a short-hand for cases where there are many alternatives that define
one symbol, the first line of the definition of the non-terminal may be
followed by “one of” to indicate that the single line in the production
defines alternatives for each symbol.
*Example*.
The production
::
unary-operator: one of
+ - ~ !
is equivalent to
::
unary-operator:
+
-
~
!
As a short-hand to indicate an optional symbol in the definition of a
production, the subscript “opt” is suffixed to the symbol.
*Example*.
The production
::
formal:
formal-tag identifier formal-type[OPT] default-expression[OPT]
is equivalent to
::
formal:
formal-tag identifier formal-type default-expression
formal-tag identifier formal-type
formal-tag identifier default-expression
formal-tag identifier
Organization
============
[Organization]
This specification is organized as follows:
- Chapter \ `[Scope] <#Scope>`__, Scope, describes the scope of this
specification.
- Chapter \ `[Notation] <#Notation>`__, Notation, introduces the
notation that is used throughout this specification.
- Chapter \ `[Organization] <#Organization>`__, Organization, describes
the contents of each of the chapters within this specification.
- Chapter \ `[Acknowledgments] <#Acknowledgments>`__, Acknowledgements,
offers a note of thanks to people and projects.
- Chapter \ `[Language_Overview] <#Language_Overview>`__, Language
Overview, describes Chapel at a high level.
- Chapter \ `[Lexical_Structure] <#Lexical_Structure>`__, Lexical
Structure, describes the lexical components of Chapel.
- Chapter \ `[Types] <#Types>`__, Types, describes the types in Chapel
and defines the primitive and enumerated types.
- Chapter \ `[Variables] <#Variables>`__, Variables, describes
variables and constants in Chapel.
- Chapter \ `[Conversions] <#Conversions>`__, Conversions, describes
the legal implicit and explicit conversions allowed between values of
different types. Chapel does not allow for user-defined conversions.
- Chapter \ `[Expressions] <#Expressions>`__, Expressions, describes
the non-parallel expressions in Chapel.
- Chapter \ `[Statements] <#Statements>`__, Statements, describes the
non-parallel statements in Chapel.
- Chapter \ `[Modules] <#Modules>`__, Modules, describes modules in
Chapel., Chapel modules allow for namespace management.
- Chapter \ `[Functions] <#Functions>`__, Functions, describes
functions and function resolution in Chapel.
- Chapter \ `[Tuples] <#Tuples>`__, Tuples, describes tuples in Chapel.
- Chapter \ `[Classes] <#Classes>`__, Classes, describes reference
classes in Chapel.
- Chapter \ `[Records] <#Records>`__, Records, describes records or
value classes in Chapel.
- Chapter \ `[Unions] <#Unions>`__, Unions, describes unions in Chapel.
- Chapter \ `[Ranges] <#Ranges>`__, Ranges, describes ranges in Chapel.
- Chapter \ `[Domains] <#Domains>`__, Domains, describes domains in
Chapel. Chapel domains are first-class index sets that support the
description of iteration spaces, array sizes and shapes, and sets of
indices.
- Chapter \ `[Arrays] <#Arrays>`__, Arrays, describes arrays in Chapel.
Chapel arrays are more general than in most languages including
support for multidimensional, sparse, associative, and unstructured
arrays.
- Chapter \ `[Iterators] <#Iterators>`__, Iterators, describes iterator
functions.
- Chapter \ `[Generics] <#Generics>`__, Generics, describes Chapel’s
support for generic functions and types.
- Chapter \ `[Input_and_Output] <#Input_and_Output>`__, Input and
Output, describes support for input and output in Chapel, including
file input and output..
- Chapter \ `[Task_Parallelism_and_Synchronization] <#Task_Parallelism_and_Synchronization>`__,
Task Parallelism and Synchronization, describes task-parallel
expressions and statements in Chapel as well as synchronization
constructs, atomic variables, and the atomic statement.
- Chapter \ `[Data_Parallelism] <#Data_Parallelism>`__, Data
Parallelism, describes data-parallel expressions and statements in
Chapel including reductions and scans, whole array assignment, and
promotion.
- Chapter \ `[Locales_Chapter] <#Locales_Chapter>`__, Locales,
describes constructs for managing locality and executing Chapel
programs on distributed-memory systems.
- Chapter \ `[Domain_Maps] <#Domain_Maps>`__, Domain Maps, describes
Chapel’s *domain map* construct for defining the layout of domains
and arrays within a single locale and/or the distribution of domains
and arrays across multiple locales.
- Chapter \ `[User_Defined_Reductions_and_Scans] <#User_Defined_Reductions_and_Scans>`__,
User-Defined Reductions and Scans, describes how Chapel programmers
can define their own reduction and scan operators.
- Chapter \ `[Memory_Consistency_Model] <#Memory_Consistency_Model>`__,
Memory Consistency Model, describes Chapel’s rules for ordering the
reads and writes performed by a program’s tasks.
- Chapter \ `[Interoperability] <#Interoperability>`__ describes
Chapel’s interoperability features for combining Chapel programs with
code written in different languages.
- Appendix \ `[Syntax] <#Syntax>`__, Collected Lexical and Syntax
Productions, contains the syntax productions listed throughout this
specification in both alphabetical and depth-first order.
Acknowledgments
===============
[Acknowledgments]
The following people have been actively involved in the recent evolution
of the Chapel language and its specification: Kyle Brady, Bradford
Chamberlain, Sung-Eun Choi, Lydia Duncan, Michael Ferguson, Ben
Harshbarger, Tom Hildebrandt, David Iten, Vassily Litvinov, Tom
MacDonald, Michael Noakes, Elliot Ronaghan, Greg Titus, Thomas Van
Doren, and Tim Zakian
The following people have contributed to previous versions of the
language and its specification: Robert Bocchino, David Callahan, Steven
Deitz, Roxana Diaconescu, James Dinan, Samuel Figueroa, Shannon
Hoffswell, Mary Beth Hribar, Mark James, Mackale Joyner, Jacob Nelson,
John Plevyak, Lee Prokowich, Albert Sidelnik, Andy Stone, Wayne Wong,
and Hans Zima.
We are also grateful to our many enthusiastic and vocal users for
helping us continually improve the quality of the Chapel language and
compiler.
Chapel is a derivative of a number of parallel and distributed languages
and takes ideas directly from them, especially the MTA extensions of C,
HPF, and ZPL.
Chapel also takes many serial programming ideas from many other
programming languages, especially C#, C++, Java, Fortran, and Ada.
The preparation of this specification was made easier and the final
result greatly improved because of the good work that went in to the
creation of other language standards and specifications, in particular
the specifications of C# and C.
Language Overview
=================
[Language_Overview]
Chapel is an emerging parallel programming language designed for
productive scalable computing. Chapel’s primary goal is to make parallel
programming far more productive, from multicore desktops and laptops to
commodity clusters and the cloud to high-end supercomputers. Chapel’s
design and development are being led by Cray Inc. in collaboration with
academia, computing centers, and industry.
Chapel is being developed in an open-source manner at GitHub under the
Apache v2.0 license and also makes use of other third-party open-source
packages under their own licenses. Chapel emerged from Cray’s entry in
the DARPA-led High Productivity Computing Systems program (HPCS). It is
currently being hardened from that initial prototype to more of a
product-grade implementation.
This section provides a brief overview of the Chapel language by
discussing first the guiding principles behind the design of the
language and second how to get started with Chapel.
.. _Guiding_Principles:
Guiding Principles
------------------
The following four principles guided the design of Chapel:
#. General parallel programming
#. Locality-aware programming
#. Object-oriented programming
#. Generic programming
The first two principles were motivated by a desire to support general,
performance-oriented parallel programming through high-level
abstractions. The second two principles were motivated by a desire to
narrow the gulf between high-performance parallel programming languages
and mainstream programming and scripting languages.
.. _General_Parallel_Programming:
General Parallel Programming
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
First and foremost, Chapel is designed to support general parallel
programming through the use of high-level language abstractions. Chapel
supports a *global-view programming model* that raises the level of
abstraction in expressing both data and control flow as compared to
parallel programming models currently in use. A global-view programming
model is best defined in terms of *global-view data structures* and a
*global view of control*.
*Global-view data structures* are arrays and other data aggregates whose
sizes and indices are expressed globally even though their
implementations may distribute them across the *locales* of a parallel
system. A locale is an abstraction of a unit of uniform memory access on
a target architecture. That is, within a locale all threads exhibit
similar access times to any specific memory address. For example, a
locale in a commodity cluster could be defined to be a single core of a
processor, a multicore processor, or an SMP node of multiple processors.
Such a global view of data contrasts with most parallel languages which
tend to require users to partition distributed data aggregates into
per-processor chunks either manually or using language abstractions. As
a simple example, consider creating a 0-based vector with :math:`n`
elements distributed between :math:`p` locales. A language that supports
global-view data structures, as Chapel does, allows the user to declare
the array to contain :math:`n` elements and to refer to the array using
the indices :math:`0 \ldots n-1`. In contrast, most traditional
approaches require the user to declare the array as :math:`p` chunks of
:math:`n/p` elements each and to specify and manage inter-processor
communication and synchronization explicitly (and the details can be
messy if :math:`p` does not divide :math:`n` evenly). Moreover, the
chunks are typically accessed using local indices on each processor
(*e.g.*, \ :math:`0..n/p`), requiring the user to explicitly translate
between logical indices and those used by the implementation.
A *global view of control* means that a user’s program commences
execution with a single logical thread of control and then introduces
additional parallelism through the use of certain language concepts. All
parallelism in Chapel is implemented via multithreading, though these
threads are created via high-level language concepts and managed by the
compiler and runtime rather than through explicit fork/join-style
programming. An impact of this approach is that Chapel can express
parallelism that is more general than the Single Program, Multiple
Data (SPMD) model that today’s most common parallel programming
approaches use. Chapel’s general support for parallelism does not
preclude users from coding in an SPMD style if they wish.
Supporting general parallel programming also means targeting a broad
range of parallel architectures. Chapel is designed to target a wide
spectrum of HPC hardware including clusters of commodity processors and
SMPs; vector, multithreading, and multicore processors; custom vendor
architectures; distributed-memory, shared-memory, and shared
address-space architectures; and networks of any topology. Our
portability goal is to have any legal Chapel program run correctly on
all of these architectures, and for Chapel programs that express
parallelism in an architecturally-neutral way to perform reasonably on
all of them. Naturally, Chapel programmers can tune their code to more
closely match a particular machine’s characteristics.
.. _Locality_Aware_Programming:
Locality-Aware Programming
~~~~~~~~~~~~~~~~~~~~~~~~~~
A second principle in Chapel is to allow the user to optionally and
incrementally specify where data and computation should be placed on the
physical machine. Such control over program locality is essential to
achieve scalable performance on distributed-memory architectures. Such
control contrasts with shared-memory programming models which present
the user with a simple flat memory model. It also contrasts with
SPMD-based programming models in which such details are explicitly
specified by the programmer on a process-by-process basis via the
multiple cooperating program instances.
.. _Object_Oriented_Programming:
Object-Oriented Programming
~~~~~~~~~~~~~~~~~~~~~~~~~~~
A third principle in Chapel is support for object-oriented programming.
Object-oriented programming has been instrumental in raising
productivity in the mainstream programming community due to its
encapsulation of related data and functions within a single software
component, its support for specialization and reuse, and its use as a
clean mechanism for defining and implementing interfaces. Chapel
supports objects in order to make these benefits available in a parallel
language setting, and to provide a familiar coding paradigm for members
of the mainstream programming community. Chapel supports traditional
reference-based classes as well as value classes that are assigned and
passed by value.
.. _Generic_Programming:
Generic Programming
~~~~~~~~~~~~~~~~~~~
Chapel’s fourth principle is support for generic programming and
polymorphism. These features allow code to be written in a style that is
generic across types, making it applicable to variables of multiple
types, sizes, and precisions. The goal of these features is to support
exploratory programming as in popular interpreted and scripting
languages, and to support code reuse by allowing algorithms to be
expressed without explicitly replicating them for each possible type.
This flexibility at the source level is implemented by having the
compiler create versions of the code for each required type signature
rather than by relying on dynamic typing which would result in
unacceptable runtime overheads for the HPC community.
.. _Getting_Started:
Getting Started
---------------
A Chapel version of the standard “hello, world” computation is as
follows:
::
writeln("hello, world");
This complete Chapel program contains a single line of code that makes a
call to the standard ``writeln`` function.
In general, Chapel programs define code using one or more named
*modules*, each of which supports top-level initialization code that is
invoked the first time the module is used. Programs also define a single
entry point via a function named ``main``. To facilitate exploratory
programming, Chapel allows programmers to define modules using files
rather than an explicit module declaration and to omit the program entry
point when the program only has a single user module.
Chapel code is stored in files with the extension ``.chpl``. Assuming
the “hello, world” program is stored in a file called ``hello.chpl``, it
would define a single user module, ``hello``, whose name is taken from
the filename. Since the file defines a module, the top-level code in the
file defines the module’s initialization code. And since the program is
composed of the single ``hello`` module, the ``main`` function is
omitted. Thus, when the program is executed, the single ``hello`` module
will be initialized by executing its top-level code thus invoking the
call to the ``writeln`` function. Modules are described in more detail
in §\ `[Modules] <#Modules>`__.
To compile and run the “hello world” program, execute the following
commands at the system prompt:
::
> chpl hello.chpl
> ./hello
The following output will be printed to the console:
::
hello, world
Lexical Structure
=================
[Lexical_Structure]
This section describes the lexical components of Chapel programs. The
purpose of lexical analysis is to separate the raw input stream into a
sequence of tokens suitable for input to the parser.
.. _Comments:
Comments
--------
Two forms of comments are supported. All text following the consecutive
characters // and before the end of the line is in a comment. All text
following the consecutive characters /\* and before the consecutive
characters / is in a comment. A comment delimited by /\* and / can be
nested in another comment delimited by /\* and /
Comments, including the characters that delimit them, do not affect the
behavior of the program (except in delimiting tokens). If the delimiters
that start the comments appear within a bytes or string literal, they do
not start a comment but rather are part of the bytes or string literal.
*Example*.
The following program makes use of both forms of comment:
::
/*
* main function
*/
proc main() {
writeln("hello, world"); // output greeting with new line
}
.. _White_Space:
White Space
-----------
White-space characters are spaces, tabs, line feeds, form feeds, and
carriage returns. Along with comments, they delimit tokens, but are
otherwise ignored.
.. _Case_Sensitivity:
Case Sensitivity
----------------
Chapel is a case sensitive language.
*Example*.
The following identifiers are considered distinct: ``chapel``,
``Chapel``, and ``CHAPEL``.
.. _Tokens:
Tokens
------
Tokens include identifiers, keywords, literals, operators, and
punctuation.
.. _Identifiers:
Identifiers
~~~~~~~~~~~
An identifier in Chapel is a sequence of characters that starts with a
lowercase or uppercase letter or an underscore and is optionally
followed by a sequence of lowercase or uppercase letters, digits,
underscores, and dollar-signs. Identifiers are designated by the
following syntax:
::
identifier:
letter-or-underscore legal-identifier-chars[OPT]
legal-identifier-chars:
legal-identifier-char legal-identifier-chars[OPT]
legal-identifier-char:
letter-or-underscore
digit
`(*\texttt{\$}*)'
letter-or-underscore:
letter
`_'
letter: one of
`A' `B' `C' `D' `E' `F' `G' `H' `I' `J' `K' `L' `M' `N' `O' `P' `Q' `R' `S' `T' `U' `V' `W' `X' `Y' `Z'
`a' `b' `c' `d' `e' `f' `g' `h' `i' `j' `k' `l' `m' `n' `o' `p' `q' `r' `s' `t' `u' `v' `w' `x' `y' `z'
digit: one of
`0' `1' `2' `3' `4' `5' `6' `7' `8' `9'
..
*Rationale*.
Why include “$” in the language? The inclusion of the $ character is
meant to assist programmers using sync and single variables by
supporting a convention (a $ at the end of such variables) in order
to help write properly synchronized code. It is felt that marking
such variables is useful since using such variables could result in
deadlocks.
*Example*.
The following are legal identifiers: ``Cray1``, ``syncvar$``,
``legalIdentifier``, and ``legal_identifier``.
.. _Keywords:
Keywords
~~~~~~~~
The following identifiers are reserved as keywords:
::
_
align
as
atomic
begin
bool
borrowed
break
by
bytes
catch
class
cobegin
coforall
complex
config
const
continue
defer
delete
dmapped
do
domain
else
enum
except
export
extern
false
for
forall
forwarding
if
imag
in
index
inline
inout
int
iter
label
let
lifetime
local
locale
module
new
nil
noinit
on
only
otherwise
out
override
owned
param
private
proc
public
real
record
reduce
ref
require
return
scan
select
serial
shared
single
sparse
string
subdomain
sync
then
this
throw
throws
true
try
type
uint
union
unmanaged
use
var
when
where
while
with
yield
zip
The following identifiers are keywords reserved for future use:
::
lambda
pragma
primitive
.. _Literals:
Literals
~~~~~~~~
[Primitive_Type_Literals]
Bool literals are designated by the following syntax:
::
bool-literal: one of
`true' `false'
Signed and unsigned integer literals are designated by the following
syntax:
::
integer-literal:
digits
`0x' hexadecimal-digits
`0X' hexadecimal-digits
`0o' octal-digits
`0O' octal-digits
`0b' binary-digits
`0B' binary-digits
digits:
digit
digit separator-digits
separator-digits:
digit
`_'
digit separator-digits
`_' separator-digits
hexadecimal-digits:
hexadecimal-digit
hexadecimal-digit separator-hexadecimal-digits
separator-hexadecimal-digits:
hexadecimal-digit
`_'
hexadecimal-digit separator-hexadecimal-digits
`_' separator-hexadecimal-digits
hexadecimal-digit: one of
`0' `1' `2' `3' `4' `5' `6' `7' `8' `9' `A' `B' `C' `D' `E' `F' `a' `b' `c' `d' `e' `f'
octal-digits:
octal-digit
octal-digit separator-octal-digits
separator-octal-digits:
octal-digit
`_'
octal-digit separator-octal-digits
`_' separator-octal-digits
octal-digit: one of
`0' `1' `2' `3' `4' `5' `6' `7'
binary-digits:
binary-digit
binary-digit separator-binary-digits
separator-binary-digits:
binary-digit
`_'
binary-digit separator-binary-digits
`_' separator-binary-digits
binary-digit: one of
`0' `1'
Integer literals in the range 0 to max(\ ``int``),
 §`7.1.4 <#Signed_and_Unsigned_Integral_Types>`__, have type ``int`` and
the remaining literals have type ``uint``.
*Rationale*.
Why are there no suffixes on integral literals? Suffixes, like those
in C, are not necessary. Explicit conversions can then be used to
change the type of the literal to another integer size.
..
*Rationale*.
Underscores can be used to group the digits of numbers for
legibility. For example:
::
var i = 1_234_567_890;
var x = 0xFF_FF_12_34;
Real literals are designated by the following syntax:
::
real-literal:
digits[OPT] . digits exponent-part[OPT]
digits .[OPT] exponent-part
`0x' hexadecimal-digits[OPT] . hexadecimal-digits p-exponent-part[OPT]
`0X' hexadecimal-digits[OPT] . hexadecimal-digits p-exponent-part[OPT]
`0x' hexadecimal-digits .[OPT] p-exponent-part
`0X' hexadecimal-digits .[OPT] p-exponent-part
exponent-part:
`e' sign[OPT] digits
`E' sign[OPT] digits
p-exponent-part:
`p' sign[OPT] digits
`P' sign[OPT] digits
sign: one of
+ -
..
*Rationale*.
Why can’t a real literal end with ’.’? There is a lexical ambiguity
between real literals ending in ’.’ and the range operator ’..’ that
makes it difficult to parse. For example, we want to parse ``1..10``
as a range from 1 to 10 without concern that ``1.`` is a real
literal.
Hexadecimal real literals are supported with a hexadecimal integer and
fractional part. Because ’e’ could be a hexadecimal character, the
exponent for these literals is instead marked with ’p’ or ’P’. The
exponent value follows and is written in decimal.
The type of a real literal is ``real``. Explicit conversions are
necessary to change the size of the literal.
Imaginary literals are designated by the following syntax:
::
imaginary-literal:
real-literal `i'
integer-literal `i'
The type of an imaginary literal is ``imag``. Explicit conversions are
necessary to change the size of the literal.
There are no complex literals. Rather, a complex value can be specified
by adding or subtracting a real literal with an imaginary literal.
Alternatively, a 2-tuple of integral or real expressions can be cast to
a complex such that the first component becomes the real part and the
second component becomes the imaginary part.
*Example*.
The following expressions are identical: ``1.0 + 2.0i`` and
``(1.0, 2.0):complex``.
Interpreted string literals are designated by the following syntax:
::
interpreted-string-literal:
" double-quote-delimited-characters[OPT] "
' single-quote-delimited-characters[OPT] '
double-quote-delimited-characters:
string-character double-quote-delimited-characters[OPT]
' double-quote-delimited-characters[OPT]
single-quote-delimited-characters:
string-character single-quote-delimited-characters[OPT]
" single-quote-delimited-characters[OPT]
string-character:
`any character except the double quote, single quote, or new line'
simple-escape-character
hexadecimal-escape-character
simple-escape-character: one of
`$\backslash\mbox{\bf '}\hspace{5pt}$' `$\backslash$"$\hspace{5pt}$' `$\backslash$?$\hspace{5pt}$' `$\backslash$$\backslash$$\hspace{5pt}$' `$\backslash$a$\hspace{5pt}$' `$\backslash$b$\hspace{5pt}$' `$\backslash$f$\hspace{5pt}$' `$\backslash$n$\hspace{5pt}$' `$\backslash$r$\hspace{5pt}$' `$\backslash$t$\hspace{5pt}$' `$\backslash$v$\hspace{5pt}$'
hexadecimal-escape-character:
`$\backslash$x' hexadecimal-digits
\end{syntax}
Uninterpreted string literals are designated by the following syntax:
\begin{syntax}
uninterpreted-string-literal:
""" uninterpreted-double-quote-delimited-characters """
''' uninterpreted-single-quote-delimited-characters '''
uninterpreted-double-quote-delimited-characters:
uninterpreted-double-quote-string-character uninterpreted-double-quote-delimited-characters[OPT]
uninterpreted-single-quote-delimited-characters:
uninterpreted-single-quote-string-character uninterpreted-single-quote-delimited-characters[OPT]
uninterpreted-double-quote-string-character:
`any character except three double quotes in a row'
uninterpreted-single-quote-string-character:
`any character except three single quotes in a row'
Uninterpreted string literals do not interpret their contents, so for
example ``"""$\backslash$n"""`` is not a newline, but rather two
characters ‘:math:`\backslash`’ and ‘n’. Uninterpreted string literals
may span multiple lines and the literal newline characters will be
included in the string.
A string literal can be either interpreted or uninterpreted.
::
string-literal:
interpreted-string-literal
uninterpreted-string-literal
Interpreted bytes literals are designated by the following syntax:
::
interpreted-bytes-literal:
b" double-quote-delimited-characters[OPT] "
b' single-quote-delimited-characters[OPT] '
Uninterpreted bytes literals are designated by the following syntax:
::
uninterpreted-bytes-literal:
b""" uninterpreted-double-quote-delimited-characters """
b''' uninterpreted-single-quote-delimited-characters '''
Uninterpreted bytes literals do not interpret their contents, so for
example ``b"""$\backslash$n"""`` is not a newline, but rather two
characters ‘:math:`\backslash`’ and ‘n’. Uninterpreted bytes literals
may span multiple lines and the literal newline characters will be
included in the bytes.
A bytes literal can be either interpreted or uninterpreted.
::
bytes-literal:
interpreted-bytes-literal
uninterpreted-bytes-literal
.. _Operators_and_Punctuation:
Operators and Punctuation
~~~~~~~~~~~~~~~~~~~~~~~~~
The following operators and punctuation are defined in the syntax of the
language:
=================================================================================================== =============================
**symbols** **use**
=================================================================================================== =============================
``=`` assignment
``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``\%=`` ``\&=`` ``|=`` ``^=`` ``\&\&=`` ``||=`` ``<<=`` ``>>=`` compound assignment
``<=>`` swap
``<~>`` I/O
``..`` range specifier
``by`` range/domain stride specifier
``#`` range count operator
``...`` variable argument lists
``\&\&`` ``||`` ````! ``\&`` ``|`` ``^`` ``~`` ``<<`` ``>>`` logical/bitwise operators
``==`` ````\ =! ``<=`` ``>=`` ``<`` ``>`` relational operators
``+`` ``-`` ``*`` ``/`` ``\%`` ``**`` arithmetic operators
``:`` type specifier
``;`` statement separator
``,`` expression separator
``.`` member access
``?`` type query
``" '`` string delimiters
=================================================================================================== =============================
.. _Grouping_Tokens:
Grouping Tokens
~~~~~~~~~~~~~~~
The following braces are part of the Chapel language:
========== ===================================================================
**braces** **use**
========== ===================================================================
``( )`` parenthesization, function calls, and tuples
``[ ]`` array literals, array types, forall expressions, and function calls
``{ }`` domain literals, block statements
========== ===================================================================
Types
=====
[Types]
Chapel is a statically typed language with a rich set of types. These
include a set of predefined primitive types, enumerated types,
structured types (classes, records, unions, tuples), data parallel types
(ranges, domains, arrays), and synchronization types (sync, single,
atomic).
The syntax of a type is as follows:
::
type-expression:
primitive-type
enum-type
structured-type
dataparallel-type
synchronization-type
lvalue-expression
if-expression
unary-expression
binary-expression
Many expressions are syntactically allowed as a type; however not all
expressions produce a type. For example, a call to a function is
syntactically allowed as the type of a variable. However it would be an
error for that call to result in a value (rather than a type) in that
context.
Programmers can define their own enumerated types, classes, records,
unions, and type aliases using type declaration statements:
::
type-declaration-statement:
enum-declaration-statement
class-declaration-statement
record-declaration-statement
union-declaration-statement
type-alias-declaration-statement
These statements are defined in Sections §\ `7.2 <#Enumerated_Types>`__,
§\ `17.1 <#Class_Declarations>`__, §\ `18.1 <#Record_Declarations>`__,
§\ `19.2 <#Union_Declarations>`__, and §\ `7.6 <#Type_Aliases>`__,
respectively.
.. _Primitive_Types:
Primitive Types
---------------
The concrete primitive types are: ``void``, ``nothing``, ``bool``,
``int``, ``uint``, ``real``, ``imag``, ``complex``, ``string`` and
``bytes``. They are defined in this section.
In addition, there are several generic primitive types that are
described in §\ `24.3.1 <#Built_in_Generic_types>`__.
The primitive types are summarized by the following syntax:
::
primitive-type:
`void'
`nothing'
`bool' primitive-type-parameter-part[OPT]
`int' primitive-type-parameter-part[OPT]
`uint' primitive-type-parameter-part[OPT]
`real' primitive-type-parameter-part[OPT]
`imag' primitive-type-parameter-part[OPT]
`complex' primitive-type-parameter-part[OPT]
`string'
`bytes'
`enum'
`record'
`class'
`owned'
`shared'
`unmanaged'
`borrowed'
primitive-type-parameter-part:
( integer-parameter-expression )
integer-parameter-expression:
expression
If present, the parenthesized ``integer-parameter-expression`` must
evaluate to a compile-time constant of integer type.
See §\ `8.4.1 <#Compile-Time_Constants>`__
*Open issue*.
There is an expectation of future support for larger bit width
primitive types depending on a platform’s native support for those
types.
.. _The_Void_Type:
The Void Type
~~~~~~~~~~~~~
The ``void`` type is used to represent the lack of a value, for example
when a function has no arguments and/or no return type. It is an error
to assign the result of a function that returns ``void`` to a variable.
.. _The_Nothing_type:
The Nothing Type
~~~~~~~~~~~~~~~~
The ``nothing`` type is used to indicate a variable or field that should
be removed by the compiler. The value ``none`` is the only value of type
``nothing``.
The value ``none`` can only be assigned to a variable of type
``nothing``, or to a generic variable that will take on the type
``nothing``. The variable will be removed from the program and have no
representation at run-time.
*Rationale*.
The ``nothing`` type can be used to conditionally remove a variable
or field from the code based on a ``param`` conditional expression.
.. _The_Bool_Type:
The Bool Type
~~~~~~~~~~~~~
Chapel defines a logical data type designated by the symbol ``bool``
with the two predefined values ``true`` and ``false``. This default
boolean type is stored using an implementation-defined number of bits. A
particular number of bits can be specified using a parameter value
following the ``bool`` keyword, such as ``bool(8)`` to request an 8-bit
boolean value. Legal sizes are 8, 16, 32, and 64 bits.
Some statements require expressions of ``bool`` type and Chapel supports
a special conversion of values to ``bool`` type when used in this
context (§\ `9.1.5 <#Implicit_Statement_Bool_Conversions>`__).
.. _Signed_and_Unsigned_Integral_Types:
Signed and Unsigned Integral Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The integral types can be parameterized by the number of bits used to
represent them. Valid bit-sizes are 8, 16, 32, and 64. The default
signed integral type, ``int``, and the default unsigned integral type,
``uint`` correspond to ``int(64)`` and ``uint(64)`` respectively.
The integral types and their ranges are given in the following table:
============== ==================== ====================
**Type** **Minimum Value** **Maximum Value**
============== ==================== ====================
int(8) -128 127
uint(8) 0 255
int(16) -32768 32767
uint(16) 0 65535
int(32) -2147483648 2147483647
uint(32) 0 4294967295
int(64), int -9223372036854775808 9223372036854775807
uint(64), uint 0 18446744073709551615
============== ==================== ====================
The unary and binary operators that are pre-defined over the integral
types operate with 32- and 64-bit precision. Using these operators on
integral types represented with fewer bits results in an implicit
conversion to the corresponding 32-bit types according to the rules
defined in §\ `9.1 <#Implicit_Conversions>`__.
.. _Real_Types:
Real Types
~~~~~~~~~~
Like the integral types, the real types can be parameterized by the
number of bits used to represent them. The default real type, ``real``,
is 64 bits. The real types that are supported are machine-dependent, but
usually include ``real(32)`` (single precision) and ``real(64)`` (double
precision) following the IEEE 754 standard.
.. _Imaginary_Types:
Imaginary Types
~~~~~~~~~~~~~~~
The imaginary types can be parameterized by the number of bits used to
represent them. The default imaginary type, ``imag``, is 64 bits. The
imaginary types that are supported are machine-dependent, but usually
include ``imag(32)`` and ``imag(64)``.
*Rationale*.
The imaginary type is included to avoid numeric instabilities and
under-optimized code stemming from always converting real values to
complex values with a zero imaginary part.
.. _Complex_Types:
Complex Types
~~~~~~~~~~~~~
Like the integral and real types, the complex types can be parameterized
by the number of bits used to represent them. A complex number is
composed of two real numbers so the number of bits used to represent a
complex is twice the number of bits used to represent the real numbers.
The default complex type, ``complex``, is 128 bits; it consists of two
64-bit real numbers. The complex types that are supported are
machine-dependent, but usually include ``complex(64)`` and
``complex(128)``.
| The real and imaginary components can be accessed via the methods
``re`` and ``im``. The type of these components is real. The standard
``Math`` module provides some functions on complex types. See
| https://chapel-lang.org/docs/modules/standard/Math.html
*Example*.
Given a complex number ``c`` with the value ``3.14+2.72i``, the
expressions ``c.re`` and ``c.im`` refer to ``3.14`` and ``2.72``
respectively.
.. _The_String_Type:
The String Type
~~~~~~~~~~~~~~~
Strings are a primitive type designated by the symbol ``string``
comprised of Unicode characters in UTF-8 encoding. Their length is
unbounded.
*Open issue*.
There is an expectation of future support for fixed-length strings.
.. _The_Bytes_Type:
The Bytes Type
~~~~~~~~~~~~~~
Bytes is a primitive type designated by the symbol ``bytes`` comprised
of arbitrary bytes. Bytes are immutable in-place and their length is
unbounded.
*Open issue*.
There is an expectation of future support for mutable bytes.
.. _Enumerated_Types:
Enumerated Types
----------------
Enumerated types are declared with the following syntax:
::
enum-declaration-statement:
`enum' identifier { enum-constant-list }
enum-constant-list:
enum-constant
enum-constant , enum-constant-list[OPT]
enum-constant:
identifier init-part[OPT]
init-part:
= expression
The enumerated type can then be referenced by its name, as summarized by
the following syntax:
enum-type: identifier
An enumerated type defines a set of named constants that can be referred
to via a member access on the enumerated type. Each enumerated type is a
distinct type.
If the ``init-part`` is omitted for all of the named constants in an
enumerated type, the enumerated values are *abstract* and do not have
associated integer values. Any constant that has an ``init-part`` will
be associated with that integer value. Such constants must be parameter
values of integral type. Any constant that does not have an
``init-part``, yet which follows one that does, will be associated with
an integer value one greater than its predecessor. An enumerated type
whose first constant has an ``init-part`` is called *concrete*, since
all constants in the enum will have an associated integer value, whether
explicit or implicit. An enumerated type that specifies an ``init-part``
for some constants, but not the first is called *semi-concrete*. Numeric
conversions are automatically supported for enumerated types which are
concrete or semi-concrete
(see §`9.2.3 <#Explicit_Enumeration_Conversions>`__).
*Example (enum.chpl)*.
The code
enum statesman Aristotle, Roosevelt, Churchill, Kissinger
defines an abstract enumerated type with four constants. The function
::
proc quote(s: statesman) {
select s {
when statesman.Aristotle do
writeln("All paid jobs absorb and degrade the mind.");
when statesman.Roosevelt do
writeln("Every reform movement has a lunatic fringe.");
when statesman.Churchill do
writeln("A joke is a very serious thing.");
when statesman.Kissinger do
{ write("No one will ever win the battle of the sexes; ");
writeln("there's too much fraternizing with the enemy."); }
}
}
::
for s in statesman do
quote(s:statesman);
All paid jobs absorb and degrade the mind. Every reform movement has
a lunatic fringe. A joke is a very serious thing. No one will ever
win the battle of the sexes; there’s too much fraternizing with the
enemy.
outputs a quote from the given statesman. Note that enumerated
constants must be prefixed by the enumerated type name and a dot
unless a use statement is employed
(see §`11.11 <#The_Use_Statement>`__).
It is possible to iterate over an enumerated type. The loop body will be
invoked on each named constant in the enum. The following method is also
available:
::
proc $enum$.size: param int
The number of constants in the given enumerated type.
.. _Structured_Types:
Structured Types
----------------
The structured types are summarized by the following syntax:
::
structured-type:
class-type
record-type
union-type
tuple-type
Classes are discussed in §\ `[Classes] <#Classes>`__. Records are
discussed in §\ `[Records] <#Records>`__. Unions are discussed in
§\ `[Unions] <#Unions>`__. Tuples are discussed in
§\ `[Tuples] <#Tuples>`__.
.. _Types_Class_Types:
Class Types
~~~~~~~~~~~
A class can contain variables, constants, and methods.
Classes are defined in §\ `[Classes] <#Classes>`__. The class type can
also contain type aliases and parameters. Such a class is generic and is
defined in §\ `24.3 <#Generic_Types>`__.
A class type ``C`` has several variants:
- ``C`` and ``C?``
- ``owned C`` and ``owned C?``
- ``shared C`` and ``shared C?``
- ``borrowed C`` and ``borrowed C?``
- ``unmanaged C`` and ``unmanaged C?``
The variants with a question mark, such as ``owned C?``, can store
``nil`` (see §`17.1.3 <#Nilable_Classes>`__). Variants without a
question mark cannot store ``nil``. The keywords ``owned``, ``shared``,
``borrowed``, and ``unmanaged`` indicate the memory management strategy
used for the class. When none is specified, as with ``C`` or ``C?``, the
class is considered to have generic memory management strategy.
See §\ `17.1.2 <#Class_Types>`__.
.. _Types_Record_Types:
Record Types
~~~~~~~~~~~~
Records can contain variables, constants, and methods. Unlike class
types, records are values rather than references. Records are defined
in §\ `[Records] <#Records>`__.
.. _Types_Union_Types:
Union Types
~~~~~~~~~~~
The union type defines a type that contains one of a set of variables.
Like classes and records, unions may also define methods. Unions are
defined in §\ `[Unions] <#Unions>`__.
.. _Types_Tuple_Types:
Tuple Types
~~~~~~~~~~~
A tuple is a light-weight record that consists of one or more anonymous
fields. If all the fields are of the same type, the tuple is
homogeneous. Tuples are defined in §\ `[Tuples] <#Tuples>`__.
.. _Data_Parallel_Types:
Data Parallel Types
-------------------
The data parallel types are summarized by the following syntax:
::
dataparallel-type:
range-type
domain-type
mapped-domain-type
array-type
index-type
Ranges and their index types are discussed in §\ `[Ranges] <#Ranges>`__.
Domains and their index types are discussed in
§\ `[Domains] <#Domains>`__. Arrays are discussed in
§\ `[Arrays] <#Arrays>`__.
.. _Types_Range_Types:
Range Types
~~~~~~~~~~~
A range defines an integral sequence of some integral type. Ranges are
defined in §\ `[Ranges] <#Ranges>`__.
.. _Domain_and_Array_Types:
Domain, Array, and Index Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A domain defines a set of indices. An array defines a set of elements
that correspond to the indices in its domain. A domain’s indices can be
of any type. Domains, arrays, and their index types are defined in
§\ `[Domains] <#Domains>`__ and §\ `[Arrays] <#Arrays>`__.
.. _Synchronization_Types:
Synchronization Types
---------------------
The synchronization types are summarized by the following syntax:
::
synchronization-type:
sync-type
single-type
atomic-type
Sync and single types are discussed in
§\ `26.3 <#Synchronization_Variables>`__. The atomic type is discussed
in §\ `26.4 <#Atomic_Variables>`__.
.. _Type_Aliases:
Type Aliases
------------
Type aliases are declared with the following syntax:
::
type-alias-declaration-statement:
privacy-specifier[OPT] `config'[OPT] `type' type-alias-declaration-list ;
external-type-alias-declaration-statement
type-alias-declaration-list:
type-alias-declaration
type-alias-declaration , type-alias-declaration-list
type-alias-declaration:
identifier = type-expression
identifier
A type alias is a symbol that aliases the type specified in the
``type-expression``. A use of a type alias has the same meaning as using
the type specified by ``type-expression`` directly.
Type aliases defined at the module level are public by default. The
optional ``privacy-specifier`` keywords are provided to specify or
change this behavior. For more details on the visibility of symbols, see
 §`12.5.2 <#Visibility_Of_Symbols>`__.
If the keyword ``config`` precedes the keyword ``type``, the type alias
is called a configuration type alias. Configuration type aliases can be
set at compilation time via compilation flags or other
implementation-defined means. The ``type-expression`` in the program is
ignored if the type-alias is alternatively set.
If the keyword ``extern`` precedes the ``type`` keyword, the type alias
is external. The declared type name is used by Chapel for type
resolution, but no type alias is generated by the backend. See the
chapter on interoperability
(§`[Interoperability] <#Interoperability>`__) for more information on
external types.
The ``type-expression`` is optional in the definition of a class or
record. Such a type alias is called an unspecified type alias. Classes
and records that contain type aliases, specified or unspecified, are
generic (§\ `24.3.3 <#Type_Aliases_in_Generic_Types>`__).
*Open issue*.
There is on going discussion on whether a type alias is a new type or
simply an alias. The former should enable redefinition of default
values, identity elements, etc.
Variables
=========
[Variables]
A variable is a symbol that represents memory. Chapel is a
statically-typed, type-safe language so every variable has a type that
is known at compile-time and the compiler enforces that values assigned
to the variable can be stored in that variable as specified by its type.
.. _Variable_Declarations:
Variable Declarations
---------------------
Variables are declared with the following syntax:
::
variable-declaration-statement:
privacy-specifier[OPT] config-or-extern[OPT] variable-kind variable-declaration-list ;
config-or-extern: one of
`config' $ $ $ $ `extern'
variable-kind:
`param'
`const'
`var'
`ref'
`const ref'
variable-declaration-list:
variable-declaration
variable-declaration , variable-declaration-list
variable-declaration:
identifier-list type-part[OPT] initialization-part
identifier-list type-part no-initialization-part[OPT]
type-part:
: type-expression
initialization-part:
= expression
no-initialization-part:
= `noinit'
identifier-list:
identifier
identifier , identifier-list
tuple-grouped-identifier-list
tuple-grouped-identifier-list , identifier-list
tuple-grouped-identifier-list:
( identifier-list )
A ``variable-declaration-statement`` is used to define one or more
variables. If the statement is a top-level module statement, the
variables are module level; otherwise they are local. Module level
variables are discussed in §\ `8.2 <#Module_Level_Variables>`__. Local
variables are discussed in §\ `8.3 <#Local_Variables>`__.
The optional ``privacy-specifier`` keywords indicate the visibility of
module level variables to outside modules. By default, variables are
publicly visible. More details on visibility can be found in
 §`12.5.2 <#Visibility_Of_Symbols>`__.
The optional keyword ``config`` specifies that the variables are
configuration variables, described in
Section §\ `8.5 <#Configuration_Variables>`__. The optional keyword
``extern`` indicates that the variable is externally defined. Its name
and type are used within the Chapel program for resolution, but no space
is allocated for it and no initialization code emitted. See
§\ `32.2.2 <#Shared_Data>`__ for further details.
The ``variable-kind`` specifies whether the variables are parameters
(``param``), constants (``const``), ref variables (``ref``), or regular
variables (``var``). Parameters are compile-time constants whereas
constants are runtime constants. Both levels of constants are discussed
in §\ `8.4 <#Constants>`__. Ref variables are discussed in
§\ `8.6 <#Ref_Variables>`__.
The ``type-part`` of a variable declaration specifies the type of the
variable. It is optional if the ``initialization-part`` is specified. If
the ``type-part`` is omitted, the type of the variable is inferred using
local type inference described in §\ `8.1.3 <#Local_Type_Inference>`__.
If the ``type-part`` refers to a generic type, then an
``initialization-part`` is required and will be used to determine the
type of the variable. In this event, the compiler will fail with an
error if the ``initialization-part`` is not coercible to an
instantiation of the generic type.
The ``initialization-part`` of a variable declaration specifies an
initial expression to assign to the variable. If the
``initialization-part`` is omitted, the ``type-part`` must be present,
and the variable is initialized to the default value of its type as
described in §\ `8.1.1 <#Default_Values_For_Types>`__.
If the ``no-initialization-part`` is present, the variable declaration
does not initialize the variable to any value, as described
in §\ `8.1.2 <#Noinit_Capability>`__. The result of any read of an
uninitialized variable is undefined until that variable is written.
Multiple variables can be defined in the same
``variable-declaration-list``. The semantics of declaring multiple
variables that share an ``initialization-part`` and/or ``type-part`` is
defined in §\ `8.1.4 <#Multiple_Variable_Declarations>`__.
Multiple variables can be grouped together using a tuple notation as
described in §\ `16.6.2 <#Variable_Declarations_in_a_Tuple>`__.
.. _Default_Values_For_Types:
Default Initialization
~~~~~~~~~~~~~~~~~~~~~~
If a variable declaration has no initialization expression, a variable
is initialized to the default value of its type. The default values are
as follows:
=========== =======================================
**Type** **Default Value**
=========== =======================================
bool(*) false
int(*) 0
uint(*) 0
real(*) 0.0
imag(*) 0.0i
complex(*) 0.0 + 0.0i
string ""
bytes b""
enums first enum constant
classes nil
records default constructed record
ranges 1..0 :math:`` :math:`` (empty sequence)
arrays elements are default values
tuples components are default values
sync/single base default value and *empty* status
atomic base default value
=========== =======================================
.. _Noinit_Capability:
Deferred Initialization
~~~~~~~~~~~~~~~~~~~~~~~
For performance purposes, a variable’s declaration can specify that the
variable should not be default initialized by using the ``noinit``
keyword in place of an initialization expression. Since this variable
should be written at a later point in order to be read properly, it must
be a regular variable (``var``). It is incompatible with declarations
that require the variable to remain unchanged throughout the program’s
lifetime, such as ``const`` or ``param``. Additionally, its type must be
specified at declaration time.
The result of any read of this variable before it is written is
undefined; it exists and therefore can be accessed, but no guarantees
are made as to its contents.
.. _Local_Type_Inference:
Local Type Inference
~~~~~~~~~~~~~~~~~~~~
If the type is omitted from a variable declaration, the type of the
variable is defined to be the type of the initialization expression.
With the exception of sync and single expressions, the declaration
::
var v = e;
is equivalent to
::
var v: e.type = e;
for an arbitrary expression ``e``. If ``e`` is of sync or single type,
the type of ``v`` is the base type of ``e``.
.. _Multiple_Variable_Declarations:
Multiple Variable Declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
All variables defined in the same ``identifier-list`` are defined such
that they have the same type and value, and so that the type and
initialization expression are evaluated only once.
*Example (multiple.chpl)*.
In the declaration
::
proc g() { writeln("side effect"); return "a string"; }
var a, b = 1.0, c, d:int, e, f = g();
writeln((a,b,c,d,e,f));
variables ``a`` and ``b`` are of type ``real`` with value ``1.0``.
Variables ``c`` and ``d`` are of type ``int`` and are initialized to
the default value of ``0``. Variables ``e`` and ``f`` are of type
``string`` with value ``"a string"``. The string ``"side effect"``
has been written to the display once. It is not evaluated twice.
::
side effect
(1.0, 1.0, 0, 0, a string, a string)
The exact way that multiple variables are declared is defined as
follows:
- If the variables in the ``identifier-list`` are declared with a type,
but without an initialization expression as in
::
var v1, v2, v3: t;
for an arbitrary type expression ``t``, then the declarations are
rewritten so that the first variable is declared to be of type ``t``
and each later variable is declared to be of the type of the first
variable as in
::
var v1: t; var v2: v1.type; var v3: v1.type;
- If the variables in the ``identifier-list`` are declared without a
type, but with an initialization expression as in
::
var v1, v2, v3 = e;
for an arbitrary expression ``e``, then the declarations are
rewritten so that the first variable is initialized by expression
``e`` and each later variable is initialized by the first variable as
in
::
var v1 = e; var v2 = v1; var v3 = v1;
- If the variables in the ``identifier-list`` are declared with both a
type and an initialization expression as in
::
var v1, v2, v3: t = e;
for an arbitrary type expression ``t`` and an arbitrary expression
``e``, then the declarations are rewritten so that the first variable
is declared to be of type ``t`` and initialized by expression ``e``,
and each later variable is declared to be of the type of the first
variable and initialized by the result of calling the function
``readXX`` on the first variable as in
::
var v1: t = e; var v2: v1.type = readXX(v1); var v3: v1.type = readXX(v1);
where the function ``readXX`` is defined as follows:
::
proc readXX(x: sync) return x.readXX();
proc readXX(x: single) return x.readXX();
proc readXX(x) return x;
Note that the use of the helper function ``readXX()`` in this code
fragment is solely for the purposes of illustration. It is not
actually a part of Chapel’s semantics or implementation.
..
*Rationale*.
This algorithm is complicated by the existence of *sync* and *single*
variables. If these did not exist, we could rewrite any
multi-variable declaration such that later variables were simply
initialized by the first variable and the first variable was defined
as if it appeared alone in the ``identifier-list``. However, both
*sync* and *single* variables require careful handling to avoid
unintentional changes to their *full*/*empty* state.
.. _Module_Level_Variables:
Module Level Variables
----------------------
Variables declared in statements that are in a module but not in a
function or block within that module are module level variables. Module
level variables can be accessed anywhere within that module after the
declaration of that variable. If they are public, they can also be
accessed in other modules that use that module.
.. _Local_Variables:
Local Variables
---------------
Local variables are declared within block statements. They can only be
accessed within the scope of that block statement (including all inner
nested block statements and functions).
A local variable only exists during the execution of code that lies
within that block statement. This time is called the lifetime of the
variable. When execution has finished within that block statement, the
local variable and the storage it represents is removed. Variables of
class type are the sole exception. Initializers of class types create
storage that is not associated with any scope. Such storage can be
reclaimed as described in §\ `17.8 <#Class_Delete>`__.
.. _Constants:
Constants
---------
Constants are divided into two categories: parameters, specified with
the keyword ``param``, are compile-time constants and constants,
specified with the keyword ``const``, are runtime constants.
.. _Compile-Time_Constants:
Compile-Time Constants
~~~~~~~~~~~~~~~~~~~~~~
A compile-time constant, or “parameter”, must have a single value that
is known statically by the compiler. Parameters are restricted to
primitive and enumerated types.
Parameters can be assigned expressions that are parameter expressions.
Parameter expressions are restricted to the following constructs:
- Literals of primitive or enumerated type.
- Parenthesized parameter expressions.
- Casts of parameter expressions to primitive or enumerated types.
- Applications of the unary operators +@, -@, !@, and  @ on operands
that are bool or integral parameter expressions.
- Applications of the unary operators +@ and -@ on operands that are
real, imaginary or complex parameter expressions.
- Applications of the binary operators +@, -@, @, /@,
- Applications of the binary operators +@, -@, @, /@, \*@, ==@, !=@,
<=@, >=@, <@, and >@ on operands that are real, imaginary or complex
parameter expressions.
- Applications of the string concatenation operator +@, string
comparison operators ==@, !=@, <=@, >=@, <@, >@, and the string
length and byte methods on parameter string expressions.
- The conditional expression where the condition is a parameter and the
then- and else-expressions are parameters.
- Call expressions of parameter functions.
See §\ `13.7.4 <#Param_Return_Intent>`__.
.. _Runtime_Constants:
Runtime Constants
~~~~~~~~~~~~~~~~~
Runtime constants, or simply “constants”, do not have the restrictions
that are associated with parameters. Constants can be of any type.
Whether initialized explicitly or via its type’s default value, a
constant stores the same value throughout its lifetime.
A variable of a class type that is a constant is a constant reference.
That is, the variable always points to the object that it was
initialized to reference. However, the fields of that object are allowed
to be modified.
.. _Configuration_Variables:
Configuration Variables
-----------------------
If the keyword ``config`` precedes the keyword ``var``, ``const``, or
``param``, the variable, constant, or parameter is called a
configuration variable, configuration constant, or configuration
parameter respectively. Such variables, constants, and parameters must
be at the module level.
The initialization of these variables can be set via implementation
dependent means, such as command-line switches or environment variables.
The initialization expression in the program is ignored if the
initialization is alternatively set.
Configuration parameters are set at compilation time via compilation
flags or other implementation-defined means. The value passed via these
means can be an arbitrary Chapel expression as long as the expression
can be evaluated at compile-time. If present, the value thus supplied
overrides the default value appearing in the Chapel code.
*Example (config-param.chpl)*.
For example,
::
config param rank = 2;
::
writeln(rank);
::
2
sets an integer parameter ``rank`` to ``2``. At compile-time, this
default value of ``rank`` can be overridden with another parameter
expression, such as ``3`` or ``2*n``, provided ``n`` itself is a
parameter. The ``rank`` configuration variable can be used to write
rank-independent code.
.. _Ref_Variables:
Ref Variables
-------------
A *ref* variable is a variable declared using the ``ref`` keyword. A ref
variable serves as an alias to another variable, field or array element.
The declaration of a ref variable must contain ``initialization-part``,
which specifies what is to be aliased and can be a variable or any
lvalue expression.
Access or update to a ref variable is equivalent to access or update to
the variable being aliased. For example, an update to a ref variable is
visible via the original variable, and visa versa.
If the expression being aliased is a runtime constant variable, a formal
argument with a ``const ref`` concrete intent
(§`13.5.1 <#Concrete Intents>`__), or a call to a function with a
``const ref`` return intent (§`13.7.2 <#Const_Ref_Return_Intent>`__),
the corresponding ref variable must be declared as ``const ref``.
Parameter constants and expressions cannot be aliased.
*Open issue*.
The behavior of a ``const ref`` alias to a non-\ ``const`` variable
is an open issue. The options include disallowing such an alias,
disallowing changes to the variable while it can be accessed via a
``const ref`` alias, making changes visible through the alias, and
making the behavior undefined.
..
*Example (refVariables.chpl)*.
For example, the following code:
::
var myInt = 51;
ref refInt = myInt; // alias of a local or global variable
myInt = 62;
writeln("refInt = ", refInt);
refInt = 73;
writeln("myInt = ", myInt);
var myArr: [1..3] int = 51;
proc arrayElement(i) ref return myArr[i];
ref refToExpr = arrayElement(3); // alias to lvalue returned by a function
myArr[3] = 62;
writeln("refToExpr = ", refToExpr);
refToExpr = 73;
writeln("myArr[3] = ", myArr[3]);
const constArr: [1..3] int = 51..53;
const ref myConstRef = constArr[2]; // would be an error without 'const'
writeln("myConstRef = ", myConstRef);
prints out:
::
refInt = 62
myInt = 73
refToExpr = 62
myArr[3] = 73
myConstRef = 52
Conversions
===========
[Conversions]
A *conversion* converts an expression of one type to another type,
possibly changing its value. In certain cases noted below the source
expression can be a type expression. We refer to these two types the
*source* and *target* types. Conversions can be either
implicit (§\ `9.1 <#Implicit_Conversions>`__) or
explicit (§\ `9.2 <#Explicit_Conversions>`__).
.. _Implicit_Conversions:
Implicit Conversions
--------------------
An *implicit conversion* is a conversion that occurs implicitly, that
is, not due to an explicit specification in the program. Implicit
conversions occur at the locations in the program listed below. Each
location determines the target type. The source and target types of an
implicit conversion must be allowed. They determine whether and how the
expression’s value changes.
An implicit conversion occurs at each of the following program
locations:
- In an assignment, the expression on the right-hand side of the
assignment is converted to the type of the variable or another lvalue
on the left-hand side of the assignment.
- In a variable or field declaration, the initializing expression is
converted to the type of the variable or field. The initializing
expression is the r.h.s. of the ``=`` in the declaration, if present,
or in the field initialization statement in an initializer.
- The actual argument of a function call or an operator is converted to
the type of the corresponding formal argument, if the formal’s intent
is ``param``, ``in``, ``const in``, or an abstract intent
(§`13.5.2 <#Abstract_Intents>`__) with the semantics of ``in`` or
``const in``.
- The actual type argument of a function call or an operator is
converted to the corresponding formal argument of the ``type`` intent
or the ``this`` formal of a type method. See
§\ `9.1.4 <#Implicit_Type_Arg_Conversions>`__.
- If the formal argument’s intent is ``out``, the formal argument is
converted to the type of the corresponding actual argument upon
function return.
- The return or yield expression within a function without a ``ref``
return intent is converted to the return type of that function.
- The condition of a conditional expression, conditional statement,
while-do or do-while loop statement is converted to the boolean type.
See §\ `9.1.5 <#Implicit_Statement_Bool_Conversions>`__.
Implicit conversions are not applied for actual arguments passed to
``ref`` or ``const ref`` formal arguments.
Implicit conversions *are allowed* between the following source and
target types, as defined in the referenced subsections:
- numeric and boolean
types (§\ `9.1.1 <#Implicit_NumBool_Conversions>`__),
- numeric types in the special case when the expression’s value is a
compile-time
constant (§\ `9.1.2 <#Implicit_Compile_Time_Constant_Conversions>`__),
and
- class types (§\ `9.1.3 <#Implicit_Class_Conversions>`__),
- class and generic types in certain cases
(§`9.1.4 <#Implicit_Type_Arg_Conversions>`__)
- from an integral or class type to ``bool`` in certain
cases (§\ `9.1.5 <#Implicit_Statement_Bool_Conversions>`__).
- generic target types
(§`9.1.6 <#Implicit_Generic_Type_Conversions>`__)
In addition, an implicit conversion from a type to the same type is
allowed for any type. Such conversion does not change the value of the
expression.
Implicit conversion is not transitive. That is, if an implicit
conversion is allowed from type ``T1`` to ``T2`` and from ``T2`` to
``T3``, that by itself does not allow an implicit conversion from ``T1``
to ``T3``.
.. _Implicit_NumBool_Conversions:
Implicit Numeric and Bool Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implicit conversions among numeric types are allowed when all values
representable in the source type can also be represented in the target
type, retaining their full precision. In addition, implicit conversions
from types ``int(64)`` and ``uint(64)`` to types ``real(64)`` and
``complex(128)`` are allowed, even though they may result in a loss of
precision.
*Rationale*.
We allow these additional conversions because they are an important
convenience for application programmers. Therefore we are willing to
lose precision in these cases. The largest real and complex types are
chosen to retain precision as often as as possible.
Any boolean type can be implicitly converted to any other boolean type,
retaining the boolean value. Any boolean type can be implicitly
converted to any integral type by representing ``false`` as 0 and
``true`` as 1, except (if applicable) a boolean cannot be converted to
``int(1)``.
*Rationale*.
We disallow implicit conversion of a boolean to a real, imaginary, or
complex type because of the following. We expect that the cases where
such a conversion is needed will more likely be unintended by the
programmer. Marking those cases as errors will draw the programmer’s
attention. If such a conversion is actually desired, a cast
§\ `9.2 <#Explicit_Conversions>`__ can be inserted.
Legal implicit conversions with numeric and boolean types may thus be
tabulated as follows:
==================== ================= ================= ============================== ======================= ================= =========================
\
Source Type bool(\ :math:`t`) uint(\ :math:`t`) int(\ :math:`t`) real(\ :math:`t`) imag(\ :math:`t`) complex(\ :math:`t`)
\
bool(\ :math:`s`) all :math:`s,t` all :math:`s,t` all :math:`s`; :math:`2 \le t`
uint(\ :math:`s`) :math:`s \le t` :math:`s < t` :math:`s \le mant(t)` :math:`s \le mant(t/2)`
uint(64) real(64) complex(128)
int(\ :math:`s`) :math:`s \le t` :math:`s \le mant(t)+1` :math:`s \le mant(t/2)+1`
int(64) real(64) complex(128)
real(\ :math:`s`) :math:`s \le t` :math:`s \le t/2`
imag(\ :math:`s`) :math:`s \le t` :math:`s \le t/2`
complex(\ :math:`s`) :math:`s \le t`
==================== ================= ================= ============================== ======================= ================= =========================
Here, :math:`mant(i)` is the number of bits in the (unsigned) mantissa
of the :math:`i`-bit floating-point type. [1]_ Conversions for the
default integral and real types (``uint``, ``complex``, etc.) are the
same as for their explicitly-sized counterparts.
.. _Implicit_Compile_Time_Constant_Conversions:
Implicit Compile-Time Constant Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A parameter of numeric type can be implicitly converted to any other
numeric type if the value of the parameter can be represented exactly by
the target type. This rule does not allow conversions from ``real`` to
``imag``, or from ``complex`` to a non-complex type. It does allow
conversions from ``real`` or ``imag`` to ``complex``.
.. _Implicit_Class_Conversions:
Implicit Class Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~
An expression of class type can be implicitly converted to the borrow
type; to a nilable type; or to a parent class type. The value ``nil``
can be implicitly converted to any nilable class type.
First, class types can be converted to the corresponding ``borrowed``
type. For example, ``owned C`` can be implicitly converted to
``borrowed C``, and ``shared C?`` can be implicitly converted to
``borrowed C?``. This coercion is equivalent to calling the
``.borrow()`` method. See §\ `17.1.1 <#Class_Lifetime_and_Borrows>`__.
For example:
*Example (implicit-conversion-to-borrow.chpl)*.
::
class C { }
var c:owned C = new owned C();
proc f(arg: borrowed C) { }
f(c); // equivalent to f(c.borrow())
Second, an expression of non-nilable class type can be implicitly
converted to the nilable class type. Continuing the above example:
*Example (implicit-conversion-to-nilable.chpl)*.
::
class C { }
var c:owned C = new owned C();
::
var b:borrowed C = c.borrow();
proc g(arg: borrowed C?) { }
g(b); // equivalent to g(b:borrowed C?)
Third, an implicit conversion from class type ``D`` to another class
type ``C`` is allowed when ``D`` is a subclass of ``C``.
Any combination of these three conversions is allowed.
.. _Implicit_Type_Arg_Conversions:
Implicit Type Argument Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An implicit type argument conversion applies only when a type actual is
passed to a formal with the ``type`` intent. This includes the ``this``
formal of a type method. In this case, a subset of Implicit Class
Conversions (§`9.1.3 <#Implicit_Class_Conversions>`__) applies, in
addition to Implicit Conversions To Generic Types
(§`9.1.6 <#Implicit_Generic_Type_Conversions>`__).
*Future*.
The details are forthcoming.
.. _Implicit_Statement_Bool_Conversions:
Implicit Statement Bool Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the condition of an if-statement, while-loop, and do-while-loop, the
following implicit conversions to ``bool`` are supported:
- An expression of integral type is taken to be false if it is zero and
is true otherwise.
- An expression of a class type is taken to be false if it is nil and
is true otherwise.
.. _Implicit_Generic_Type_Conversions:
Implicit Conversions To Generic Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When the target type ``T`` is generic (§`24.3 <#Generic_Types>`__), an
implicit conversion is allowed when there is an instantiation of this
type such that an implicit conversion is allowed between the source type
and that instantiation by another rule in this section.
That instantiation is taken to be the instantiated type of the variable,
field, formal argument, or the return type whose declared type is the
generic type ``T``.
The conversions in this subsection apply when the source is either an
expression or a type expression.
.. _Explicit_Conversions:
Explicit Conversions
--------------------
Explicit conversions require a cast in the code. Casts are defined
in §\ `10.9 <#Casts>`__. Explicit conversions are supported between more
types than implicit conversions, but not between all types.
The explicit conversions are a superset of the implicit conversions. In
addition to the following definitions, an explicit conversion from a
type to the same type is allowed for any type. Such conversion does not
change the value of the expression.
.. _Explicit_Numeric_Conversions:
Explicit Numeric Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Explicit conversions are allowed from any numeric type or boolean to
bytes or string, and vice-versa.
When a ``bool`` is converted to a ``bool``, ``int`` or ``uint`` of equal
or larger size, its value is zero-extended to fit the new
representation. When a ``bool`` is converted to a smaller ``bool``,
``int`` or ``uint``, its most significant bits are truncated (as
appropriate) to fit the new representation.
When a ``int``, ``uint``, or ``real`` is converted to a ``bool``, the
result is ``false`` if the number was equal to 0 and ``true`` otherwise.
When an ``int`` is converted to a larger ``int`` or ``uint``, its value
is sign-extended to fit the new representation. When a ``uint`` is
converted to a larger ``int`` or ``uint``, its value is zero-extended.
When an ``int`` or ``uint`` is converted to an ``int`` or ``uint`` of
the same size, its binary representation is unchanged. When an ``int``
or ``uint`` is converted to a smaller ``int`` or ``uint``, its value is
truncated to fit the new representation.
*Future*.
There are several kinds of integer conversion which can result in a
loss of precision. Currently, the conversions are performed as
specified, and no error is reported. In the future, we intend to
improve type checking, so the user can be informed of potential
precision loss at compile time, and actual precision loss at run
time. Such cases include: When an ``int`` is converted to a ``uint``
and the original value is negative; When a ``uint`` is converted to
an ``int`` and the sign bit of the result is true; When an ``int`` is
converted to a smaller ``int`` or ``uint`` and any of the truncated
bits differs from the original sign bit; When a ``uint`` is converted
to a smaller ``int`` or ``uint`` and any of the truncated bits is
true;
..
*Rationale*.
For integer conversions, the default behavior of a program should be
to produce a run-time error if there is a loss of precision. Thus,
cast expressions not only give rise to a value conversion at run
time, but amount to an assertion that the required precision is
preserved. Explicit conversion procedures would be available in the
run-time library so that one can perform explicit conversions that
result in a loss of precision but do not generate a run-time
diagnostic.
When converting from a ``real`` type to a larger ``real`` type, the
represented value is preserved. When converting from a ``real`` type to
a smaller ``real`` type, the closest representation in the target type
is chosen. [2]_
When converting to a ``real`` type from an integer type, integer types
smaller than ``int`` are first converted to ``int``. Then, the closest
representation of the converted value in the target type is chosen. The
exact behavior of this conversion is implementation-defined.
When converting from ``real($k$)`` to ``complex($2k$)``, the original
value is copied into the real part of the result, and the imaginary part
of the result is set to zero. When converting from a ``real($k$)`` to a
``complex($\ell$)`` such that :math:`\ell > 2k`, the conversion is
performed as if the original value is first converted to
``real($\ell/2$)`` and then to ``$\ell$``.
The rules for converting from ``imag`` to ``complex`` are the same as
for converting from real, except that the imaginary part of the result
is set using the input value, and the real part of the result is set to
zero.
.. _Explicit_Tuple_to_Complex_Conversion:
Explicit Tuple to Complex Conversion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A two-tuple of numerical values may be converted to a ``complex`` value.
If the destination type is ``complex(128)``, each member of the
two-tuple must be convertible to ``real(64)``. If the destination type
is ``complex(64)``, each member of the two-tuple must be convertible to
``real(32)``. The first member of the tuple becomes the real part of the
resulting complex value; the second member of the tuple becomes the
imaginary part of the resulting complex value.
.. _Explicit_Enumeration_Conversions:
Explicit Enumeration Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Explicit conversions are allowed from any enumerated type to ``bytes``
or ``string`` and vice-versa, and include ``param`` conversions. For
enumerated types that are either concrete or semi-concrete
(§`7.2 <#Enumerated_Types>`__), conversions are supported between the
enum’s constants and any numeric type or ``bool``, including ``param``
conversions. For a semi-concrete enumerated type, if a conversion is
attempted involving a constant with no underlying integer value, it will
generate a compile-time error for a ``param`` conversion or an
execution-time error otherwise.
When the target type is an integer type, the value is first converted to
its underlying integer type and then to the target type, following the
rules above for converting between integers.
When the target type is a real, imaginary, or complex type, the value is
first converted to its underlying integer type and then to the target
type.
When the target type is ``bool``, the value is first converted to its
underlying integer type. If the result is zero, the value of the
``bool`` is ``false``; otherwise, it is ``true``.
When the target type is ``bytes`` or ``string``, the value becomes the
name of the enumerator.
When the source type is ``bool``, enumerators corresponding to the
values 0 and 1 in the underlying integer type are selected,
corresponding to input values of ``false`` and ``true``, respectively.
When the source type is a real or integer type, the value is converted
to the target type’s underlying integer type.
The conversion from ``complex`` and ``imag`` types to an enumerated type
is not permitted.
When the source type is ``bytes`` or ``string``, the enumerator whose
name matches value of the input is selected. If no such enumerator
exists, an ``IllegalArgumentError`` is thrown.
.. _Explicit_Class_Conversions:
Explicit Class Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~
An expression of static class type ``C`` can be explicitly converted to
a class type ``D`` provided that ``C`` is derived from ``D`` or ``D`` is
derived from ``C``.
When at run time the source expression refers to an instance of ``D`` or
it subclass, its value is not changed. Otherwise, the cast fails and the
result depends on whether or not the destination type is nilable. If the
cast fails and the destination type is not nilable, the cast expression
will throw a ``classCastError``. If the cast fails and the destination
type is nilable, as with ``D?``, then the result will be ``nil``.
An expression of class type can also be converted to a different
nilability with a cast. For conversions from a nilable class type to a
non-nilable class type, the cast will throw a ``NilClassError`` if the
value was actually ``nil``.
In some cases a new variant of a class type needs to be computed that
has different nilability or memory management strategy. Supposing that
``T`` represents a class type, then these casts may compute a new type:
- ``T:owned`` - new management is ``owned``, nilability from ``T``
- ``T:shared`` - new management ``shared``, nilability from ``T``
- ``T:borrowed`` - new management ``borrowed``, nilability from ``T``
- ``T:unmanaged`` - new management ``unmanaged``, nilability from ``T``
- ``T:class`` - non-nilable type with specific concrete or generic
management from ``T``
- ``T:class?`` - nilable type with specific concrete or generic
management from ``T``
- ``T:owned class`` - non-nilable type with ``owned`` management
- ``T:owned class?`` - nilable type with ``owned`` management
- ``T:shared class`` - non-nilable type with ``shared`` management
- ``T:shared class?`` - nilable type with ``shared`` management
- ``T:borrowed class`` - non-nilable type with ``borrowed`` management
- ``T:borrowed class?`` - nilable type with ``borrowed`` management
- ``T:unmanaged class`` - non-nilable type with ``unmanaged``
management
- ``T:unmanaged class?`` - nilable type with ``unmanaged`` management
The conversions in this subsection apply when the source is either an
expression or a type expression.
.. _Explicit_Range_Conversions:
Explicit Range Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~
An expression of stridable range type can be explicitly converted to an
unstridable range type, changing the stride to 1 in the process.
.. _Explicit_Domain_Conversions:
Explicit Domain Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
An expression of stridable domain type can be explicitly converted to an
unstridable domain type, changing all strides to 1 in the process.
.. _Explicit_String_to_Bytes_Conversions:
Explicit String to Bytes Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An expression of ``string`` type can be explicitly converted to a
``bytes``. However, the reverse is not possible as a ``bytes`` can
contain arbitrary bytes. Instead, ``bytes.decode()`` method should be
used to produce a ``string`` from a ``bytes``.
.. _Explicit_Type_to_String_Conversions:
Explicit Type to String Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A type expression can be explicitly converted to a ``string``. The
resultant ``string`` is the name of the type.
*Example (explicit-type-to-string.chpl)*.
For example:
::
var x: real(64) = 10.0;
writeln(x.type:string);
::
real(64)
This program will print out the string ``"real(64)"``.
Expressions
===========
[Expressions]
Chapel provides the following expressions:
::
expression:
literal-expression
nil-expression
variable-expression
enum-constant-expression
call-expression
type-expression
iteratable-call-expression
member-access-expression
new-expression
query-expression
cast-expression
lvalue-expression
parenthesized-expression
unary-expression
binary-expression
let-expression
if-expression
for-expression
forall-expression
reduce-expression
scan-expression
module-access-expression
tuple-expression
tuple-expand-expression
locale-access-expression
mapped-domain-expression
Individual expressions are defined in the remainder of this chapter and
additionally as follows:
- forall, reduce, and scan
§\ `[Data_Parallelism] <#Data_Parallelism>`__
- module access §\ `12.5.4 <#Explicit_Naming>`__
- tuple and tuple expand §\ `[Tuples] <#Tuples>`__
- locale access §\ `28.1.5 <#Querying_the_Locale_of_a_Variable>`__
- mapped domain §\ `[Domain_Maps] <#Domain_Maps>`__
- initializer calls §\ `17.3 <#Class_New>`__
- ``nil`` §\ `17.1.5 <#Class_nil_value>`__
.. _Literal_Expressions:
Literal Expressions
-------------------
A literal value for any of the predefined types is a literal expression.
Literal expressions are given by the following syntax:
::
literal-expression:
bool-literal
integer-literal
real-literal
imaginary-literal
string-literal
bytes-literal
range-literal
domain-literal
array-literal
Literal values for primitive types are described in
§\ `[Primitive_Type_Literals] <#Primitive_Type_Literals>`__. Literal
range values are described in §\ `20.3.1 <#Range_Literals>`__. Literal
tuple values are described in §\ `16.2 <#Tuple_Values>`__. Literal
values for domains are described in
§\ `21.2.1.2 <#Rectangular_Domain_Values>`__ and
§\ `21.2.2.2 <#Associative_Domain_Values>`__. Literal values for arrays
are described in §\ `22.2.1 <#Rectangular_Array_Literals>`__ and
§\ `22.2.2 <#Associative_Array_Literals>`__.
.. _Variable_Expressions:
Variable Expressions
--------------------
A use of a variable, constant, parameter, or formal argument, is itself
an expression. The syntax of a variable expression is given by:
::
variable-expression:
identifier
.. _Enumeration_Constant_Expression:
Enumeration Constant Expression
-------------------------------
A use of an enumeration constant is itself an expression. Such a
constant must be preceded by the enumeration type name. The syntax of an
enumeration constant expression is given by:
::
enum-constant-expression:
enum-type . identifier
For an example of using enumeration constants,
see §\ `7.2 <#Enumerated_Types>`__.
.. _Parenthesized_Expressions:
Parenthesized Expressions
-------------------------
A ``parenthesized-expression`` is an expression that is delimited by
parentheses as given by:
::
parenthesized-expression:
( expression )
Such an expression evaluates to the expression. The parentheses are
ignored and have only a syntactical effect.
.. _Call_Expressions:
Call Expressions
----------------
Functions and function calls are defined
in §\ `[Functions] <#Functions>`__.
.. _Indexing_Expressions:
Indexing Expressions
--------------------
Indexing, for example into arrays, tuples, and domains, has the same
syntax as a call expression.
Indexing is performed by an implicit invocation of the ``this`` method
on the value being indexed, passing the indices as the actual arguments.
.. _Member_Access_Expressions:
Member Access Expressions
-------------------------
Member access expressions provide access to a field or invoke a method
of an instance of a class, record, or union. They are defined in
§\ `17.5 <#Class_Field_Accesses>`__ and
§\ `17.6 <#Class_Method_Calls>`__, respectively.
::
member-access-expression:
field-access-expression
method-call-expression
.. _The_Query_Expression:
The Query Expression
--------------------
A query expression is used to query a type or value within a formal
argument type expression. The syntax of a query expression is given by:
::
query-expression:
? identifier[OPT]
Querying is restricted to querying the type of a formal argument, the
element type of a formal argument that is an array, the domain of a
formal argument that is an array, the size of a primitive type, or a
type or parameter field of a formal argument type.
The identifier can be omitted. This is useful for ensuring the
genericity of a generic type that defines default values for all of its
generic fields when specifying a formal argument as discussed
in §\ `24.1.5 <#Formal_Arguments_of_Generic_Type>`__.
*Example (query.chpl)*.
The following code defines a generic function where the type of the
first argument is queried and stored in the type alias ``t`` and the
domain of the second argument is queried and stored in the variable
``D``:
::
{ // }
::
proc foo(x: ?t, y: [?D] t) {
for i in D do
y[i] = x;
}
::
// {
var x = 1.5;
var y: [1..4] x.type;
foo(x, y);
writeln(y);
}
This allows a generic specification of assigning a particular value
to all elements of an array. The value and the elements of the array
are constrained to be the same type. This function can be rewritten
without query expression as follows:
::
{ // }
::
proc foo(x, y: [] x.type) {
for i in y.domain do
y[i] = x;
}
::
// {
var x = 1.5;
var y: [1..4] x.type;
foo(x, y);
writeln(y);
}
::
1.5 1.5 1.5 1.5
1.5 1.5 1.5 1.5
There is an expectation that query expressions will be allowed in more
places in the future.
.. _Casts:
Casts
-----
A cast is specified with the following syntax:
::
cast-expression:
expression : type-expression
The expression is converted to the specified type. A cast expression
invokes the corresponding explicit
conversion (§\ `9.2 <#Explicit_Conversions>`__). A resolution error
occurs if no such conversion exists.
.. _LValue_Expressions:
LValue Expressions
------------------
An *lvalue* is an expression that can be used on the left-hand side of
an assignment statement or on either side of a swap statement, that can
be passed to a formal argument of a function that has ``out``, ``inout``
or ``ref`` intent, or that can be returned by a function with a ``ref``
return intent (§\ `13.7.1 <#Ref_Return_Intent>`__). Valid lvalue
expressions include the following:
- Variable expressions.
- Member access expressions.
- Call expressions of functions with a ``ref`` return intent.
- Indexing expressions.
LValue expressions are given by the following syntax:
::
lvalue-expression:
variable-expression
member-access-expression
call-expression
parenthesized-expression
The syntax is less restrictive than the definition above. For example,
not all ``call-expression``\ s are lvalues.
.. _Operator_Precedence_and_Associativity:
Precedence and Associativity
----------------------------
| \|l|l|l\| **Operator** & **Associativity** & **Use**
| ``.`` & & member access
| ``()`` & & function call or access
| ``[]`` & & function call or access
| ``new`` & right & initializer call
| ``owned`` & right & apply management strategy to a class
| ``shared`` & &
| ``borrowed`` & &
| ``unmanaged`` & &
| postfix ``?`` & left & compute a nilable class type
| postfix ````! & & assert non-nilable and borrow
| ``:`` & left & cast
| ``**`` & right & exponentiation
| ``reduce`` & & reduction
| ``scan`` & & scan
| ``dmapped`` & & domain map application
| prefix ````! & & logical negation
| ``~`` & & bitwise negation
| ``*`` & & multiplication
| ``/`` & & division
| ``\%`` & & modulus
| unary ``+`` & & positive identity
| unary ``-`` & & negation
| ``<<`` & & left shift
| ``>>`` & & right shift
| ``&`` & left & bitwise/logical and
| ``^`` & left & bitwise/logical xor
| ``|`` & left & bitwise/logical or
| ``+`` & & addition
| ``-`` & & subtraction
| ``..`` & left & range initialization
| ``<=`` & & less-than-or-equal-to comparison
| ``>=`` & & greater-than-or-equal-to comparison
| ``<`` & & less-than comparison
| ``>`` & & greater-than comparison
| ``==`` & & equal-to comparison
| ````\ =! & & not-equal-to comparison
| ``&&`` & left & short-circuiting logical and
| ``||`` & left & short-circuiting logical or
| ``by`` & & range/domain stride application
| ``#`` & & range count application
| ``align`` & & range alignment
| ``in`` & left & forall expression
| ``if then else`` & & conditional expression
| ``forall do`` & & forall expression
| ``[ ]`` & & forall expression
| ``for do`` & & for expression
| ``sync single atomic`` & & sync, single and atomic type
| ``,`` & left & comma separated expressions
The above table summarizes operator and expression precedence and
associativity. Operators and expressions listed earlier have higher
precedence than those listed later.
*Rationale*.
In general, our operator precedence is based on that of the C family
of languages including C++, Java, Perl, and C#. We comment on a few
of the differences and unique factors here.
We find that there is tension between the relative precedence of
exponentiation, unary minus/plus, and casts. The following three
expressions show our intuition for how these expressions should be
parenthesized.
================== ===== ======================
``-2**4`` wants ``-(2**4)``
``-2:uint`` wants ``(-2):uint``
``2:uint**4:uint`` wants ``(2:uint)**(4:uint)``
================== ===== ======================
Trying to support all three of these cases results in a
circularity—exponentiation wants precedence over unary minus, unary
minus wants precedence over casts, and casts want precedence over
exponentiation. We chose to break the circularity by making unary
minus have a lower precedence. This means that for the second case
above:
=========== ======== =============
``-2:uint`` requires ``(-2):uint``
=========== ======== =============
We also chose to depart from the C family of languages by making
unary plus/minus have lower precedence than binary multiplication,
division, and modulus as in Fortran. We have found very few cases
that distinguish between these cases. An interesting one is:
================================
``const minint = min(int(32));``
``...-minint/2...``
================================
Intuitively, this should result in a positive value, yet C’s
precedence rules results in a negative value due to asymmetry in
modern integer representations. If we learn of cases that argue in
favor of the C approach, we would likely reverse this decision in
order to more closely match C.
We were tempted to diverge from the C precedence rules for the binary
bitwise operators to make them bind less tightly than comparisons.
This would allow us to interpret:
============== == ================
``a | b == 0`` as ``(a | b) == 0``
============== == ================
However, given that no other popular modern language has made this
change, we felt it unwise to stray from the pack. The typical
rationale for the C ordering is to allow these operators to be used
as non-short-circuiting logical operations.
In contrast to C, we give bitwise operations a higher precedence than
binary addition/subtraction and comparison operators. This enables
using the shift operators as shorthand for multiplication/division by
powers of 2, and also makes it easier to extract and test a bitmapped
field:
======================= == =====================
``(x \& MASK) == MASK`` as ``x \& MASK == MASK``
``a + b * pow(2,y)`` as ``a * b << y``
======================= == =====================
One final area of note is the precedence of reductions. Two common
cases tend to argue for making reductions very low or very high in
the precedence table:
=============================== ===== ===================================
``max reduce A - min reduce A`` wants ``(max reduce A) - (min reduce A)``
``max reduce A * B`` wants ``max reduce (A * B)``
=============================== ===== ===================================
The first statement would require reductions to have a higher
precedence than the arithmetic operators while the second would
require them to be lower. We opted to make reductions have high
precedence due to the argument that they tend to resemble unary
operators. Thus, to support our intuition:
==================== ======== ======================
``max reduce A * B`` requires ``max reduce (A * B)``
==================== ======== ======================
This choice also has the (arguably positive) effect of making the
unparenthesized version of this statement result in an aggregate
value if A and B are both aggregates—the reduction of A results in a
scalar which promotes when being multiplied by B, resulting in an
aggregate. Our intuition is that users who forget the parentheses
will learn of their error at compilation time because the resulting
expression is not a scalar as expected.
.. _Binary_Expressions:
Operator Expressions
--------------------
[Unary_Expressions]
The application of operators to expressions is itself an expression. The
syntax of a unary expression is given by:
::
unary-expression:
unary-operator expression
unary-operator: one of
+ - ~ !
The syntax of a binary expression is given by:
::
binary-expression:
expression binary-operator expression
binary-operator: one of
+ - * / % ** & | ^ << >> && || == != <= >= < > `by' #
The operators are defined in subsequent sections.
.. _Arithmetic_Operators:
Arithmetic Operators
--------------------
This section describes the predefined arithmetic operators. These
operators can be redefined over different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__).
For each operator, implicit conversions are applied to the operands of
an operator such that they are compatible with one of the function forms
listed, those listed earlier in the list being given preference. If no
compatible implicit conversions exist, then a compile-time error occurs.
In these cases, an explicit cast is required.
.. _Unary_Plus_Operators:
Unary Plus Operators
~~~~~~~~~~~~~~~~~~~~
The unary plus operators are predefined as follows:
::
proc +(a: int(8)): int(8)
proc +(a: int(16)): int(16)
proc +(a: int(32)): int(32)
proc +(a: int(64)): int(64)
proc +(a: uint(8)): uint(8)
proc +(a: uint(16)): uint(16)
proc +(a: uint(32)): uint(32)
proc +(a: uint(64)): uint(64)
proc +(a: real(32)): real(32)
proc +(a: real(64)): real(64)
proc +(a: imag(32)): imag(32)
proc +(a: imag(64)): imag(64)
proc +(a: complex(64)): complex(64)
proc +(a: complex(128)): complex(128)
For each of these definitions, the result is the value of the operand.
.. _Unary_Minus_Operators:
Unary Minus Operators
~~~~~~~~~~~~~~~~~~~~~
The unary minus operators are predefined as follows:
::
proc -(a: int(8)): int(8)
proc -(a: int(16)): int(16)
proc -(a: int(32)): int(32)
proc -(a: int(64)): int(64)
proc -(a: real(32)): real(32)
proc -(a: real(64)): real(64)
proc -(a: imag(32)): imag(32)
proc -(a: imag(64)): imag(64)
proc -(a: complex(64)): complex(64)
proc -(a: complex(128)): complex(128)
For each of these definitions that return a value, the result is the
negation of the value of the operand. For integral types, this
corresponds to subtracting the value from zero. For real and imaginary
types, this corresponds to inverting the sign. For complex types, this
corresponds to inverting the signs of both the real and imaginary parts.
It is an error to try to negate a value of type ``uint(64)``. Note that
negating a value of type ``uint(32)`` first converts the type to
``int(64)`` using an implicit conversion.
.. _Addition_Operators:
Addition Operators
~~~~~~~~~~~~~~~~~~
The addition operators are predefined as follows:
::
proc +(a: int(8), b: int(8)): int(8)
proc +(a: int(16), b: int(16)): int(16)
proc +(a: int(32), b: int(32)): int(32)
proc +(a: int(64), b: int(64)): int(64)
proc +(a: uint(8), b: uint(8)): uint(8)
proc +(a: uint(16), b: uint(16)): uint(16)
proc +(a: uint(32), b: uint(32)): uint(32)
proc +(a: uint(64), b: uint(64)): uint(64)
proc +(a: real(32), b: real(32)): real(32)
proc +(a: real(64), b: real(64)): real(64)
proc +(a: imag(32), b: imag(32)): imag(32)
proc +(a: imag(64), b: imag(64)): imag(64)
proc +(a: complex(64), b: complex(64)): complex(64)
proc +(a: complex(128), b: complex(128)): complex(128)
proc +(a: real(32), b: imag(32)): complex(64)
proc +(a: imag(32), b: real(32)): complex(64)
proc +(a: real(64), b: imag(64)): complex(128)
proc +(a: imag(64), b: real(64)): complex(128)
proc +(a: real(32), b: complex(64)): complex(64)
proc +(a: complex(64), b: real(32)): complex(64)
proc +(a: real(64), b: complex(128)): complex(128)
proc +(a: complex(128), b: real(64)): complex(128)
proc +(a: imag(32), b: complex(64)): complex(64)
proc +(a: complex(64), b: imag(32)): complex(64)
proc +(a: imag(64), b: complex(128)): complex(128)
proc +(a: complex(128), b: imag(64)): complex(128)
For each of these definitions that return a value, the result is the sum
of the two operands.
It is a compile-time error to add a value of type ``uint(64)`` and a
value of type ``int(64)``.
Addition over a value of real type and a value of imaginary type
produces a value of complex type. Addition of values of complex type and
either real or imaginary types also produces a value of complex type.
.. _Subtraction_Operators:
Subtraction Operators
~~~~~~~~~~~~~~~~~~~~~
The subtraction operators are predefined as follows:
::
proc -(a: int(8), b: int(8)): int(8)
proc -(a: int(16), b: int(16)): int(16)
proc -(a: int(32), b: int(32)): int(32)
proc -(a: int(64), b: int(64)): int(64)
proc -(a: uint(8), b: uint(8)): uint(8)
proc -(a: uint(16), b: uint(16)): uint(16)
proc -(a: uint(32), b: uint(32)): uint(32)
proc -(a: uint(64), b: uint(64)): uint(64)
proc -(a: real(32), b: real(32)): real(32)
proc -(a: real(64), b: real(64)): real(64)
proc -(a: imag(32), b: imag(32)): imag(32)
proc -(a: imag(64), b: imag(64)): imag(64)
proc -(a: complex(64), b: complex(64)): complex(64)
proc -(a: complex(128), b: complex(128)): complex(128)
proc -(a: real(32), b: imag(32)): complex(64)
proc -(a: imag(32), b: real(32)): complex(64)
proc -(a: real(64), b: imag(64)): complex(128)
proc -(a: imag(64), b: real(64)): complex(128)
proc -(a: real(32), b: complex(64)): complex(64)
proc -(a: complex(64), b: real(32)): complex(64)
proc -(a: real(64), b: complex(128)): complex(128)
proc -(a: complex(128), b: real(64)): complex(128)
proc -(a: imag(32), b: complex(64)): complex(64)
proc -(a: complex(64), b: imag(32)): complex(64)
proc -(a: imag(64), b: complex(128)): complex(128)
proc -(a: complex(128), b: imag(64)): complex(128)
For each of these definitions that return a value, the result is the
value obtained by subtracting the second operand from the first operand.
It is a compile-time error to subtract a value of type ``uint(64)`` from
a value of type ``int(64)``, and vice versa.
Subtraction of a value of real type from a value of imaginary type, and
vice versa, produces a value of complex type. Subtraction of values of
complex type from either real or imaginary types, and vice versa, also
produces a value of complex type.
.. _Multiplication_Operators:
Multiplication Operators
~~~~~~~~~~~~~~~~~~~~~~~~
The multiplication operators are predefined as follows:
::
proc *(a: int(8), b: int(8)): int(8)
proc *(a: int(16), b: int(16)): int(16)
proc *(a: int(32), b: int(32)): int(32)
proc *(a: int(64), b: int(64)): int(64)
proc *(a: uint(8), b: uint(8)): uint(8)
proc *(a: uint(16), b: uint(16)): uint(16)
proc *(a: uint(32), b: uint(32)): uint(32)
proc *(a: uint(64), b: uint(64)): uint(64)
proc *(a: real(32), b: real(32)): real(32)
proc *(a: real(64), b: real(64)): real(64)
proc *(a: imag(32), b: imag(32)): real(32)
proc *(a: imag(64), b: imag(64)): real(64)
proc *(a: complex(64), b: complex(64)): complex(64)
proc *(a: complex(128), b: complex(128)): complex(128)
proc *(a: real(32), b: imag(32)): imag(32)
proc *(a: imag(32), b: real(32)): imag(32)
proc *(a: real(64), b: imag(64)): imag(64)
proc *(a: imag(64), b: real(64)): imag(64)
proc *(a: real(32), b: complex(64)): complex(64)
proc *(a: complex(64), b: real(32)): complex(64)
proc *(a: real(64), b: complex(128)): complex(128)
proc *(a: complex(128), b: real(64)): complex(128)
proc *(a: imag(32), b: complex(64)): complex(64)
proc *(a: complex(64), b: imag(32)): complex(64)
proc *(a: imag(64), b: complex(128)): complex(128)
proc *(a: complex(128), b: imag(64)): complex(128)
For each of these definitions that return a value, the result is the
product of the two operands.
It is a compile-time error to multiply a value of type ``uint(64)`` and
a value of type ``int(64)``.
Multiplication of values of imaginary type produces a value of real
type. Multiplication over a value of real type and a value of imaginary
type produces a value of imaginary type. Multiplication of values of
complex type and either real or imaginary types produces a value of
complex type.
.. _Division_Operators:
Division Operators
~~~~~~~~~~~~~~~~~~
The division operators are predefined as follows:
::
proc /(a: int(8), b: int(8)): int(8)
proc /(a: int(16), b: int(16)): int(16)
proc /(a: int(32), b: int(32)): int(32)
proc /(a: int(64), b: int(64)): int(64)
proc /(a: uint(8), b: uint(8)): uint(8)
proc /(a: uint(16), b: uint(16)): uint(16)
proc /(a: uint(32), b: uint(32)): uint(32)
proc /(a: uint(64), b: uint(64)): uint(64)
proc /(a: real(32), b: real(32)): real(32)
proc /(a: real(64), b: real(64)): real(64)
proc /(a: imag(32), b: imag(32)): real(32)
proc /(a: imag(64), b: imag(64)): real(64)
proc /(a: complex(64), b: complex(64)): complex(64)
proc /(a: complex(128), b: complex(128)): complex(128)
proc /(a: real(32), b: imag(32)): imag(32)
proc /(a: imag(32), b: real(32)): imag(32)
proc /(a: real(64), b: imag(64)): imag(64)
proc /(a: imag(64), b: real(64)): imag(64)
proc /(a: real(32), b: complex(64)): complex(64)
proc /(a: complex(64), b: real(32)): complex(64)
proc /(a: real(64), b: complex(128)): complex(128)
proc /(a: complex(128), b: real(64)): complex(128)
proc /(a: imag(32), b: complex(64)): complex(64)
proc /(a: complex(64), b: imag(32)): complex(64)
proc /(a: imag(64), b: complex(128)): complex(128)
proc /(a: complex(128), b: imag(64)): complex(128)
For each of these definitions that return a value, the result is the
quotient of the two operands.
It is a compile-time error to divide a value of type ``uint(64)`` by a
value of type ``int(64)``, and vice versa.
Division of values of imaginary type produces a value of real type.
Division over a value of real type and a value of imaginary type
produces a value of imaginary type. Division of values of complex type
and either real or imaginary types produces a value of complex type.
When the operands are integers, the result (quotient) is also an
integer. If ``b`` does not divide ``a`` exactly, then there are two
candidate quotients :math:`q1` and :math:`q2` such that :math:`b * q1`
and :math:`b * q2` are the two multiples of ``b`` closest to ``a``. The
integer result :math:`q` is the candidate quotient which lies closest to
zero.
.. _Modulus_Operators:
Modulus Operators
~~~~~~~~~~~~~~~~~
The modulus operators are predefined as follows:
::
proc %(a: int(8), b: int(8)): int(8)
proc %(a: int(16), b: int(16)): int(16)
proc %(a: int(32), b: int(32)): int(32)
proc %(a: int(64), b: int(64)): int(64)
proc %(a: uint(8), b: uint(8)): uint(8)
proc %(a: uint(16), b: uint(16)): uint(16)
proc %(a: uint(32), b: uint(32)): uint(32)
proc %(a: uint(64), b: uint(64)): uint(64)
For each of these definitions that return a value, the result is the
remainder when the first operand is divided by the second operand.
The sign of the result is the same as the sign of the dividend ``a``,
and the magnitude of the result is always smaller than that of the
divisor ``b``. For integer operands, the ``\%`` and ``/`` operators are
related by the following identity:
::
var q = a / b;
var r = a % b;
writeln(q * b + r == a); // true
It is a compile-time error to take the remainder of a value of type
``uint(64)`` and a value of type ``int(64)``, and vice versa.
There is an expectation that the predefined modulus operators will be
extended to handle real, imaginary, and complex types in the future.
.. _Exponentiation_Operators:
Exponentiation Operators
~~~~~~~~~~~~~~~~~~~~~~~~
The exponentiation operators are predefined as follows:
::
proc **(a: int(8), b: int(8)): int(8)
proc **(a: int(16), b: int(16)): int(16)
proc **(a: int(32), b: int(32)): int(32)
proc **(a: int(64), b: int(64)): int(64)
proc **(a: uint(8), b: uint(8)): uint(8)
proc **(a: uint(16), b: uint(16)): uint(16)
proc **(a: uint(32), b: uint(32)): uint(32)
proc **(a: uint(64), b: uint(64)): uint(64)
proc **(a: real(32), b: real(32)): real(32)
proc **(a: real(64), b: real(64)): real(64)
For each of these definitions that return a value, the result is the
value of the first operand raised to the power of the second operand.
It is a compile-time error to take the exponent of a value of type
``uint(64)`` by a value of type ``int(64)``, and vice versa.
There is an expectation that the predefined exponentiation operators
will be extended to handle imaginary and complex types in the future.
.. _Bitwise_Operators:
Bitwise Operators
-----------------
This section describes the predefined bitwise operators. These operators
can be redefined over different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__).
.. _Bitwise_Complement_Operators:
Bitwise Complement Operators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The bitwise complement operators are predefined as follows:
::
proc ~(a: int(8)): int(8)
proc ~(a: int(16)): int(16)
proc ~(a: int(32)): int(32)
proc ~(a: int(64)): int(64)
proc ~(a: uint(8)): uint(8)
proc ~(a: uint(16)): uint(16)
proc ~(a: uint(32)): uint(32)
proc ~(a: uint(64)): uint(64)
For each of these definitions, the result is the bitwise complement of
the operand.
.. _Bitwise_And_Operators:
Bitwise And Operators
~~~~~~~~~~~~~~~~~~~~~
The bitwise and operators are predefined as follows:
::
proc &(a: bool, b: bool): bool
proc &(a: int(?w), b: int(w)): int(w)
proc &(a: uint(?w), b: uint(w)): uint(w)
proc &(a: int(?w), b: uint(w)): uint(w)
proc &(a: uint(?w), b: int(w)): uint(w)
For each of these definitions, the result is computed by applying the
logical and operation to the bits of the operands.
Chapel allows mixing signed and unsigned integers of the same size when
passing them as arguments to bitwise and. In the mixed case the result
is of the same size as the arguments and is unsigned. No run-time error
is issued, even if the apparent sign changes as the required conversions
are performed.
*Rationale*.
The mathematical meaning of integer arguments is discarded when they
are passed to bitwise operators. Instead the arguments are treated
simply as bit vectors. The bit-vector meaning is preserved when
converting between signed and unsigned of the same size. The choice
of unsigned over signed as the result type in the mixed case reflects
the semantics of standard C.
.. _Bitwise_Or_Operators:
Bitwise Or Operators
~~~~~~~~~~~~~~~~~~~~
The bitwise or operators are predefined as follows:
::
proc |(a: bool, b: bool): bool
proc |(a: int(?w), b: int(w)): int(w)
proc |(a: uint(?w), b: uint(w)): uint(w)
proc |(a: int(?w), b: uint(w)): uint(w)
proc |(a: uint(?w), b: int(w)): uint(w)
For each of these definitions, the result is computed by applying the
logical or operation to the bits of the operands. Chapel allows mixing
signed and unsigned integers of the same size when passing them as
arguments to bitwise or. No run-time error is issued, even if the
apparent sign changes as the required conversions are performed.
*Rationale*.
The same as for bitwise and (§`10.14.2 <#Bitwise_And_Operators>`__).
.. _Bitwise_Xor_Operators:
Bitwise Xor Operators
~~~~~~~~~~~~~~~~~~~~~
The bitwise xor operators are predefined as follows:
::
proc ^(a: bool, b: bool): bool
proc ^(a: int(?w), b: int(w)): int(w)
proc ^(a: uint(?w), b: uint(w)): uint(w)
proc ^(a: int(?w), b: uint(w)): uint(w)
proc ^(a: uint(?w), b: int(w)): uint(w)
For each of these definitions, the result is computed by applying the
XOR operation to the bits of the operands. Chapel allows mixing signed
and unsigned integers of the same size when passing them as arguments to
bitwise xor. No run-time error is issued, even if the apparent sign
changes as the required conversions are performed.
*Rationale*.
The same as for bitwise and (§`10.14.2 <#Bitwise_And_Operators>`__).
.. _Shift_Operators:
Shift Operators
---------------
This section describes the predefined shift operators. These operators
can be redefined over different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__).
The shift operators are predefined as follows:
::
proc <<(a: int(8), b): int(8)
proc <<(a: int(16), b): int(16)
proc <<(a: int(32), b): int(32)
proc <<(a: int(64), b): int(64)
proc <<(a: uint(8), b): uint(8)
proc <<(a: uint(16), b): uint(16)
proc <<(a: uint(32), b): uint(32)
proc <<(a: uint(64), b): uint(64)
proc >>(a: int(8), b): int(8)
proc >>(a: int(16), b): int(16)
proc >>(a: int(32), b): int(32)
proc >>(a: int(64), b): int(64)
proc >>(a: uint(8), b): uint(8)
proc >>(a: uint(16), b): uint(16)
proc >>(a: uint(32), b): uint(32)
proc >>(a: uint(64), b): uint(64)
The type of the second actual argument must be any integral type.
The ``<<`` operator shifts the bits of ``a`` left by the integer ``b``.
The new low-order bits are set to zero.
The ``>>`` operator shifts the bits of ``a`` right by the integer ``b``.
When ``a`` is negative, the new high-order bits are set to one;
otherwise the new high-order bits are set to zero.
The value of ``b`` must be non-negative.
.. _Logical_Operators:
Logical Operators
-----------------
This section describes the predefined logical operators. These operators
can be redefined over different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__).
.. _Logical_Negation_Operators:
The Logical Negation Operator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The logical negation operator is predefined for booleans and integers as
follows:
::
proc !(a: bool): bool
proc !(a: int(?w)): bool
proc !(a: uint(?w)): bool
For the boolean form, the result is the logical negation of the operand.
For the integer forms, the result is true if the operand is zero and
false otherwise.
.. _Logical_And_Operators:
The Logical And Operator
~~~~~~~~~~~~~~~~~~~~~~~~
The logical and operator is predefined over bool type. It returns true
if both operands evaluate to true; otherwise it returns false. If the
first operand evaluates to false, the second operand is not evaluated
and the result is false.
The logical and operator over expressions ``a`` and ``b`` given by
::
a && b
is evaluated as the expression
::
if isTrue(a) then isTrue(b) else false
The function ``isTrue`` is predefined over bool type as follows:
::
proc isTrue(a:bool) return a;
Overloading the logical and operator over other types is accomplished by
overloading the ``isTrue`` function over other types.
.. _Logical_Or_Operators:
The Logical Or Operator
~~~~~~~~~~~~~~~~~~~~~~~
The logical or operator is predefined over bool type. It returns true if
either operand evaluate to true; otherwise it returns false. If the
first operand evaluates to true, the second operand is not evaluated and
the result is true.
The logical or operator over expressions ``a`` and ``b`` given by
::
a || b
is evaluated as the expression
::
if isTrue(a) then true else isTrue(b)
The function ``isTrue`` is predefined over bool type as described
in §\ `10.16.2 <#Logical_And_Operators>`__. Overloading the logical or
operator over other types is accomplished by overloading the ``isTrue``
function over other types.
.. _Relational_Operators:
Relational Operators
--------------------
This section describes the predefined relational operators. These
operators can be redefined over different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__).
.. _Ordered_Comparison_Operators:
Ordered Comparison Operators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The “less than” comparison operators are predefined over numeric types
as follows:
::
proc <(a: int(8), b: int(8)): bool
proc <(a: int(16), b: int(16)): bool
proc <(a: int(32), b: int(32)): bool
proc <(a: int(64), b: int(64)): bool
proc <(a: uint(8), b: uint(8)): bool
proc <(a: uint(16), b: uint(16)): bool
proc <(a: uint(32), b: uint(32)): bool
proc <(a: uint(64), b: uint(64)): bool
proc <(a: int(64), b: uint(64)): bool
proc <(a: uint(64), b: int(64)): bool
proc <(a: real(32), b: real(32)): bool
proc <(a: real(64), b: real(64)): bool
The result of ``a < b`` is true if ``a`` is less than ``b``; otherwise
the result is false.
The “greater than” comparison operators are predefined over numeric
types as follows:
::
proc >(a: int(8), b: int(8)): bool
proc >(a: int(16), b: int(16)): bool
proc >(a: int(32), b: int(32)): bool
proc >(a: int(64), b: int(64)): bool
proc >(a: uint(8), b: uint(8)): bool
proc >(a: uint(16), b: uint(16)): bool
proc >(a: uint(32), b: uint(32)): bool
proc >(a: uint(64), b: uint(64)): bool
proc >(a: int(64), b: uint(64)): bool
proc >(a: uint(64), b: int(64)): bool
proc >(a: real(32), b: real(32)): bool
proc >(a: real(64), b: real(64)): bool
The result of ``a > b`` is true if ``a`` is greater than ``b``;
otherwise the result is false.
The “less than or equal to” comparison operators are predefined over
numeric types as follows:
::
proc <=(a: int(8), b: int(8)): bool
proc <=(a: int(16), b: int(16)): bool
proc <=(a: int(32), b: int(32)): bool
proc <=(a: int(64), b: int(64)): bool
proc <=(a: uint(8), b: uint(8)): bool
proc <=(a: uint(16), b: uint(16)): bool
proc <=(a: uint(32), b: uint(32)): bool
proc <=(a: uint(64), b: uint(64)): bool
proc <=(a: int(64), b: uint(64)): bool
proc <=(a: uint(64), b: int(64)): bool
proc <=(a: real(32), b: real(32)): bool
proc <=(a: real(64), b: real(64)): bool
The result of ``a <= b`` is true if ``a`` is less than or equal to
``b``; otherwise the result is false.
The “greater than or equal to” comparison operators are predefined over
numeric types as follows:
::
proc >=(a: int(8), b: int(8)): bool
proc >=(a: int(16), b: int(16)): bool
proc >=(a: int(32), b: int(32)): bool
proc >=(a: int(64), b: int(64)): bool
proc >=(a: uint(8), b: uint(8)): bool
proc >=(a: uint(16), b: uint(16)): bool
proc >=(a: uint(32), b: uint(32)): bool
proc >=(a: uint(64), b: uint(64)): bool
proc >=(a: int(64), b: uint(64)): bool
proc >=(a: uint(64), b: int(64)): bool
proc >=(a: real(32), b: real(32)): bool
proc >=(a: real(64), b: real(64)): bool
The result of ``a >= b`` is true if ``a`` is greater than or equal to
``b``; otherwise the result is false.
The ordered comparison operators are predefined over strings as follows:
::
proc <(a: string, b: string): bool
proc >(a: string, b: string): bool
proc <=(a: string, b: string): bool
proc >=(a: string, b: string): bool
Comparisons between strings are defined based on the ordering of the
character set used to represent the string, which is applied elementwise
to the string’s characters in order.
.. _Equality_Comparison_Operators:
Equality Comparison Operators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The equality comparison operators ``==`` and ``\``\ =! are predefined
over bool and the numeric types as follows:
::
proc ==(a: int(8), b: int(8)): bool
proc ==(a: int(16), b: int(16)): bool
proc ==(a: int(32), b: int(32)): bool
proc ==(a: int(64), b: int(64)): bool
proc ==(a: uint(8), b: uint(8)): bool
proc ==(a: uint(16), b: uint(16)): bool
proc ==(a: uint(32), b: uint(32)): bool
proc ==(a: uint(64), b: uint(64)): bool
proc ==(a: int(64), b: uint(64)): bool
proc ==(a: uint(64), b: int(64)): bool
proc ==(a: real(32), b: real(32)): bool
proc ==(a: real(64), b: real(64)): bool
proc ==(a: imag(32), b: imag(32)): bool
proc ==(a: imag(64), b: imag(64)): bool
proc ==(a: complex(64), b: complex(64)): bool
proc ==(a: complex(128), b: complex(128)): bool
proc !=(a: int(8), b: int(8)): bool
proc !=(a: int(16), b: int(16)): bool
proc !=(a: int(32), b: int(32)): bool
proc !=(a: int(64), b: int(64)): bool
proc !=(a: uint(8), b: uint(8)): bool
proc !=(a: uint(16), b: uint(16)): bool
proc !=(a: uint(32), b: uint(32)): bool
proc !=(a: uint(64), b: uint(64)): bool
proc !=(a: int(64), b: uint(64)): bool
proc !=(a: uint(64), b: int(64)): bool
proc !=(a: real(32), b: real(32)): bool
proc !=(a: real(64), b: real(64)): bool
proc !=(a: imag(32), b: imag(32)): bool
proc !=(a: imag(64), b: imag(64)): bool
proc !=(a: complex(64), b: complex(64)): bool
proc !=(a: complex(128), b: complex(128)): bool
The result of ``a == b`` is true if ``a`` and ``b`` contain the same
value; otherwise the result is false. The result of ``a \``\ = b! is
equivalent to ``\``\ (a == b)!.
The equality comparison operators are predefined over classes as
follows:
::
proc ==(a: object, b: object): bool
proc !=(a: object, b: object): bool
The result of ``a == b`` is true if ``a`` and ``b`` reference the same
storage location; otherwise the result is false. The result of
``a \``\ = b! is equivalent to ``\``\ (a == b)!.
Default equality comparison operators are generated for records if the
user does not define them. These operators are described
in §\ `18.6.3 <#Record_Comparison_Operators>`__.
The equality comparison operators are predefined over strings as
follows:
::
proc ==(a: string, b: string): bool
proc !=(a: string, b: string): bool
The result of ``a == b`` is true if the sequence of characters in ``a``
matches exactly the sequence of characters in ``b``; otherwise the
result is false. The result of ``a \``\ = b! is equivalent to ``\``\ (a
== b)!.
.. _Class_Operators:
Class Operators
---------------
The keywords ``owned``, ``shared``, ``borrowed``, and ``unmanaged`` act
as a prefix unary operator when specifying the management strategy for a
class type. See §\ `17.1.2 <#Class_Types>`__.
The unary postfix operator ``?`` results in the nilable variant of a
class type. See §\ `17.1.3 <#Nilable_Classes>`__.
The unary postfix operator ````! asserts that the receiver is not
storing ``nil`` and borrows from it.
See §\ `17.1.3 <#Nilable_Classes>`__.
.. _Miscellaneous_Operators:
Miscellaneous Operators
-----------------------
This section describes several miscellaneous operators. These operators
can be redefined over different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__).
.. _The_String_Concatenation_Operator:
The String Concatenation Operator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The string concatenation operator ``+`` is predefined for string
arguments and returns a new string that is the concatenation of its
arguments:
::
proc +(s0: string, s1: string): string
..
*Example (string-concat.chpl)*.
The code:
::
var x: string = "hi";
var y: string = " there";
var z = x + y;
::
writeln(z);
::
hi there
will cause ``z`` to be a new string containing the value
``"hi there"``.
.. _The_By_Operator:
The By Operator
~~~~~~~~~~~~~~~
The operator ``by`` is predefined on ranges and rectangular domains. It
is described in §\ `20.5.1 <#By_Operator_For_Ranges>`__ for ranges
and §\ `21.8.2 <#Domain_Striding>`__ for domains.
.. _The_Align_Operator:
The Align Operator
~~~~~~~~~~~~~~~~~~
The operator ``align`` is predefined on ranges and rectangular domains.
It is described in §\ `20.5.2 <#Align_Operator_For_Ranges>`__ for ranges
and §\ `21.8.3 <#Domain_Alignment>`__ for domains.
.. _The_Range_Count_Operator:
The Range Count Operator
~~~~~~~~~~~~~~~~~~~~~~~~
The operator ``\#`` is predefined on ranges. It is described in
 §`20.5.3 <#Count_Operator>`__.
.. _Let_Expressions:
Let Expressions
---------------
A let expression allows variables to be declared at the expression level
and used within that expression. The syntax of a let expression is given
by:
::
let-expression:
`let' variable-declaration-list `in' expression
The scope of the variables is the let-expression.
*Example (let.chpl)*.
Let expressions are useful for defining variables in the context of
an expression. In the code
::
var a = 4;
var b = 5;
var l =
::
let x: real = a*b, y = x*x in 1/y
the value determined by ``a*b`` is computed and converted to type
real if it is not already a real. The square of the real is then
stored in ``y`` and the result of the expression is the reciprocal of
that value.
::
;
writeln(l);
::
0.0025
.. _Conditional_Expressions:
Conditional Expressions
-----------------------
A conditional expression is given by the following syntax:
::
if-expression:
`if' expression `then' expression `else' expression
`if' expression `then' expression
The conditional expression is evaluated in two steps. First, the
expression following the ``if`` keyword is evaluated. Then, if the
expression evaluated to true, the expression following the ``then``
keyword is evaluated and taken to be the value of this expression.
Otherwise, the expression following the ``else`` keyword is evaluated
and taken to be the value of this expression. In both cases, the
unselected expression is not evaluated.
The ‘else’ clause can be omitted only when the conditional expression is
nested immediately inside a for or forall expression. Such an expression
is used to filter predicates as described
in §\ `10.22.1 <#Filtering_Predicates_For>`__
and §\ `27.2.4 <#Filtering_Predicates_Forall>`__, respectively.
*Example (condexp.chpl)*.
This example shows how if-then-else can be used in a context in which
an expression is expected. The code
::
writehalf(8);
writehalf(21);
writehalf(1000);
proc writehalf(i: int) {
var half = if (i % 2) then i/2 +1 else i/2;
writeln("Half of ",i," is ",half);
}
produces the output
::
Half of 8 is 4
Half of 21 is 11
Half of 1000 is 500
.. _For_Expressions:
For Expressions
---------------
A for expression is given by the following syntax:
::
for-expression:
`for' index-var-declaration `in' iteratable-expression `do' expression
`for' iteratable-expression `do' expression
A for expression is an iterator that executes a for loop
(§`11.9 <#The_For_Loop>`__), evaluates the body expression on each
iteration of the loop, and yields each resulting value.
When a for expression is used to initialize a variable, such as
::
var X = for iterableExpression() do computeValue();
the variable will be inferred to have an array type. The array’s domain
is defined by the ``iterable-expression`` following the same rules as
for promotion, both in the regular case §\ `27.5 <#Promotion>`__ and in
the zipper case §\ `27.5.2 <#Zipper_Promotion>`__.
.. _Filtering_Predicates_For:
Filtering Predicates in For Expressions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A conditional expression that is immediately enclosed in a for
expression and does not require an else clause filters the iterations of
the for expression. The iterations for which the condition does not hold
are not reflected in the result of the for expression.
When a for expression with a filtering predicate is captured into a
variable, the resulting array has a 1-based one-dimensional domain.
*Example (yieldPredicates.chpl)*.
The code
::
var A = for i in 1..10 do if i % 3 != 0 then i;
::
writeln(A);
::
1 2 4 5 7 8 10
declares an array A that is initialized to the integers between 1 and
10 that are not divisible by 3.
Statements
==========
[Statements]
Chapel is an imperative language with statements that may have side
effects. Statements allow for the sequencing of program execution.
Chapel provides the following statements:
::
statement:
block-statement
expression-statement
assignment-statement
swap-statement
io-statement
conditional-statement
select-statement
while-do-statement
do-while-statement
for-statement
label-statement
break-statement
continue-statement
param-for-statement
use-statement
defer-statement
empty-statement
return-statement
yield-statement
module-declaration-statement
procedure-declaration-statement
external-procedure-declaration-statement
exported-procedure-declaration-statement
iterator-declaration-statement
method-declaration-statement
type-declaration-statement
variable-declaration-statement
remote-variable-declaration-statement
on-statement
cobegin-statement
coforall-statement
begin-statement
sync-statement
serial-statement
atomic-statement
forall-statement
delete-statement
Individual statements are defined in the remainder of this chapter and
additionally as follows:
- return §\ `13.8 <#The_Return_Statement>`__
- yield §\ `23.2 <#The_Yield_Statement>`__
- module declaration §\ `[Modules] <#Modules>`__
- procedure declaration §\ `13.2 <#Function_Definitions>`__
- external procedure declaration
§\ `32.1.1 <#Calling_External_Functions>`__
- exporting procedure declaration
§\ `32.1.2 <#Calling_Chapel_Functions>`__
- iterator declaration §\ `23.1 <#Iterator_Function_Definitions>`__
- method declaration §\ `17.1.7 <#Class_Methods>`__
- type declaration §\ `[Types] <#Types>`__
- variable declaration §\ `8.1 <#Variable_Declarations>`__
- remote variable declaration
 §`28.2.1 <#remote_variable_declarations>`__
- ``on`` statement §\ `28.2 <#On>`__
- cobegin, coforall, begin, sync, serial and atomic statements
§\ `[Task_Parallelism_and_Synchronization] <#Task_Parallelism_and_Synchronization>`__
- forall §\ `[Data_Parallelism] <#Data_Parallelism>`__
- delete §\ `17.8 <#Class_Delete>`__
.. _Blocks:
Blocks
------
A block is a statement or a possibly empty list of statements that form
their own scope. A block is given by
::
block-statement:
{ statements[OPT] }
statements:
statement
statement statements
Variables defined within a block are local
variables (§\ `8.3 <#Local_Variables>`__).
The statements within a block are executed serially unless the block is
in a cobegin statement (§\ `26.5 <#Cobegin>`__).
.. _Expression_Statements:
Expression Statements
---------------------
The expression statement evaluates an expression solely for side
effects. The syntax for an expression statement is given by
::
expression-statement:
variable-expression ;
member-access-expression ;
call-expression ;
new-expression ;
let-expression ;
.. _Assignment_Statements:
Assignment Statements
---------------------
An assignment statement assigns the value of an expression to another
expression, for example, a variable. Assignment statements are given by
::
assignment-statement:
lvalue-expression assignment-operator expression
assignment-operator: one of
= += -= *= /= %= **= &= |= ^= &&= ||= <<= >>=
The assignment operators that contain a binary operator symbol as a
prefix are *compound assignment* operators. The remaining assignment
operator ``=`` is called *simple assignment*.
The expression on the left-hand side of the assignment operator must be
a valid lvalue (§\ `10.10 <#LValue_Expressions>`__). It is evaluated
before the expression on the right-hand side of the assignment operator,
which can be any expression.
When the left-hand side is of a numerical type, there is an implicit
conversion (§\ `9.1 <#Implicit_Conversions>`__) of the right-hand side
expression to the type of the left-hand side expression. Additionally,
for simple assignment, if the left-hand side is of Boolean type, the
right-hand side is implicitly converted to the type of the left-hand
side (i.e. a ``bool(?w)`` with the same width ``w``).
For simple assignment, the validity and semantics of assigning between
classes (§\ `17.7.1 <#Class_Assignment>`__),
records (§\ `18.6.2 <#Record_Assignment>`__),
unions (§\ `19.3 <#Union_Assignment>`__),
tuples (§\ `16.5 <#Tuple_Assignment>`__),
ranges (§\ `20.4.1 <#Range_Assignment>`__),
domains (§\ `21.8.1 <#Domain_Assignment>`__), and
arrays (§\ `22.5 <#Array_Assignment>`__) are discussed in these later
sections.
A compound assignment is shorthand for applying the binary operator to
the left- and right-hand side expressions and then assigning the result
to the left-hand side expression. For numerical types, the left-hand
side expression is evaluated only once, and there is an implicit
conversion of the result of the binary operator to the type of the
left-hand side expression. Thus, for example, ``x += y`` is equivalent
to ``x = x + y`` where the expression ``x`` is evaluated once.
For all other compound assignments, Chapel provides a completely generic
catch-all implementation defined in the obvious way. For example:
::
inline proc +=(ref lhs, rhs) {
lhs = lhs + rhs;
}
Thus, compound assignment can be used with operands of arbitrary types,
provided that the following provisions are met: If the type of the
left-hand argument of a compound assignment operator ``op=`` is
:math:`L` and that of the right-hand argument is :math:`R`, then a
definition for the corresponding binary operator ``op`` exists, such
that :math:`L` is coercible to the type of its left-hand formal and
:math:`R` is coercible to the type of its right-hand formal. Further,
the result of ``op`` must be coercible to :math:`L`, and there must
exist a definition for simple assignment between objects of type
:math:`L`.
Both simple and compound assignment operators can be overloaded for
different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__). In such an overload,
the left-hand side expression should have ``ref`` intent and be modified
within the body of the function. The return type of the function should
be ``void``.
.. _The_Swap_Statement:
The Swap Statement
------------------
The swap statement indicates to swap the values in the expressions on
either side of the swap operator. Since both expressions are assigned
to, each must be a valid lvalue
expression (§\ `10.10 <#LValue_Expressions>`__).
The swap operator can be overloaded for different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__).
::
swap-statement:
lvalue-expression swap-operator lvalue-expression
swap-operator:
<=>
To implement the swap operation, the compiler uses temporary variables
as necessary.
*Example*.
When resolved to the default swap operator, the following swap
statement
::
var a, b: real;
a <=> b;
is semantically equivalent to:
::
const t = b;
b = a;
a = t;
.. _The_IO_Statement:
The I/O Statement
-----------------
The I/O operator indicates writing to the left-hand-side the value in
the right-hand-side; or reading from the left-hand-side and storing the
result in the variable on the right-hand-side. This operator can be
chained with other I/O operator calls.
The I/O operator can be overloaded for different types using operator
overloading (§\ `13.12 <#Function_Overloading>`__).
::
io-statement:
io-expression io-operator expression
io-expression:
expression
io-expression io-operator expression
io-operator:
<~>
See the module documentation on I/O for details on how to use the I/O
statement.
*Example*.
In the example below,
::
var w = opentmp().writer(); // a channel
var a: real;
var b: int;
w <~> a <~> b;
the I/O operator is left-associative and indicates writing ``a`` and
then ``b`` to ``w`` in this case.
.. _The_Conditional_Statement:
The Conditional Statement
-------------------------
The conditional statement allows execution to choose between two
statements based on the evaluation of an expression of ``bool`` type.
The syntax for a conditional statement is given by
::
conditional-statement:
`if' expression `then' statement else-part[OPT]
`if' expression block-statement else-part[OPT]
else-part:
`else' statement
A conditional statement evaluates an expression of bool type. If the
expression evaluates to true, the first statement in the conditional
statement is executed. If the expression evaluates to false and the
optional else-clause exists, the statement following the ``else``
keyword is executed.
If the expression is a parameter, the conditional statement is folded by
the compiler. If the expression evaluates to true, the first statement
replaces the conditional statement. If the expression evaluates to
false, the second statement, if it exists, replaces the conditional
statement; if the second statement does not exist, the conditional
statement is removed.
Each statement embedded in the *conditional-statement* has its own scope
whether or not an explicit block surrounds it.
If the statement that immediately follows the optional ``then`` keyword
is a conditional statement and it is not in a block, the else-clause is
bound to the nearest preceding conditional statement without an
else-clause. The statement in the else-clause can be a conditional
statement, too.
*Example (conditionals.chpl)*.
The following function prints ``two`` when ``x`` is ``2`` and
``B,four`` when ``x`` is ``4``.
::
proc condtest(x:int) {
if x > 3 then
if x > 5 then
write("A,");
else
write("B,");
if x == 2 then
writeln("two");
else if x == 4 then
writeln("four");
else
writeln("other");
}
::
for i in 2..6 do condtest(i);
::
two
other
B,four
B,other
A,other
.. _The_Select_Statement:
The Select Statement
--------------------
The select statement is a multi-way variant of the conditional
statement. The syntax is given by:
::
select-statement:
`select' expression { when-statements }
when-statements:
when-statement
when-statement when-statements
when-statement:
`when' expression-list `do' statement
`when' expression-list block-statement
`otherwise' statement
`otherwise' `do' statement
expression-list:
expression
expression , expression-list
The expression that follows the keyword ``select``, the select
expression, is evaluated once and its value is then compared with the
list of case expressions following each ``when`` keyword. These values
are compared using the equality operator ``==``. If the expressions
cannot be compared with the equality operator, a compile-time error is
generated. The first case expression that contains an expression where
that comparison is ``true`` will be selected and control transferred to
the associated statement. If the comparison is always ``false``, the
statement associated with the keyword ``otherwise``, if it exists, will
be selected and control transferred to it. There may be at most one
``otherwise`` statement and its location within the select statement
does not matter.
Each statement embedded in the *when-statement* or the
*otherwise-statement* has its own scope whether or not an explicit block
surrounds it.
.. _The_While_and_Do_While_Loops:
The While Do and Do While Loops
-------------------------------
There are two variants of the while loop in Chapel. The syntax of the
while-do loop is given by:
::
while-do-statement:
`while' expression `do' statement
`while' expression block-statement
The syntax of the do-while loop is given by:
::
do-while-statement:
`do' statement `while' expression ;
In both variants, the expression evaluates to a value of type ``bool``
which determines when the loop terminates and control continues with the
statement following the loop.
The while-do loop is executed as follows:
#. The expression is evaluated.
#. If the expression evaluates to ``false``, the statement is not
executed and control continues to the statement following the loop.
#. If the expression evaluates to ``true``, the statement is executed
and control continues to step 1, evaluating the expression again.
The do-while loop is executed as follows:
#. The statement is executed.
#. The expression is evaluated.
#. If the expression evaluates to ``false``, control continues to the
statement following the loop.
#. If the expression evaluates to ``true``, control continues to step 1
and the the statement is executed again.
In this second form of the loop, note that the statement is executed
unconditionally the first time.
*Example (while.chpl)*.
The following example illustrates the difference between the
``do-while-statement`` and the ``while-do-statement``. The body of
the do-while loop is always executed at least once, even if the loop
conditional is already false when it is entered. The code
::
var t = 11;
writeln("Scope of do while loop:");
do {
t += 1;
writeln(t);
} while (t <= 10);
t = 11;
writeln("Scope of while loop:");
while (t <= 10) {
t += 1;
writeln(t);
}
produces the output
::
Scope of do while loop:
12
Scope of while loop:
Chapel do-while loops differ from those found in most other languages in
one important regard. If the body of a do-while statement is a block
statement and new variables are defined within that block statement,
then the scope of those variables extends to cover the loop’s
termination expression.
*Example (do-while.chpl)*.
The following example demonstrates that the scope of the variable t
includes the loop termination expression.
::
var i = 0;
do {
var t = i;
i += 1;
writeln(t);
} while (t != 5);
produces the output
::
0
1
2
3
4
5
.. _The_For_Loop:
The For Loop
------------
The for loop iterates over ranges, domains, arrays, iterators, or any
class that implements an iterator named ``these``. The syntax of the for
loop is given by:
::
for-statement:
`for' index-var-declaration `in' iteratable-expression `do' statement
`for' index-var-declaration `in' iteratable-expression block-statement
`for' iteratable-expression `do' statement
`for' iteratable-expression block-statement
index-var-declaration:
identifier
tuple-grouped-identifier-list
iteratable-expression:
expression
`zip' ( expression-list )
The ``index-var-declaration`` declares new variables for the scope of
the loop. It may specify a new identifier or may specify multiple
identifiers grouped using a tuple notation in order to destructure the
values returned by the iterator expression, as described
in §\ `16.6.3 <#Indices_in_a_Tuple>`__.
The ``index-var-declaration`` is optional and may be omitted if the
indices do not need to be referenced in the loop.
If the iteratable-expression begins with the keyword ``zip`` followed by
a parenthesized expression-list, the listed expressions must support
zipper iteration.
.. _Zipper_Iteration:
Zipper Iteration
~~~~~~~~~~~~~~~~
When multiple iterators are iterated over in a zipper context, on each
iteration, each expression is iterated over, the values are returned by
the iterators in a tuple and assigned to the index, and then statement
is executed.
The shape of each iterator, the rank and the extents in each dimension,
must be identical.
*Example (zipper.chpl)*.
The output of
::
for (i, j) in zip(1..3, 4..6) do
write(i, " ", j, " ");
::
writeln();
is
::
1 4 2 5 3 6
.. _Parameter_For_Loops:
Parameter For Loops
~~~~~~~~~~~~~~~~~~~
Parameter for loops are unrolled by the compiler so that the index
variable is a parameter rather than a variable. The syntax for a
parameter for loop statement is given by:
::
param-for-statement:
`for' `param' identifier `in' param-iteratable-expression `do' statement
`for' `param' identifier `in' param-iteratable-expression block-statement
param-iteratable-expression:
range-literal
range-literal `by' integer-literal
Parameter for loops are restricted to iteration over range literals with
an optional by expression where the bounds and stride must be
parameters. The loop is then unrolled for each iteration.
.. _Label_Break_Continue:
The Break, Continue and Label Statements
----------------------------------------
The break- and continue-statements are used to alter the flow of control
within a loop construct. A break-statement causes flow to exit the
containing loop and resume with the statement immediately following it.
A continue-statement causes control to jump to the end of the body of
the containing loop and resume execution from there. By default, break-
and continue-statements exit or skip the body of the
immediately-containing loop construct.
The label-statement is used to name a specific loop so that ``break``
and ``continue`` can exit or resume a less-nested loop. Labels can only
be attached to for-, while-do- and do-while-statements. When a break
statement has a label, execution continues with the first statement
following the loop statement with the matching label. When a continue
statement has a label, execution continues at the end of the body of the
loop with the matching label. If there is no containing loop construct
with a matching label, a compile-time error occurs.
The syntax for label, break, and continue statements is given by:
::
break-statement:
`break' identifier[OPT] ;
continue-statement:
`continue' identifier[OPT] ;
label-statement:
`label' identifier statement
A ``break`` statement cannot be used to exit a parallel loop
§\ `27.1 <#Forall>`__.
*Rationale*.
Breaks are not permitted in parallel loops because the execution
order of the iterations of parallel loops is not defined.
..
*Future*.
We expect to support a *eureka* concept which would enable one or
more tasks to stop the execution of all current and future iterations
of the loop.
*Example*.
In the following code, the index of the first element in each row of
``A`` that is equal to ``findVal`` is printed. Once a match is found,
the continue statement is executed causing the outer loop to move to
the next row.
::
label outer for i in 1..n {
for j in 1..n {
if A[i, j] == findVal {
writeln("index: ", (i, j), " matches.");
continue outer;
}
}
}
.. _The_Use_Statement:
The Use Statement
-----------------
The use statement provides access to the constants in an enumerated type
or to the public symbols of a module without the need to use a fully
qualified name. When using a module, the statement also ensures that the
module symbol itself is visible within the current scope (top-level
modules are not otherwise visible without a ``use``).
The syntax of the use statement is given by:
::
use-statement:
privacy-specifier[OPT] `use' module-or-enum-name-list ;
module-or-enum-name-list:
module-or-enum-name limitation-clause[OPT]
module-or-enum-name , module-or-enum-name-list
module-or-enum-name:
identifier
identifier . module-or-enum-name
limitation-clause:
`except' exclude-list
`only' rename-list[OPT]
exclude-list:
identifier-list
$ * $
rename-list:
rename-base
rename-base , rename-list
rename-base:
identifier `as' identifier
identifier
For example, the program
*Example (use1.chpl)*.
::
module M1 {
proc foo() {
writeln("In M1's foo.");
}
}
module M2 {
use M1;
proc main() {
writeln("In M2's main.");
M1.foo();
}
}
prints out
::
In M2's main.
In M1's foo.
This program is equivalent to:
*Example (use2.chpl)*.
::
module M1 {
proc foo() {
writeln("In M1's foo.");
}
}
module M2 {
proc main() {
use M1;
writeln("In M2's main.");
foo();
}
}
which also prints out
::
In M2's main.
In M1's foo.
The names that are imported by a use statement are inserted in to a new
scope that immediately encloses the scope within which the statement
appears. This implies that the position of the use statement within a
scope has no effect on its behavior. If a scope includes multiple use
statements then the imported names are inserted in to a common enclosing
scope.
An error is signaled if multiple enumeration constants or public
module-level symbols would be inserted into this enclosing scope with
the same name, and that name is referenced by other statements in the
same scope as the use.
Use statements are transitive by default: if a module A uses a module B,
and module B contains a use of a module or enumerated type C, then C’s
public symbols may also be visible within A. The exception to this
occurs when B has public symbols that shadow symbols with the same name
in C, or when the use of C has been declared explicitly ``private``. If
a use statement is declared to be ``private``, then the symbols it makes
visible will only be visible to the scope containing the use.
This notion of transitivity extends to the case in which a scope imports
symbols from multiple modules or constants from multiple enumeration
types. For example if a module A uses modules B1, B2, B3 and modules B1,
B2, B3 use modules C1, C2, C3 respectively, then all of the public
symbols in B1, B2, B3 have the potential to shadow the public symbols of
C1, C2, and C3. However an error is signaled if C1, C2, C3 have public
module level definitions of the same symbol.
An optional ``limitation-clause`` may be provided to limit the symbols
made available by a given use statement. If an ``except`` list is
provided, then all the visible but unlisted symbols in the module or
enumerated type will be made available without prefix. If an ``only``
list is provided, then just the listed visible symbols in the module or
enumerated type will be made available without prefix. All visible
symbols not provided via these limited use statements are still
accessible by prefixing the access with the name of the module or
enumerated type. It is an error to provide a name in a
``limitation-clause`` that does not exist or is not visible in the
respective module or enumerated type.
If a type is specified in the ``limitation-clause``, then the type’s
fields and methods are treated similarly to the type name. These fields
and methods cannot be specified in a ``limitation-clause`` on their own.
If an ``only`` list is left empty or ``except`` is followed by :math:`*`
then no symbols are made available to the scope without prefix. However,
any methods or fields defined within a module used in this way will
still be accessible on instances of the type. For example:
*Example (limited-access.chpl)*.
::
module M1 {
record A {
var x = 1;
proc foo() {
writeln("In A.foo()");
}
}
}
module M2 {
proc main() {
use M1 only;
var a = new M1.A(3); // Only accessible via the module prefix
writeln(a.x); // Accessible because we have a record instance
a.foo(); // Ditto
}
}
will print out
::
3
In A.foo()
Within an ``only`` list, a visible symbol from that module may
optionally be given a new name using the ``as`` keyword. This new name
will be usable from the scope of the use in place of the old name unless
the old name is additionally specified in the ``only`` list. If a use
which renames a symbol is present at module scope, uses of that module
will also be able to reference that symbol using the new name instead of
the old name. Renaming does not affect accesses to that symbol via the
source module’s or enumerated type’s prefix, nor does it affect uses of
that module or enumerated type from other contexts. It is an error to
attempt to rename a symbol that does not exist or is not visible in the
respective module or enumerated type, or to rename a symbol to a name
that is already present in the same ``only`` list. It is, however,
perfectly acceptable to rename a symbol to a name present in the
respective module or enumerated type which was not specified via that
``only`` list.
If a use statement mentions multiple modules or enumerated types or a
mix of these symbols, only the last module or enumerated type can have a
``limitation-clause``. Limitation clauses are applied transitively as
well - in the first example, if module A’s use of module B contains an
``except`` or ``only`` list, that list will also limit which of C’s
symbols are visible to A.
For more information on enumerated types, please
see §\ `7.2 <#Enumerated_Types>`__. For use statement rules which are
only applicable to modules, please see §\ `12.5.3 <#Using_Modules>`__.
For more information on modules in general, please
see §\ `[Modules] <#Modules>`__.
.. _The_Defer_Statement:
The Defer Statement
-------------------
A ``defer`` statement declares a clean-up action to be run when exiting
a block. ``defer`` is useful because the clean-up action will be run no
matter how the block is exited.
The syntax is:
::
defer-statement:
`defer' statement
At a given place where control flow exits a block, the in-scope
``defer`` statements and the local variables will be handled in reverse
declaration order. Handling a ``defer`` statement consists of executing
the contained clean-up action. Handling a local variable consists of
running its deinitializer if it is of record type.
When an iterator contains a ``defer`` statement at the top level, the
associated clean-up action will be executed when the loop running the
iterator exits. ``defer`` actions inside a loop body are executed when
that iteration completes.
The following program demonstrates a simple use of ``defer`` to create
an action to be executed when returning from a function:
*Example (defer1.chpl)*.
::
class Integer {
var x:int;
}
proc deferInFunction() {
var c = new unmanaged Integer(1);
writeln("created ", c);
defer {
writeln("defer action: deleting ", c);
delete c;
}
// ... (function body, possibly including return statements)
// The defer action is executed no matter how this function returns.
}
deferInFunction();
produces the output
::
created {x = 1}
defer action: deleting {x = 1}
::
created {x = 1}
defer action: deleting {x = 1}
The following example uses a nested block to demonstrate that ``defer``
is handled when exiting the block in which it is contained:
*Example (defer2.chpl)*.
::
class Integer {
var x:int;
}
proc deferInNestedBlock() {
var i = 1;
writeln("before inner block");
{
var c = new unmanaged Integer(i);
writeln("created ", c);
defer {
writeln("defer action: deleting ", c);
delete c;
}
writeln("in inner block");
// note, defer action is executed no matter how this block is exited
}
writeln("after inner block");
}
deferInNestedBlock();
produces the output
::
before inner block
created {x = 1}
in inner block
defer action: deleting {x = 1}
after inner block
::
before inner block
created {x = 1}
in inner block
defer action: deleting {x = 1}
after inner block
Lastly, this example shows that when ``defer`` is used in a loop, the
action will be executed for every loop iteration, whether or not loop
body is exited early.
*Example (defer3.chpl)*.
::
class Integer {
var x:int;
}
proc deferInLoop() {
for i in 1..10 {
var c = new unmanaged Integer(i);
writeln("created ", c);
defer {
writeln("defer action: deleting ", c);
delete c;
}
writeln(c);
if i == 2 then
break;
}
}
deferInLoop();
produces the output
::
created {x = 1}
{x = 1}
defer action: deleting {x = 1}
created {x = 2}
{x = 2}
defer action: deleting {x = 2}
::
created {x = 1}
{x = 1}
defer action: deleting {x = 1}
created {x = 2}
{x = 2}
defer action: deleting {x = 2}
.. _The_Empty_Statement:
The Empty Statement
-------------------
An empty statement has no effect. The syntax of an empty statement is
given by
::
empty-statement:
;
Modules
=======
[Modules]
Chapel supports modules to manage namespaces. A program consists of one
or more modules. Every symbol, including variables, functions, and
types, is associated with some module.
Module definitions are described in §\ `12.1 <#Module_Definitions>`__.
The relation between files and modules is described
in §\ `12.3 <#Implicit_Modules>`__. Nested modules are described
in §\ `12.4 <#Nested_Modules>`__. The visibility of a module’s symbols
by users of the module is described
in §\ `12.5.2 <#Visibility_Of_Symbols>`__. The execution of a program
and module initialization/deinitialization are described
in §\ `12.6 <#Program_Execution>`__.
.. _Module_Definitions:
Module Definitions
------------------
A module is declared with the following syntax:
::
module-declaration-statement:
privacy-specifier[OPT] prototype-specifier[OPT] `module' module-identifier block-statement
privacy-specifier:
`private'
`public'
prototype-specifier:
`prototype'
module-identifier:
identifier
A module’s name is specified after the ``module`` keyword. The
``block-statement`` opens the module’s scope. Symbols defined in this
block statement are defined in the module’s scope and are called
*module-scope symbols*. The visibility of a module is defined by its
``privacy-specifier`` (§`12.5.1 <#Visibility_Of_A_Module>`__).
Module declaration statements are only legal as file-scope or
module-scope statements. For example, module declaration statements may
not occur within block statements, functions, classes, or records.
Any module declaration that is not contained within another module
creates a *top-level module*. Module declarations within other modules
create nested modules (§\ `12.4 <#Nested_Modules>`__).
.. _Prototype_Modules:
Prototype Modules
-----------------
Modules that are declared with the ``prototype`` keyword use relaxed
rules for error handling and ``nil`` checking. These relaxed rules are
appropriate for programs in the early stages of development but are not
appropriate for libraries. In particular, within a ``prototype`` module:
- errors that are not handled will terminate the program
(see §`15.8.1 <#Errors_Prototype_Mode>`__)
- methods on nilable class instances can be called - these will
implicitly convert to non-nilable class instances
(see §`[Methods_On_Nilable_In_Prototype_Modules] <#Methods_On_Nilable_In_Prototype_Modules>`__)
.. _Implicit_Modules:
Files and Implicit Modules
--------------------------
Multiple modules can be defined within the same file and need not bear
any relation to the file in terms of their names.
*Example (two-modules.chpl)*.
The following file contains two explicitly named modules, MX and MY.
::
module MX {
var x: string = "Module MX";
proc printX() {
writeln(x);
}
}
module MY {
var y: string = "Module MY";
proc printY() {
writeln(y);
}
}
::
MX.printX();
MY.printY();
::
Module MX
Module MY
Module MX defines module-scope symbols x and printX, while MY defines
module-scope symbols y and printY.
For any file that contains file-scope statements other than module
declarations, the file itself is treated as a module declaration. In
this case, the module is implicit. Implicit modules are always
``prototype`` modules. An implicit module takes its name from the base
filename. In particular, the module name is defined as the remaining
string after removing the ``.chpl`` suffix and any path specification
from the specified filename. If the resulting name is not a legal Chapel
identifier, it cannot be referenced in a use statement.
*Example (implicit.chpl)*.
The following file, named implicit.chpl, defines an implicitly named
module called implicit.
::
var x: int = 0;
var y: int = 1;
proc printX() {
writeln(x);
}
proc printY() {
writeln(y);
}
::
printX();
printY();
::
0
1
Module implicit defines the module-scope symbols x, y, printX, and
printY.
.. _Nested_Modules:
Nested Modules
--------------
A *nested module* (or *sub-module*) is a module that is defined within
another module, known as the outer, or parent, module. A nested module
automatically has access to all of the symbols in its outer module.
However, an outer module needs to explicitly name or use a nested module
in order to access its symbols.
A nested module can be used without using the outer module by explicitly
naming the outer module in the use statement.
*Example (nested-use.chpl)*.
The code
::
module libsci {
writeln("Initializing libsci");
module blas {
writeln("\\tInitializing blas");
}
}
module testmain { // used to avoid warnings
}
::
use libsci.blas;
::
Initializing libsci
Initializing blas
uses a module named ``blas`` that is nested inside a module named
``libsci``.
Files with both module declarations and file-scope statements result in
nested modules.
*Example (nested.chpl)*.
The following file, named nested.chpl, defines an implicitly named
module called nested, with nested modules MX and MY.
::
module MX {
var x: int = 0;
}
module MY {
var y: int = 0;
}
use MX, MY;
proc printX() {
writeln(x);
}
proc printY() {
writeln(y);
}
::
printX();
printY();
::
0
0
.. _Access_Of_Module_Contents:
Access of Module Contents
-------------------------
A module’s contents can be accessed by code outside of that module
depending on the visibility of the module
itself (§\ `12.5.1 <#Visibility_Of_A_Module>`__) and the visibility of
each individual symbol (§\ `12.5.2 <#Visibility_Of_Symbols>`__). This
can be done via the use statement (§\ `12.5.3 <#Using_Modules>`__) or
qualified naming (§\ `12.5.4 <#Explicit_Naming>`__).
.. _Visibility_Of_A_Module:
Visibility Of A Module
~~~~~~~~~~~~~~~~~~~~~~
A top-level module is available for use (§\ `12.5.3 <#Using_Modules>`__)
anywhere. The visibility of a nested module is subject to the rules
of §\ `12.5.2 <#Visibility_Of_Symbols>`__, where the nested module is
considered a "module-scope symbol" of its outer module.
.. _Visibility_Of_Symbols:
Visibility Of A Module’s Symbols
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A symbol defined at module scope is *visible* from outside the module
when the ``privacy-specifier`` of its definition is ``public`` or is
omitted (i.e. by default). When a module-scope symbol is declared
``private``, it is not visible outside of that module. A symbol’s
visibility inside its module is controlled by normal lexical scoping and
is not affected by its ``privacy-specifier``. When a module’s symbol is
visible (§\ `12.5.1 <#Visibility_Of_A_Module>`__), the visible symbols
it contains are accessible via the use
statement (§\ `12.5.3 <#Using_Modules>`__) or qualified
naming (§\ `12.5.4 <#Explicit_Naming>`__).
.. _Using_Modules:
Using Modules
~~~~~~~~~~~~~
The ``use`` statement provides the primary means of accessing a module’s
symbols from outside of the module. Use statements make both the
module’s name and its public symbols available for reference within a
given scope. For top-level modules, a use statement is required before
referring to the module’s name or the symbols it contains within a given
lexical scope.
Use statements can also restrict or rename the set of module symbols
that are available within the scope. For further information about use
statements, see §\ `11.11 <#The_Use_Statement>`__.
.. _Explicit_Naming:
Qualified Naming of Module Symbols
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a module’s symbol is visible—via a use statement, or lexically for
nested modules—its public symbols can be referred to via qualified
naming with the following syntax:
::
module-access-expression:
module-identifier-list . identifier
module-identifier-list:
module-identifier
module-identifier . module-identifier-list
This allows two symbols that have the same name to be distinguished
based on the name of their module. Using qualified naming in a function
call restricts the set of candidate functions to those in the specified
module.
If code refers to symbols that are defined by multiple modules, the
compiler will issue an error. Qualified naming can be used to
disambiguate the symbols in this case.
*Example (ambiguity.chpl)*.
In the following example,
::
module M1 {
var x: int = 1;
var y: int = -1;
proc printX() {
writeln("M1's x is: ", x);
}
proc printY() {
writeln("M1's y is: ", y);
}
}
module M2 {
use M3;
use M1;
var x: int = 2;
proc printX() {
writeln("M2's x is: ", x);
}
proc main() {
M1.x = 4;
M1.printX();
writeln(x);
printX(); // This is not ambiguous
printY(); // ERROR: This is ambiguous
}
}
module M3 {
var x: int = 3;
var y: int = -3;
proc printY() {
writeln("M3's y is: ", y);
}
}
::
ambiguity.chpl:22: In function 'main':
ambiguity.chpl:27: error: ambiguous call 'printY()'
ambiguity.chpl:34: note: candidates are: printY()
ambiguity.chpl:7: note: printY()
The call to printX() is not ambiguous because M2’s definition shadows
that of M1. On the other hand, the call to printY() is ambiguous
because it is defined in both M1 and M3. This will result in a
compiler error. The call could be qualified via M1.printY() or
M3.printY() to resolve this ambiguity.
.. _Module_Initialization:
Module Initialization
~~~~~~~~~~~~~~~~~~~~~
Module initialization occurs at program start-up. All module-scope
statements within a module other than function and type declarations are
executed during module initialization. Modules that are not referred to,
including both top-level modules and sub-modules, will not be
initialized.
*Example (init.chpl)*.
In the code,
::
proc foo() {
return 1;
}
::
var x = foo(); // executed at module initialization
writeln("Hi!"); // executed at module initialization
proc sayGoodbye {
writeln("Bye!"); // not executed at module initialization
}
::
Hi!
The function foo() will be invoked and its result assigned to x. Then
“Hi!” will be printed.
Module initialization order is discussed
in §\ `12.6.2 <#Module_Initialization_Order>`__.
.. _Module_Deinitialization:
Module Deinitialization
~~~~~~~~~~~~~~~~~~~~~~~
Module deinitialization occurs at program tear-down. During module
deinitialization:
- If the module contains a deinitializer, which is a module-scope
function named ``deinit()``, it is executed first.
- If the module declares global variables, they are deinitialized in
the reverse declaration order.
Module deinitialization order is discussed
in §\ `12.6.3 <#Module_Deinitialization_Order>`__.
.. _Program_Execution:
Program Execution
-----------------
Chapel programs start by initializing all modules and then executing the
main function (§\ `12.6.1 <#The_main_Function>`__).
.. _The_main_Function:
The *main* Function
~~~~~~~~~~~~~~~~~~~
The main function must be called ``main`` and must have zero arguments.
It can be specified with or without parentheses. In any Chapel program,
there is a single main function that defines the program’s entry point.
If a program defines multiple potential entry points, the implementation
may provide a compiler flag that disambiguates between main functions in
multiple modules.
*Cray’s Chapel Implementation*.
In the Cray Chapel compiler implementation, the *– –main-module* flag
can be used to specify the module from which the main function
definition will be used.
..
*Example (main-module.chpl)*.
Because it defines two ``main`` functions, the following code will
yield an error unless a main module is specified on the command line.
::
module M1 {
const x = 1;
proc main() {
writeln("M", x, "'s main");
}
}
module M2 {
use M1;
const x = 2;
proc main() {
M1.main();
writeln("M", x, "'s main");
}
}
::
--main-module M1 \# main\_module.M1.good
--main-module M2 \# main\_module.M2.good
If M1 is specified as the main module, the program will output:
::
main_module.M1.good
::
M1's main
If M2 is specified as the main module the program will output:
::
main_module.M2.good
::
M1's main
M2's main
Notice that main is treated like just another function if it is not
in the main module and can be called as such.
To aid in exploratory programming, a default main function is created if
the program does not contain a user-defined main function. The default
main function is equivalent to
proc main()
*Example (no-main.chpl)*.
The code
::
writeln("hello, world");
::
hello, world
is a legal and complete Chapel program. The startup code for a Chapel
program first calls the module initialization code for the main
module and then calls ``main()``. This program’s initialization
function is the file-scope writeln() statement. The module
declaration is taken to be the entire file, as described
in §\ `12.3 <#Implicit_Modules>`__.
.. _Module_Initialization_Order:
Module Initialization Order
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Module initialization is performed using the following algorithm.
Starting from the module that defines the main function, the modules
named in its use statements are visited depth-first and initialized in
post-order. If a use statement names a module that has already been
visited, it is not visited a second time. Thus, infinite recursion is
avoided.
Modules used by a given module are visited in the order in which they
appear in the program text. For nested modules, the parent module and
its uses are initialized before the nested module and its uses.
*Example (init-order.chpl)*.
The code
::
module M1 {
use M2.M3;
use M2;
writeln("In M1's initializer");
proc main() {
writeln("In main");
}
}
module M2 {
use M4;
writeln("In M2's initializer");
module M3 {
writeln("In M3's initializer");
}
}
module M4 {
writeln("In M4's initializer");
}
prints the following
::
In M4's initializer
In M2's initializer
In M3's initializer
In M1's initializer
In main
M1, the main module, uses M2.M3 and then M2, thus M2.M3 must be
initialized. Because M2.M3 is a nested module, M4 (which is used by
M2) must be initialized first. M2 itself is initialized, followed by
M2.M3. Finally M1 is initialized, and the main function is run.
.. _Module_Deinitialization_Order:
Module Deinitialization Order
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Module deinitialization is performed in the reverse order of module
initialization, as specified in
§\ `12.6.2 <#Module_Initialization_Order>`__.
Procedures
==========
[Functions]
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 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 §\ `13.2 <#Function_Definitions>`__,
§\ `10.12 <#Binary_Expressions>`__
- iterators §\ `[Iterators] <#Iterators>`__
- methods (when bound to a class) §\ `17.1.7 <#Class_Methods>`__
- function calls §\ `13.1 <#Function_Calls>`__
- various aspects of defining a procedure
§\ `13.2 <#Function_Definitions>`__–§`13.11 <#Nested_Functions>`__
- calling external functions from Chapel
§\ `32.1.1 <#Calling_External_Functions>`__
- calling Chapel functions from external
functions§\ `32.1.2 <#Calling_Chapel_Functions>`__
- determining the function to invoke for a given call site: function
and operator overloading §\ `13.12 <#Function_Overloading>`__,
function resolution §\ `13.13 <#Function_Resolution>`__
.. _Function_Calls:
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 §\ `13.13 <#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 as defined by scope resolution. Functions without
parentheses are discussed
in §\ `13.3 <#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 §\ `13.4.1 <#Named_Arguments>`__.
Calls to methods are defined in
Section §\ `17.6 <#Class_Method_Calls>`__.
.. _Function_Definitions:
Procedure Definitions
---------------------
Procedures are defined with the following syntax:
::
procedure-declaration-statement:
privacy-specifier[OPT] procedure-kind[OPT] `proc' function-name argument-list[OPT] return-intent[OPT] return-type[OPT] where-clause[OPT]
function-body
procedure-kind:
`inline'
`export'
`extern'
`override'
function-name:
identifier
operator-name
operator-name: one of
+ - * / % ** ! == != <= >= < > << >> & | ^ ~
= += -= *= /= %= **= &= |= ^= <<= >>= <=> <~>
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
: ? identifier[OPT]
default-expression:
= expression
variable-argument-expression:
... expression
... ? identifier[OPT]
...
formal-intent:
`const'
`const in'
`const ref'
`in'
`out'
`inout'
`ref'
`param'
`type'
return-intent:
`const'
`const ref'
`ref'
`param'
`type'
return-type:
: type-expression
where-clause:
`where' expression
function-body:
block-statement
return-statement
Functions do not require parentheses if they have no arguments. Such
functions are described in §\ `13.3 <#Functions_without_Parentheses>`__.
Formal arguments can be grouped together using a tuple notation as
described in §\ `16.6.4 <#Formal_Argument_Declarations_in_a_Tuple>`__.
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 §\ `13.4.2 <#Default_Values>`__.
The intents ``const``, ``const in``, ``const ref``, ``in``, ``out``,
``inout`` and ``ref`` are discussed in §\ `13.5 <#Argument_Intents>`__.
The intents ``param`` and ``type`` make a function generic and are
discussed in §\ `24.1 <#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 §\ `24.1 <#Generic_Functions>`__.
Functions can take a variable number of arguments. Such functions are
discussed in §\ `13.6 <#Variable_Length_Argument_Lists>`__.
The ``return-intent`` can be used to indicate how the value is returned
from a function. ``return-intent`` is described further in
§\ `13.7 <#Return_Intent>`__.
*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 §\ `13.9 <#Return_Types>`__. A type function may not specify a return
type.
The ``where-clause`` is optional and is discussed
in §\ `13.10 <#Where_Clauses>`__.
Function and operator overloading is supported in Chapel and is
discussed in §\ `13.12 <#Function_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
 §`12.5.2 <#Visibility_Of_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] <#Interoperability>`__) for details on exported
and imported functions.
.. _Functions_without_Parentheses:
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"); }
::
foo;
bar();
::
In foo
In bar
the procedure ``foo`` can be called by writing ``foo`` and the
procedure ``bar`` can be called by writing ``bar()``. It is an error
to use parentheses when calling ``foo`` or omit them when calling
``bar``.
.. _Formal_Arguments:
Formal Arguments
----------------
A formal argument’s intent (§\ `13.5 <#Argument_Intents>`__) specifies
how the actual argument is passed to the function. If no intent is
specified, the default intent (§\ `13.5.2.3 <#The_Default_Intent>`__) is
applied, resulting in type-dependent behavior.
.. _Named_Arguments:
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
3
named 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
~~~~~~~~~~~~~~
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. Note though that the default value is
evaluated in the same scope as the called function. Default value
expressions can refer to previous formal arguments or to variables that
are visible to the scope of the function definition.
*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
5
Default values are specified for the formal arguments ``x`` and
``y``. The three calls to ``foo`` are equivalent to the following
three calls where the actual arguments are explicit: ``foo(5, 7)``,
``foo(7, 7)``, and ``foo(5, 5)``. The example ``foo(y=5)`` shows how
to use a named argument for ``y`` in order to use the default value
for ``x`` in the case when ``x`` appears earlier than ``y`` in the
formal argument list.
.. _Argument_Intents:
Argument Intents
----------------
Argument intents specify how an actual argument is passed to a function
where it is represented by the corresponding formal argument.
Argument intents are categorized as being either *concrete* or
*abstract*. Concrete intents are those in which the semantics of the
intent keyword are independent of the argument’s type. Abstract intents
are those in which the keyword (or lack thereof) expresses a general
intention that will ultimately be implemented via one of the concrete
intents. The specific choice of concrete intent depends on the
argument’s type and may be implementation-defined. Abstract intents are
provided to support productivity and code reuse.
.. _Concrete Intents:
Concrete Intents
~~~~~~~~~~~~~~~~
The concrete intents are ``in``, ``out``, ``inout``, ``ref``,
``const in``, and ``const ref``.
.. _The_In_Intent:
The In Intent
^^^^^^^^^^^^^
When ``in`` is specified as the intent, the formal argument represents a
variable that is copy-initialized with the value of the actual argument.
For example, for integer arguments, the formal argument will store a
copy of the actual argument. An implicit conversion 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.
.. _The_Out_Intent:
The Out Intent
^^^^^^^^^^^^^^
When ``out`` is specified as the intent, the actual argument is ignored
when the call is made, but when the function returns, the formal
argument is copied back to the actual argument. An implicit conversion
occurs from the type of the formal to the type of the actual. The actual
argument must be a valid lvalue. The formal argument is initialized to
its default value if one is supplied, or to its type’s default value
otherwise. The formal argument can be modified within the function.
.. _The_Inout_Intent:
The Inout Intent
^^^^^^^^^^^^^^^^
When ``inout`` is specified as the intent, the actual argument is copied
into the formal argument as with the ``in`` intent and then copied back
out as with the ``out`` intent. The actual argument must be a valid
lvalue. The formal argument can be modified within the function. The
type of the actual argument must be the same as the type of the formal.
.. _The_Ref_Intent:
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
^^^^^^^^^^^^^^^^^^^
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
^^^^^^^^^^^^^^^^^^^^
The ``const ref`` intent is identical to the ``ref`` intent, except that
modifications to the formal argument are prohibited within the dynamic
scope of the function. Note that concurrent tasks may modify the actual
argument while the function is executing and that these modifications
may be visible to reads of the formal argument within the function’s
dynamic scope (subject to the memory consistency model).
.. _Summary_of_Concrete_Intents:
Summary of Concrete Intents
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following table summarizes the differences between the concrete
intents:
================================ ====== ========= ========= =========== ============ =============
\ ``in`` ``out`` ``inout`` ``ref`` ``const in`` ``const ref``
================================ ====== ========= ========= =========== ============ =============
copied in on function call? yes no yes no yes no
copied out on function return? no yes yes no no no
refers to actual argument? no no no yes no yes
formal can be read? yes yes yes yes yes yes
formal can be modified? yes yes yes yes no no
local changes affect the actual? no on return on return immediately N/A N/A
================================ ====== ========= ========= =========== ============ =============
.. _Abstract_Intents:
Abstract Intents
~~~~~~~~~~~~~~~~
The abstract intents are ``const`` and the *default intent* (when no
intent is specified).
.. _Abstract_Intents_Table:
Abstract Intents Table
^^^^^^^^^^^^^^^^^^^^^^
The following table summarizes what these abstract intents mean for each
type:
=================== ================ ======================= =====
\ meaning of meaning of
type ``const`` intent default intent notes
``bool`` ``const in`` ``const in``
``int`` ``const in`` ``const in``
``uint`` ``const in`` ``const in``
``real`` ``const in`` ``const in``
``imag`` ``const in`` ``const in``
``complex`` ``const in`` ``const in``
``range`` ``const in`` ``const in``
``owned class`` ``const ref`` ``const ref``
``shared class`` ``const ref`` ``const ref``
``borrowed class`` ``const in`` ``const in``
``unmanaged class`` ``const in`` ``const in``
``atomic`` ``const ref`` ``ref``
``single`` ``const ref`` ``ref``
``sync`` ``const ref`` ``ref``
``string`` ``const ref`` ``const ref``
``bytes`` ``const ref`` ``const ref``
``record`` ``const ref`` ``const ref`` see
``union`` ``const ref`` ``const ref``
``dmap`` ``const ref`` ``const ref``
``domain`` ``const ref`` ``const ref``
array ``const ref`` ``ref`` / ``const ref`` see
=================== ================ ======================= =====
.. _The_Const_Intent:
The Const Intent
^^^^^^^^^^^^^^^^
The ``const`` intent specifies the intention that the function will not
and cannot modify the formal argument within its dynamic scope. Whether
the actual argument will be passed by ``const in`` or ``const ref``
intent depends on its type. In general, small values, such as scalar
types, will be passed by ``const in``; while larger values, such as
domains and arrays, will be passed by ``const ref`` intent. The earlier
in this sub-section lists the meaning of the const intent for each type.
.. _The_Default_Intent:
The Default Intent
^^^^^^^^^^^^^^^^^^
When no intent is specified for a formal argument, the *default intent*
is applied. It is designed to take the most natural/least surprising
action for the argument, based on its type. The earlier in this
sub-section lists the meaning of the default intent for each type.
Default argument passing for tuples generally matches the default
argument passing strategy that would be applied if each tuple element
was passed as a separate argument.
*Open issue*.
How tuples should be handled under default intents is an open issue;
particularly for heterogeneous tuples whose components would fall
into separate categories in the table above. One proposed approach is
to apply the default intent to each component of the tuple
independently.
.. _Default_Intent_for_Arrays_and_Record_this:
Default Intent for Arrays and Record ’this’
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The default intent for arrays and for a ``this`` argument of record
type (see §\ `14.2 <#Method_receiver_and_this>`__) is ``ref`` or
``const ref``. It is ``ref`` if the formal argument is modified inside
the function, otherwise it is ``const ref``. Note that neither of these
cause an array or record to be copied by default. The choice between
``ref`` and ``const ref`` is similar to and interacts with return intent
overloads (see §\ `13.7.3 <#Return_Intent_Overloads>`__).
.. _Default_Intent_for_owned_and_shared:
Default Intent for ’owned’ and ’shared’
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The default intent for ``owned`` and ``shared`` arguments is
``const ref``. Arguments can use the ``in`` or ``const in`` intents to
transfer or share ownership if those arguments apply to ``owned`` or
``shared`` types.
*Example (owned-any-intent.chpl)*.
::
proc defaultGeneric(arg) {
writeln(arg.type:string);
}
class SomeClass { }
var own = new owned SomeClass();
defaultGeneric(own);
writeln(own != nil);
::
owned SomeClass
true
.. _Variable_Length_Argument_Lists:
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.
*Example (varargs.chpl)*.
The code
::
proc mywriteln(x ...?k) {
for param i in 1..k do
writeln(x(i));
}
mywriteln("hi", "there"); mywriteln(1, 2.0, 3, 4.0);
::
hi
there
1
2.0
3
4.0
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 parameter
for-loop (§\ `11.9.2 <#Parameter_For_Loops>`__) is unrolled by the
compiler so that ``i`` is a parameter, rather than a variable. This
needs to be a parameter for-loop because the expression ``x(i)`` will
have a different type on each iteration. The type of ``x`` can be
specified in the formal argument list to ensure that the actuals all
have the same type.
..
*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) return x(1) + x(2) + x(3);
::
writeln(sum(1, 2, 3));
writeln(sum(-1, -2, -3));
::
6
-6
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 ...) return x;
::
writeln(tuple(1));
writeln(tuple("hi", "there"));
writeln(tuple(tuple(1, 2), tuple(3, 4)));
::
(1)
(hi, there)
((1, 2), (3, 4))
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 expressions ``tuple(1)`` and ``(1,)``.
.. _Return_Intent:
Return Intents
--------------
The ``return-intent`` specifies how the value is returned from a
function, and in what contexts that function is allowed to be used. By
default, or if the ``return-intent`` is ``const``, the function returns
a value that cannot be used as an lvalue.
.. _Ref_Return_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 or yield an lvalue.
*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``.
::
3
.. _Const_Ref_Return_Intent:
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.
.. _Return_Intent_Overloads:
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 `13.13.5 <#Choosing_Return_Intent_Overload>`__ 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;
::
ref-return-intent-pair.chpl:8: error: halt reached - cannot assign value to A(1) if A(0) <= 0
.. _Param_Return_Intent:
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
return a**2 + b**2;
var x: sumOfSquares(2, 3)*int;
::
writeln(x);
::
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
``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 §\ `11.9.2 <#Parameter_For_Loops>`__ and conditionals with a
conditional expressions that is not a parameter.
.. _Type_Return_Intent:
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 §\ `11.9.2 <#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);
}
::
var a = 4: int(32),
b = 4.0;
var at: myType(a),
bt: myType(b);
writeln((numBits(at.type), numBits(bt.type)));
::
(32, 64)
``myType`` is a type procedure that takes a single argument ``x`` and
returns ``int(32)`` if the number of bits used to represent ``x`` is
less than or equal to 32, otherwise it returns ``int(64)``.
``numBits`` is a param procedure defined in the standard Types
module.
.. _The_Return_Statement:
The Return Statement
--------------------
The return statement can only appear in a function. 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 non-\ ``void`` return type
(§`13.9 <#Return_Types>`__) must include an expression. A return
statement in a procedure of a ``void`` return type or in an iterator
must not include an expression. A return statement of a variable
procedure must contain an lvalue expression.
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)
return i1 + i2 + i3;
::
writeln(sum(1, 2, 3));
::
6
.. _Return_Types:
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:
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 (§\ `13.7.1 <#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:
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
(§`13.7.1 <#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:
Where Clauses
-------------
The list of function candidates can be constrained by *where clauses*. A
where clause is specified in the definition of a
function (§\ `13.2 <#Function_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"); }
::
foo(3);
foo(3.14);
::
int
real
the call foo(3) resolves to the first definition because the where
clause on the second function evaluates to false.
.. _Nested_Functions:
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_Overloading:
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 §\ `13.13 <#Function_Resolution>`__.
Operator overloading is achieved by defining a function with a name
specified by that operator. The operators that may be overloaded are
listed in the following table:
| \|l|l\| **arity** & **operators**
| unary & + - !  @
| binary & + - \* / & = += -= \*= /=
The arity and precedence of the operator must be maintained when it is
overloaded. Operator resolution follows the same algorithm as function
resolution.
.. _Function_Resolution:
Function Resolution
-------------------
*Function resolution* is the algorithm that determines which 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 §\ `13.13.1 <#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 §\ `13.13.2 <#Determining_Candidate_Functions>`__. If no candidate
function can be 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 function.
- From the set of candidate functions, determine the set of most
specific functions. In most cases, there is one most specific
function, but there can be several if they differ only in return
intent. The set of most specific functions is the set of functions
that are not *more specific* than each other but that are *more
specific* than every other candidate function. The *more specific*
relationship is defined in
 §`13.13.3 <#Determining_More_Specific_Functions>`__.
- From the set of most specific functions, the compiler determines a
best function for each return intent as described in
 §`13.13.4 <#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 which function to call based on the calling context as
described in §\ `13.13.5 <#Choosing_Return_Intent_Overload>`__.
.. _Determining_Visible_Functions:
Determining Visible Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Given a function call, a function is determined to be a *visible
function* if the name of the function is the same as the name of the
function call and the function is defined in the same scope as the
function call or a lexical outer scope of the function call, or if the
function is publicly declared in a module that is used from the same
scope as the function call or a lexical outer scope of the function
call. Function visibility in generic functions is discussed
in §\ `24.2 <#Function_Visibility_in_Generic_Functions>`__.
.. _Determining_Candidate_Functions:
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 and each actual argument is mapped to a formal argument that is
a *legal argument mapping*.
.. _Valid_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:
Legal Argument Mapping
^^^^^^^^^^^^^^^^^^^^^^
An actual argument of type :math:`T_A` can be mapped to a formal
argument of type :math:`T_F` if any of the following conditions hold:
- :math:`T_A` and :math:`T_F` are the same type.
- There is an implicit conversion from :math:`T_A` to :math:`T_F`.
- :math:`T_A` is derived from :math:`T_F`.
- :math:`T_A` is scalar promotable to :math:`T_F`.
.. _Determining_More_Specific_Functions:
Determining More Specific Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Given two functions :math:`F_1` and :math:`F_2`, the more specific
function is determined by the first of the following steps that applies:
- If :math:`F_1` does not require promotion and :math:`F_2` does
require promotion, then :math:`F_1` is more specific.
- If :math:`F_2` does not require promotion and :math:`F_1` does
require promotion, then :math:`F_2` is more specific.
- If at least one of the legal argument mappings to :math:`F_1` is a
*more specific argument mapping* than the corresponding legal
argument mapping to :math:`F_2` and none of the legal argument
mappings to :math:`F_2` is a more specific argument mapping than the
corresponding legal argument mapping to :math:`F_1`, then :math:`F_1`
is more specific.
- If at least one of the legal argument mappings to :math:`F_2` is a
*more specific argument mapping* than the corresponding legal
argument mapping to :math:`F_1` and none of the legal argument
mappings to :math:`F_1` is a more specific argument mapping than the
corresponding legal argument mapping to :math:`F_2`, then :math:`F_2`
is more specific.
- If :math:`F_1` shadows :math:`F_2`, then :math:`F_1` is more
specific.
- If :math:`F_2` shadows :math:`F_1`, then :math:`F_2` is more
specific.
- If at least one of the legal argument mappings to :math:`F_1` is
*weak preferred* and none of the legal argument mappings to
:math:`F_2` are *weak preferred*, then :math:`F_1` is more specific.
- If at least one of the legal argument mappings to :math:`F_2` is
*weak preferred* and none of the legal argument mappings to
:math:`F_1` are *weak preferred*, then :math:`F_2` is more specific.
- If at least one of the legal argument mappings to :math:`F_1` is
*weaker preferred* and none of the legal argument mappings to
:math:`F_2` are *weaker preferred*, then :math:`F_1` is more
specific.
- If at least one of the legal argument mappings to :math:`F_2` is
*weaker preferred* and none of the legal argument mappings to
:math:`F_1` are *weaker preferred*, then :math:`F_2` is more
specific.
- If at least one of the legal argument mappings to :math:`F_1` is
*weakest preferred* and none of the legal argument mappings to
:math:`F_2` are *weakest preferred*, then :math:`F_1` is more
specific.
- If at least one of the legal argument mappings to :math:`F_2` is
*weakest preferred* and none of the legal argument mappings to
:math:`F_1` are *weakest preferred*, then :math:`F_2` is more
specific.
- Otherwise neither function is more specific.
Given an argument mapping, :math:`M_1`, from an actual argument,
:math:`A`, of type :math:`T_A` to a formal argument, :math:`F1`, of type
:math:`T_{F1}` and an argument mapping, :math:`M_2`, from the same
actual argument to a formal argument, :math:`F2`, of type
:math:`T_{F2}`, the level of preference for one of these argument
mappings is determined by the first of the following steps that applies:
- If :math:`T_{F1}` and :math:`T_{F2}` are the same type, :math:`F1` is
an instantiated parameter, and :math:`F2` is not an instantiated
parameter, :math:`M_1` is more specific.
- If :math:`T_{F1}` and :math:`T_{F2}` are the same type, :math:`F2` is
an instantiated parameter, and :math:`F1` is not an instantiated
parameter, :math:`M_2` is more specific.
- If :math:`M_1` does not require scalar promotion and :math:`M_2`
requires scalar promotion, :math:`M_1` is more specific.
- If :math:`M_1` requires scalar promotion and :math:`M_2` does not
require scalar promotion, :math:`M_2` is more specific.
- If :math:`T_{F1}` and :math:`T_{F2}` are the same type, :math:`F1` is
generic, and :math:`F2` is not generic, :math:`M_1` is more specific.
- If :math:`T_{F1}` and :math:`T_{F2}` are the same type, :math:`F2` is
generic, and :math:`F1` is not generic, :math:`M_2` is more specific.
- If :math:`F1` is not generic over all types and :math:`F2` is generic
over all types, :math:`M_1` is more specific.
- If :math:`F1` is generic over all types and :math:`F2` is not generic
over all types, :math:`M_2` is more specific.
- If :math:`F1` and :math:`F2` are both generic, and :math:`F1` is
partially concrete but :math:`F2` is not, then :math:`M_1` is more
specific.
- If :math:`F1` and :math:`F2` are both generic, and :math:`F2` is
partially concrete but :math:`F1` is not, then :math:`M_2` is more
specific.
- If :math:`F1` is a ``param`` argument but :math:`F2` is not, then
:math:`M_1` is weak preferred.
- If :math:`F2` is a ``param`` argument but :math:`F1` is not, then
:math:`M_2` is weak preferred.
- If :math:`A` is not a ``param`` argument with a default size and
:math:`F2` requires a narrowing conversion but :math:`F1` does not,
then :math:`M_1` is weak preferred.
- If :math:`A` is not a ``param`` argument with a default size and
:math:`F1` requires a narrowing conversion but :math:`F2` does not,
then :math:`M_2` is weak preferred.
- If :math:`T_A` and :math:`T_{F1}` are the same type and :math:`T_A`
and :math:`T_{F2}` are not the same type, :math:`M_1` is more
specific.
- If :math:`T_A` and :math:`T_{F1}` are not the same type and
:math:`T_A` and :math:`T_{F2}` are the same type, :math:`M_2` is more
specific.
- If :math:`A` uses a scalar promotion type equal to :math:`T_{F1}` but
different from :math:`T_{F2}`, then :math:`M_1` will be preferred as
follows:
- if :math:`A` is a ``param`` argument with a default size, then
:math:`M_1` is weakest preferred
- if :math:`A` is a ``param`` argument with non-default size, then
:math:`M_1` is weaker preferred
- otherwise, :math:`M_1` is more specific
- If :math:`A` uses a scalar promotion type equal to :math:`T_{F2}` but
different from :math:`T_{F1}`, then :math:`M_2` will be preferred as
follows:
- if :math:`A` is a ``param`` argument with a default size, then
:math:`M_2` is weakest preferred
- if :math:`A` is a ``param`` argument with non-default size, then
:math:`M_2` is weaker preferred
- otherwise, :math:`M_2` is more specific
- If :math:`T_A` or its scalar promotion type prefers conversion to
:math:`T_{F1}` over conversion to :math:`T_{F2}`, then :math:`M_1` is
preferred. If :math:`A` is a ``param`` argument with a default size,
then :math:`M_1` is weakest preferred. Otherwise, :math:`M_1` is
weaker preferred.
Type conversion preferences are as follows:
- Prefer converting a numeric argument to a numeric argument of a
different width but the same category over converting to another
type. Categories are
- bool
- enum
- int or uint
- real
- imag
- complex
- Prefer an enum or bool cast to int over uint
- Prefer an enum or bool cast to a default-sized int or uint over
another size of int or uint
- Prefer an enum, bool, int, or uint cast to a default-sized real
over another size of real or complex
- Prefer an enum, bool, int, or uint cast to a default-sized complex
over another size of complex
- Prefer real/imag cast to the complex with that component size (ie
total width of twice the real/imag) over another size of complex
- If :math:`T_A` or its scalar promotion type prefers conversion to
:math:`T_{F2}` over conversion to :math:`T_{F1}`, then :math:`M_2` is
preferred. If :math:`A` is a ``param`` argument with a default size,
then :math:`M_2` is weakest preferred. Otherwise, :math:`M_2` is
weaker preferred.
- If :math:`T_{F1}` is derived from :math:`T_{F2}`, then :math:`M_1` is
more specific.
- If :math:`T_{F2}` is derived from :math:`T_{F1}`, then :math:`M_2` is
more specific.
- If there is an implicit conversion from :math:`T_{F1}` to
:math:`T_{F2}`, then :math:`M_1` is more specific.
- If there is an implicit conversion from :math:`T_{F2}` to
:math:`T_{F1}`, then :math:`M_2` is more specific.
- If :math:`T_{F1}` is any ``int`` type and :math:`T_{F2}` is any
``uint`` type, :math:`M_1` is more specific.
- If :math:`T_{F2}` is any ``int`` type and :math:`T_{F1}` is any
``uint`` type, :math:`M_2` is more specific.
- Otherwise neither mapping is more specific.
.. _Determining_Best_Functions:
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_Overload:
Choosing Return Intent Overloads Based on Calling Context
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
See also `13.7.3 <#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``, ``const``, or the default (blank) return intent
- at least two of the above return intents have a best function.
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``, or ``ref`` intent
- the call is captured into a ``ref`` variable
- the 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`` intent
- the call is captured into a ``const ref`` variable
- the call is returned from a function with ``const ref`` return intent
Otherwise, the value version will be chosen.
Methods
=======
[Methods]
A *method* is a procedure or iterator that is associated with an
expression known as the *receiver*.
Methods are declared with the following syntax:
::
method-declaration-statement:
procedure-kind[OPT] proc-or-iter this-intent[OPT] type-binding[OPT] function-name argument-list[OPT]
return-intent[OPT] return-type[OPT] where-clause[OPT] function-body
proc-or-iter:
`proc'
`iter'
this-intent:
`param'
`type'
`ref'
`const ref'
`const'
type-binding:
identifier .
`(' expr `)' .
Methods defined within the lexical scope of a class, record, or union
are referred to as *primary methods*. For such methods, the
``type-binding`` is omitted and is taken to be the innermost class,
record, or union in which the method is defined.
Methods defined outside of such scopes are known as *secondary methods*
and must have a ``type-binding`` (otherwise, they would simply be
standalone functions rather than methods). Note that secondary methods
can be defined not only for classes, records, and unions, but also for
any other type (e.g., integers, reals, strings).
[Secondary_Methods_with_Type_Expressions] Secondary methods can be
declared with a type expression instead of a type identifier. In
particular, if the ``type-binding`` is a parenthesized expression, the
compiler will evaluate that expression to find the receiver type for the
method. In that case, the method applies only to receivers of that type.
See also
§\ `24.5 <#Creating_General_and_Specialized_Versions_of_a_Function>`__.
Method calls are described in §\ `14.1 <#Method_Calls>`__.
The use of ``this-intent`` is described in
§\ `14.2 <#Method_receiver_and_this>`__.
.. _Method_Calls:
Method Calls
------------
A method is invoked with a method call, which is similar to a non-method
call expression, but it can include a receiver clause. The receiver
clause syntactically identifies a single argument by putting it before
the method name. That argument is the method receiver. When calling a
method from another method, or from within a class or record
declaration, the receiver clause can be omitted.
::
method-call-expression:
receiver-clause[OPT] expression ( named-expression-list )
receiver-clause[OPT] expression [ named-expression-list ]
receiver-clause[OPT] parenthesesless-function-identifier
The receiver-clause (or its absence) specifies the method’s receiver
§\ `14.2 <#Method_receiver_and_this>`__.
*Example (defineMethod.chpl)*.
A method to output information about an instance of the ``Actor``
class can be defined as follows:
::
class Actor {
var name: string;
var age: uint;
}
var anActor = new owned Actor(name="Tommy", age=27);
writeln(anActor);
::
proc Actor.print() {
writeln("Actor ", name, " is ", age, " years old");
}
::
anActor.print();
::
{name = Tommy, age = 27}
Actor Tommy is 27 years old
This method can be called on an instance of the ``Actor`` class,
``anActor``, with the call expression ``anActor.print()``.
The actual arguments supplied in the method call are bound to the formal
arguments in the method declaration following the rules specified for
procedures (§`[Functions] <#Functions>`__). The exception is the
receiver §\ `14.2 <#Method_receiver_and_this>`__.
.. _Method_receiver_and_this:
The Method Receiver and the *this* Argument
-------------------------------------------
A method’s *receiver* is an implicit formal argument named ``this``
representing the expression on which the method is invoked. The
receiver’s actual argument is specified by the ``receiver-clause`` of a
method-call-expression as specified in §\ `14.1 <#Method_Calls>`__.
*Example (implicitThis.chpl)*.
Let class ``C``, method ``foo``, and function ``bar`` be defined as
::
class C {
proc foo() {
bar(this);
}
}
proc bar(c: C) { writeln(c); }
::
var c1: C = new owned C();
c1.foo();
::
{}
Then given an instance of ``C`` called ``c1``, the method call
``c1.foo()`` results in a call to ``bar`` where the argument is
``c1``. Within primary method ``C.foo()``, the (implicit) receiver
formal has static type ``C`` (otherwise known as ``borrowed C``) and
is referred to as ``this``.
Methods whose receivers are objects are called *instance methods*.
Methods may also be defined to have ``type`` receivers—these are known
as *type methods*.
Note that within a method for a class ``C``, the type of ``this`` is
generally ``borrowed C``. Within a type method on a class ``C``,
``this`` refers to the class type ``C`` with management and nilability
matching the type of the receiver. Please
see §\ `17.1.7 <#Class_Methods>`__ for more details.
The optional ``this-intent`` is used to specify type methods, to
constrain a receiver argument to be a ``param``, or to specify how the
receiver argument should be passed to the method.
When no ``this-intent`` is used, a default this intent applies. For
methods on classes and other primitive types, the default this intent is
the same as the default intent for that type. For record methods, the
intent for the receiver formal argument is ``ref`` or ``const ref``,
depending on whether the formal argument is modified inside of the
method. Programmers wishing to be explicit about whether or not record
methods modify the receiver can explicitly use the ``ref`` or
``const ref`` ``this-intent``.
A method whose ``this-intent`` is ``type`` defines a *type method*. It
can only be called on the type itself rather than on an instance of the
type. When ``this-intent`` is ``param``, it specifies that the function
can only be applied to param objects of the given type binding.
*Example (paramTypeThisIntent.chpl)*.
In the following code, the ``isOdd`` method is defined with a
``this-intent`` of ``param``, permitting it to be called on params
only. The ``size`` method is defined with a ``this-intent`` of
``type``, requiring it to be called on the ``int`` type itself, not
on integer values.
::
proc param int.isOdd() param {
return this & 0x1 == 0x1;
}
proc type int.size() param {
return 64;
}
param three = 3;
var seven = 7;
writeln(42.isOdd()); // prints false
writeln(three.isOdd()); // prints true
writeln((42+three).isOdd()); // prints true
// writeln(seven.isOdd()); // illegal since 'seven' is not a param
writeln(int.size()); // prints 64
// writeln(42.size()); // illegal since 'size()' is a type method
::
false
true
true
64
Type methods can also be iterators.
*Example (typeMethodIter.chpl)*.
In the following code, the class ``C`` defines a type method iterator
which can be invoked on the type itself:
::
class C {
var x: int;
var y: string;
iter type myIter() {
yield 3;
yield 5;
yield 7;
yield 11;
}
}
for i in C.myIter() do
writeln(i);
::
3
5
7
11
When ``this-intent`` is ``ref``, the receiver argument will be passed by
reference, allowing modifications to ``this``. If ``this-intent`` is
``const ref``, the receiver argument is passed by reference but it
cannot be modified inside the method. The ``this-intent`` can also
describe an abstract intent as follows. If it is ``const``, the receiver
argument will be passed with ``const`` intent. If it is left out
entirely, the receiver will be passed with a default intent. For
records, that default intent is ``ref`` if ``this`` is modified within
the function and ``const ref`` otherwise. For other types, the default
``this`` intent matches the default argument intent described in
§\ `13.5.2.3 <#The_Default_Intent>`__.
*Example (refThisIntent.chpl)*.
In the following code, the ``doubleMe`` function is defined with a
``this-intent`` of ``ref``, allowing variables of type ``int`` to
double themselves.
::
proc ref int.doubleMe() { this *= 2; }
::
var x: int = 2;
x.doubleMe();
writeln(x);
::
4
Given a variable ``x = 2``, a call to ``x.doubleMe()`` will set ``x``
to ``4``.
.. _The_this_Method:
The *this* Method
-----------------
A procedure method declared with the name ``this`` allows the receiver
to be “indexed” similarly to how an array is indexed. Indexing (as with
``A[1]``) has the semantics of calling a method named ``this``. There is
no other way to call a method called ``this``. The ``this`` method must
be declared with parentheses even if the argument list is empty.
*Example (thisMethod.chpl)*.
In the following code, the ``this`` method is used to create a class
that acts like a simple array that contains three integers indexed by
1, 2, and 3.
::
class ThreeArray {
var x1, x2, x3: int;
proc this(i: int) ref {
select i {
when 1 do return x1;
when 2 do return x2;
when 3 do return x3;
}
halt("ThreeArray index out of bounds: ", i);
}
}
::
var ta = new borrowed ThreeArray();
ta(1) = 1;
ta(2) = 2;
ta(3) = 3;
for i in 1..3 do
writeln(ta(i));
ta(4) = 4;
::
1
2
3
thisMethod.chpl:9: error: halt reached - ThreeArray index out of bounds: 4
.. _The_these_Method:
The *these* Method
------------------
An iterator method declared with the name ``these`` allows the receiver
to be “iterated over” similarly to how a domain or array supports
iteration. When a value supporting a ``these`` method is used as the the
``iteratable-expression`` of a loop, the loop proceeds in a manner
controlled by the ``these`` iterator.
*Example (theseIterator.chpl)*.
In the following code, the ``these`` method is used to create a class
that acts like a simple array that can be iterated over and contains
three integers.
::
class ThreeArray {
var x1, x2, x3: int;
iter these() ref {
yield x1;
yield x2;
yield x3;
}
}
::
var ta = new owned ThreeArray();
for (i, j) in zip(ta, 1..) do
i = j;
for i in ta do
writeln(i);
::
1
2
3
An iterator type method with the name ``these`` supports iteration over
the class type itself.
*Example (typeMethodIterThese.chpl)*.
In the following code, the class ``C`` defines a type method iterator
named ``these``, supporting direct iteration over the type:
::
class C {
var x: int;
var y: string;
iter type these() {
yield 1;
yield 2;
yield 4;
yield 8;
}
}
for i in C do
writeln(i);
::
1
2
4
8
Error Handling
==============
[Error_Handling]
The Chapel language supports ``throw``, ``try``, ``try``!, ``catch``,
and ``throws`` which are described below. Chapel supports several error
handling modes, including a mode suitable for prototype development and
a less-permissive mode intended for production code.
*Cray’s Chapel Implementation*.
| Additional information about the Cray Chapel implementation of
error handling and the *strict* error handling mode, which is not
defined here, is available online in the technical note:
| https://chapel-lang.org/docs/technotes/errorHandling.html
.. _Throwing_Errors:
Throwing Errors
---------------
Errors may be thrown from a function to its callee with a ``throw``
statement. For a function to throw an error, its signature must include
a ``throws`` declaration. The declaration is put after the return type
and before any ``where`` clauses.
Only ``owned`` instances of a type inheriting from ``Error`` can be
thrown.
*Example (throwing.chpl)*.
::
proc canThrow(i: int): int throws {
if i < 0 then
throw new owned Error();
return i + 1;
}
proc alwaysThrows():int throws {
throw new owned Error();
// never reached
return 1;
}
.. _Handling_Errors:
Handling Errors
---------------
There are three ways to handle an error:
- Halt with ``try``!.
- Handle the error with ``catch`` blocks.
- Propagate the error out of the current function with ``throws``.
.. _Halting_on_error_with_try_bang:
Halting on error with try!
~~~~~~~~~~~~~~~~~~~~~~~~~~
If an error is thrown by a call within the lexical scope of a ``try``!
block or a ``try``! expression prefix, the program halts.
*Example (try-bang.chpl)*.
::
proc haltsOnError():int {
// the try! next to the throwing call
// halts the program if an error occurs.
return try! canThrow(0);
}
proc haltsOnErrorBlock() {
try! {
canThrow(1);
canThrow(0);
}
}
.. _Handling_an_error_with_catch:
Handling an error with catch
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When an error is raised by a call in a ``try`` or ``try``! block, the
rest of the block is abandoned and control flow is passed to its
``catch`` clause(s), if any.
.. _Catch_clauses:
Catch clauses
^^^^^^^^^^^^^
A ``try`` or ``try``! block can have one or more ``catch`` clauses.
A ``catch`` clause can specify the variable that refers to the caught
error within the ``catch`` block. If the variable is given a type, for
example ``catch e:SomeError``, it is a *type filter*. The corresponding
``catch`` clause *matches* the errors that are of the class
``SomeError`` or its subclass. If no type filter is present on a catch
clause, or if no variable is present at all, then it is a *catchall*
clause, which matches all errors.
The type filters are evaluated in the order that the ``catch`` clauses
appear in the program. If a ``catch`` clause’s type filter matches, then
its block is executed to the exclusion of the others. Hence there is no
notion of best match, only a first match.
If the ``catch`` block itself throws an error, it is handled in the same
manner as if that error were thrown by a statement adjacent to the
``try``-``catch`` blocks. Otherwise, after the execution of the
``catch`` block completes, the program execution proceeds to the next
statement after the ``try``-``catch`` blocks.
*Example (catching-errors.chpl)*.
::
proc catchingErrors() throws {
try {
alwaysThrows(0);
} catch {
writeln("caught an error, unnamed catchall clause");
}
try {
var x = alwaysThrows(-1);
writeln("never reached");
} catch e:FileNotFoundError {
writeln("caught an error, FileNotFoundError type filter matched");
} catch e {
writeln("caught an error in a named catchall clause");
}
}
.. _try_bang_with_catch:
try! with catch
^^^^^^^^^^^^^^^
If an error is thrown within a ``try``! block and none of its ``catch``
clauses, if any, match that error, the program halts.
*Example (catching-errors-halt.chpl)*.
::
proc catchingErrorsHalt() {
try! {
var x = alwaysThrows(-1);
writeln("never reached");
} catch e:FileNotFoundError {
writeln("caught a file not found error");
}
// errors other than FileNotFoundError cause a halt
}
.. _Nested_try:
Nested try
^^^^^^^^^^
If an error is thrown within a ``try`` block and none of its ``catch``
clauses, if any, match that error, the error is directed to the
enclosing ``try`` block, when present.
*Example (nested-try.chpl)*.
::
class DemoError : Error { }
proc nestedTry() {
try {
try {
alwaysThrows(0);
} catch e: DemoError {
writeln("caught a DemoError");
}
writeln("never reached");
} catch {
writeln("caught an Error from inner try");
}
}
::
proc alwaysThrows():int throws {
throw new owned Error();
// never reached
return 1;
}
.. _Propagating_an_error_with_throws:
Propagating an error with throws
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A function marked ``throws`` can pass along an error thrown by a
function called within it. This can be done in several ways.
.. _After_catch_clauses:
After catch clauses
^^^^^^^^^^^^^^^^^^^
Propagation can occur when no matching ``catch`` clause is found for an
error raised in a ``try`` block.
*Example (catching-errors-propagate.chpl)*.
::
proc catchingErrorsPropagate() throws {
try {
var x = alwaysThrows(-1);
writeln("never reached");
} catch e:FileNotFoundError {
writeln("caught a file not found error");
}
// errors other than FileNotFoundError propagate
}
.. _catch_less_try:
catch-less try
^^^^^^^^^^^^^^
A logical extension of the above is the case where no ``catch`` blocks
are attached to the ``try``. Here the ``try`` keyword marks throwing
calls to clarify control flow.
*Example (propagates-error.chpl)*.
::
proc propagatesError() throws {
// control flow changes if an error was thrown;
// could be indicated more clearly with try
canThrow(0);
try canThrow(0);
try {
canThrow(0);
}
var x = try canThrow(1);
writeln(x);
return try canThrow(0);
}
.. _try_expressions:
try expressions
^^^^^^^^^^^^^^^
``try`` and ``try``! are available as expressions to clarify control
flow at expression granularity. The expression form may not be used with
``catch`` clauses.
*Example (expression-try.chpl)*.
::
proc expressionTry(): int throws {
var x = try canThrow(1);
writeln(x);
return try canThrow(0);
}
.. _Complete_handling:
Complete handling
~~~~~~~~~~~~~~~~~
For a function to handle errors from its calls without itself throwing,
its ``try``/``catch`` must be complete. This may be accomplished in two
ways:
- A catchall clause on ``try``. This prevents ``try`` from propagating
the error out of the function as described above.
*Example (warns-on-error.chpl)*.
::
proc warnsOnError(i: int): int {
try {
alwaysThrows(i);
} catch e {
writeln("Warning: caught a error ", e);
}
}
- ``try``! instead of ``try``. This will halt the program if no
matching ``catch`` clause is found, instead of propagating.
*Example (halts-on-error.chpl)*.
::
class DemoError : Error { }
proc haltsOnError(i: int): int {
try! {
canThrow(i);
} catch e: DemoError {
writeln("caught a DemoError");
}
}
.. _Errors_defer:
Defer statement
---------------
When an error is thrown, it is sometimes necessary to clean up state and
allocated memory. ``defer`` statements facilitate that by running when a
scope is exited, regardless of how it is exited.
*Example (defer.chpl)*.
::
proc deferredDelete(i: int) {
try {
var huge = allocateLargeObject();
defer {
delete huge;
writeln("huge has been deleted");
}
canThrow(i);
processObject(huge);
} catch {
writeln("no memory leaks");
}
}
It is not possible to throw errors out of a ``defer`` statement because
the atomicity of all ``defer`` statements must be guaranteed, and the
handling context would be unclear.
Errors also cannot be thrown by ``deinit()`` for similar reasons.
.. _Errors_Methods:
Methods
-------
Errors can be thrown by methods, just as with any other function. An
overriding method must throw if the overridden method throws, or not
throw if the overridden method does not throw.
*Example (throwing-methods.chpl)*.
::
class ThrowingObject {
proc f() throws {
throw new owned Error();
}
}
class SubThrowingObject : ThrowingObject {
// must be marked throws even though it doesn't throw
proc f() throws {
writeln("this version doesn't throw");
}
}
.. _Errors_Multilocale:
Multilocale
-----------
Errors can be thrown within ``on`` statements. In that event, the error
will be propagated out of the ``on`` statement.
*Example (handle-from-on.chpl)*.
::
proc handleFromOn() {
try {
on Locales[0] {
canThrow(1);
}
} catch {
writeln("caught from Locale 0");
}
}
.. _Errors_Parallelism:
Parallelism
-----------
.. _TaskErrors:
TaskErrors
~~~~~~~~~~
``TaskErrors`` class helps coordinate errors among groups of tasks by
collecting them for centralized handling. It can be iterated on and
filtered for different kinds of errors. See also
https://chapel-lang.org/docs/builtins/ChapelError.html#ChapelError.TaskErrors.
Nested ``coforall`` statements do not produce nested ``TaskErrors``.
Instead, the nested errors are flattened into the ``TaskErrors`` error
thrown by the outer loop.
.. _Errors_begin:
begin
~~~~~
Errors can be thrown within a ``begin`` statement. In that event, the
error will be propagated to the ``sync`` statement that waits for that
task.
*Example (handle-from-begin.chpl)*.
::
proc handleFromBegin() {
try! {
sync {
begin canThrow(0);
begin canThrow(1);
}
} catch e: TaskErrors {
writeln("caught from Locale 0");
}
}
.. _Errors_coforall_and_cobegin:
coforall and cobegin
~~~~~~~~~~~~~~~~~~~~
Errors can be thrown from ``coforall`` and ``cobegin`` statements and
handled as ``TaskErrors``. The nested ``coforall`` loops will emit a
flattened ``TaskErrors`` error.
*Example (handle-from-coforall.chpl)*.
::
proc handleFromCoforall() {
try! {
writeln("before coforall block");
coforall i in 1..2 {
coforall j in 1..2 {
throw new owned DemoError();
}
}
writeln("after coforall block");
} catch errors: TaskErrors { // not nested
// all of e will be of runtime type DemoError in this example
for e in errors {
writeln("Caught task error e ", e.message());
}
}
}
..
*Example (handle-from-cobegin.chpl)*.
::
proc handleFromCobegin() {
try! {
writeln("before cobegin block");
cobegin {
throw new owned DemoError();
throw new owned DemoError();
}
writeln("after cobegin block");
} catch errors: TaskErrors {
for e in errors {
writeln("Caught task error e ", e.message());
}
}
}
.. _Errors_forall:
forall
~~~~~~
Errors can be thrown from ``forall`` loops, too. Although the ``forall``
may execute serially within a single task, it will always throw a
``TaskErrors`` error if error(s) are thrown in the loop body.
*Example (handle-from-forall.chpl)*.
::
proc handleFromForall() {
try! {
writeln("before forall block");
forall i in 1..2 {
throw new owned DemoError();
}
writeln("after forall block");
} catch errors: TaskErrors {
for e in errors {
writeln("Caught task error e ", e.message());
}
}
}
.. _Creating_New_Error_Types:
Creating New Error Types
------------------------
Errors in Chapel are implemented as classes, with a base class ``Error``
defined in the standard modules. ``Error`` may be used directly, and new
subclass hierarchies may be created from it. See also
https://chapel-lang.org/docs/builtins/ChapelError.html.
A hierarchy for system errors is included in the ``SysError`` module,
accessed with a ``use`` statement. See also
https://chapel-lang.org/docs/modules/standard/SysError.html
*Example (defining-errors.chpl)*.
::
use SysError;
class DemoError : Error { }
class DemoSysError : SystemError { }
.. _Error_Handling_Modes:
Error Handling Modes
--------------------
Certain error handling details depend on the *error handling mode*:
- Code in ``prototype`` modules (§`12.2 <#Prototype_Modules>`__),
including implicit modules (§`12.3 <#Implicit_Modules>`__), is
handled in the *prototype* mode.
- Otherwise, code is handled in the *production* mode.
Code that is legal in the production mode is always legal in the
prototype mode.
.. _Errors_Prototype_Mode:
Prototype Mode
~~~~~~~~~~~~~~
In the prototype mode, it is not necessary to explicitly handle errors
from a function that throws. If an error is thrown and the calling
function throws, the error will be propagated out of the function.
However, if an error is thrown and the calling function does not include
a ``throws`` declaration, the program will halt.
In the following example, the code is in an implicit module. It is legal
in the prototype mode:
*Example (fatal-mode.chpl)*.
::
canThrow(1); // handling can be omitted; halts if an error occurs
proc throwsErrorsOn() throws {
// error propagates out of this function
canThrow(-1);
}
proc doesNotThrowErrorsOn() {
// causes a halt if called
alwaysThrows();
}
::
proc canThrow(i: int): int throws {
if i < 0 then
throw new owned Error();
return i + 1;
}
The following module is explicitly marked as a prototype module, so the
prototype mode applies here, too.
*Example (PrototypeModule.chpl)*.
::
prototype module PrototypeModule {
canThrow(1); // handling can be omitted; halts if an error occurs
proc throwsErrorsOn() throws {
// error propagates out of this function
alwaysThrows();
}
proc doesNotThrowErrorsOn() {
// causes a halt if called
alwaysThrows();
}
proc canThrow(i: int): int throws {
if i < 0 then
throw new owned Error();
return i + 1;
}
}
.. _Production_Mode_for_Explicit_Modules:
Production Mode
~~~~~~~~~~~~~~~
In the production mode, it is necessary to handle errors if the calling
function does not throw. If the calling function does throw, then the
error will be propagated out, as with the prototype mode.
*Example (ProductionModule.chpl)*.
::
module ProductionModule {
// This would cause a compilation error since the error is not handled:
// canThrow(1);
proc throwsErrorsOn() throws {
// any error thrown by alwaysThrows will propagate out
alwaysThrows();
}
// this function does not compile because the error is not handled
// proc doesNotThrowErrorsOn() {
// alwaysThrows();
// }
}
Tuples
======
[Tuples]
A tuple is an ordered set of components that allows for the
specification of a light-weight collection of values. As the examples in
this chapter illustrate, tuples are a boon to the Chapel programmer. In
addition to making it easy to return multiple values from a function,
tuples help to support multidimensional indices, to group arguments to
functions, and to specify mathematical concepts.
.. _Tuple_Types:
Tuple Types
-----------
A tuple type is defined by a fixed number (a compile-time constant) of
component types. It can be specified by a parenthesized, comma-separated
list of types. The number of types in the list defines the size of the
tuple; the types themselves specify the component types.
The syntax of a tuple type is given by:
::
tuple-type:
( type-expression , type-list )
( type-expression , )
type-list:
type-expression
type-expression , type-list
A homogeneous tuple is a special-case of a general tuple where the types
of the components are identical. Homogeneous tuples have fewer
restrictions for how they can be
indexed (§\ `16.3 <#Tuple_Indexing>`__). Homogeneous tuple types can be
defined using the above syntax, or they can be defined as a product of
an integral parameter (a compile-time constant integer) and a type.
*Rationale*.
Homogeneous tuples require the size to be specified as a parameter (a
compile-time constant). This avoids any overhead associated with
storing the runtime size in the tuple. It also avoids the question as
to whether a non-parameter size should be part of the type of the
tuple. If a programmer requires a non-parameter value to define a
data structure, an array may be a better choice.
..
*Example (homogeneous.chpl)*.
The statement
::
var x1: (string, real),
x2: (int, int, int),
x3: 3*int;
defines three variables. Variable ``x1`` is a 2-tuple with component
types ``string`` and ``real``. Variables ``x2`` and ``x3`` are
homogeneous 3-tuples with component type ``int``. The types of ``x2``
and ``x3`` are identical even though they are specified in different
ways.
::
writeln((x1,x2,x3));
::
((, 0.0), (0, 0, 0), (0, 0, 0))
Note that if a single type is delimited by parentheses, the parentheses
only impact precedence. Thus ``(int)`` is equivalent to ``int``.
Nevertheless, tuple types with a single component type are legal and
useful. One way to specify a 1-tuple is to use the overloaded ``*``
operator since every 1-tuple is trivially a homogeneous tuple.
*Rationale*.
Like parentheses around expressions, parentheses around types are
necessary for grouping in order to avoid the default precedence of
the grammar. Thus it is not the case that we would always want to
create a tuple. The type ``3*(3*int)`` specifies a 3-tuple of
3-tuples of integers rather than a 3-tuple of 1-tuples of 3-tuples of
integers. The type ``3*3*int``, on the other hand, specifies a
9-tuple of integers.
.. _Tuple_Values:
Tuple Values
------------
A value of a tuple type attaches a value to each component type. Tuple
values can be specified by a parenthesized, comma-separated list of
expressions. The number of expressions in the list defines the size of
the tuple; the types of these expressions specify the component types of
the tuple. A trailing comma is allowed.
The syntax of a tuple expression is given by:
::
tuple-expression:
( tuple-component , )
( tuple-component , tuple-component-list )
( tuple-component , tuple-component-list , )
tuple-component:
expression
`_'
tuple-component-list:
tuple-component
tuple-component , tuple-component-list
An underscore can be used to omit components when splitting a tuple (see
`16.6.1 <#Assignments_in_a_Tuple>`__).
*Example (values.chpl)*.
The statement
::
var x1: (string, real) = ("hello", 3.14),
x2: (int, int, int) = (1, 2, 3),
x3: 3*int = (4, 5, 6);
defines three tuple variables. Variable ``x1`` is a 2-tuple with
component types ``string`` and ``real``. It is initialized such that
the first component is ``"hello"`` and the second component is
``3.14``. Variables ``x2`` and ``x3`` are homogeneous 3-tuples with
component type ``int``. Their initialization expressions specify
3-tuples of integers.
::
writeln((x1,x2,x3));
::
((hello, 3.14), (1, 2, 3), (4, 5, 6))
Note that if a single expression is delimited by parentheses, the
parentheses only impact precedence. Thus ``(1)`` is equivalent to ``1``.
To specify a 1-tuple, use the form with the trailing comma ``(1,)``.
*Example (onetuple.chpl)*.
The statement
::
var x: 1*int = (7,);
creates a 1-tuple of integers storing the value 7.
::
writeln(x);
::
(7)
Tuple expressions are evaluated similarly to function calls where the
arguments are all generic with no explicit intent. So a tuple expression
containing an array does not copy the array.
When a tuple is passed as an argument to a function, it is passed as if
it is a record type containing fields of the same type and in the same
order as in the tuple.
.. _Tuple_Indexing:
Tuple Indexing
--------------
A tuple component may be accessed by an integral parameter (a
compile-time constant) as if the tuple were an array. Indexing is
1-based, so the first component in the tuple is accessed by the index
``1``, and so forth.
*Example (access.chpl)*.
The loop
::
var myTuple = (1, 2.0, "three");
for param i in 1..3 do
writeln(myTuple(i));
uses a param loop to output the components of a tuple.
::
1
2.0
three
Homogeneous tuples may be accessed by integral values that are not
necessarily compile-time constants.
*Example (access-homogeneous.chpl)*.
The loop
::
var myHTuple = (1, 2, 3);
for i in 1..3 do
writeln(myHTuple(i));
uses a serial loop to output the components of a homogeneous tuple.
Since the index is not a compile-time constant, this would result in
an error were tuple not homogeneous.
::
1
2
3
..
*Rationale*.
Non-homogeneous tuples can only be accessed by compile-time constants
since the type of an expression must be statically known.
.. _Iteration_over_Tuples:
Iteration over Tuples
---------------------
Only homogeneous tuples support iteration via standard ``for``,
``forall`` and ``coforall`` loops. These loops iterate over all of the
tuple’s elements. A loop of the form:
::
[for|forall|coforall] e in t do
...e...
where t is a homogeneous tuple of size ``n``, is semantically equivalent
to:
::
[for|forall|coforall] i in 1..n do
...t(i)...
The iterator variable for an tuple iteration is a either a const value
or a reference to the tuple element type, following default intent
semantics.
.. _Tuple_Assignment:
Tuple Assignment
----------------
In tuple assignment, the components of the tuple on the left-hand side
of the assignment operator are each assigned the components of the tuple
on the right-hand side of the assignment. These assignments occur in
component order (component one followed by component two, etc.).
.. _Tuple_Destructuring:
Tuple Destructuring
-------------------
Tuples can be split into their components in the following ways:
- In assignment where multiple expression on the left-hand side of the
assignment operator are grouped using tuple notation.
- In variable declarations where multiple variables in a declaration
are grouped using tuple notation.
- In for, forall, and coforall loops (statements and expressions) where
multiple indices in a loop are grouped using tuple notation.
- In function calls where multiple formal arguments in a function
declaration are grouped using tuple notation.
- In an expression context that accepts a comma-separated list of
expressions where a tuple expression is expanded in place using the
tuple expansion expression.
.. _Assignments_in_a_Tuple:
Splitting a Tuple with Assignment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When multiple expression on the left-hand side of an assignment operator
are grouped using tuple notation, the tuple on the right-hand side is
split into its components. The number of grouped expressions must be
equal to the size of the tuple on the right-hand side. In addition to
the usual assignment evaluation order of left to right, the assignment
is evaluated in component order.
*Example (splitting.chpl)*.
The code
::
var a, b, c: int;
(a, (b, c)) = (1, (2, 3));
defines three integer variables ``a``, ``b``, and ``c``. The second
line then splits the tuple ``(1, (2, 3))`` such that ``1`` is
assigned to ``a``, ``2`` is assigned to ``b``, and ``3`` is assigned
to ``c``.
::
writeln((a, b, c));
::
(1, 2, 3)
..
*Example (aliasing.chpl)*.
The code
::
var A = [i in 1..4] i;
writeln(A);
(A(1..2), A(3..4)) = (A(3..4), A(1..2));
writeln(A);
creates a non-distributed, one-dimensional array containing the four
integers from ``1`` to ``4``. Line 2 outputs ``1 2 3 4``. Line 3 does
what appears to be a swap of array slices. However, because the tuple
is created with array aliases (like a function call), the assignment
to the second component uses the values just overwritten in the
assignment to the first component. Line 4 outputs ``3 4 3 4``.
::
1 2 3 4
3 4 3 4
When splitting a tuple with assignment, the underscore token can be used
to omit storing some of the components. In this case, the full
expression on the right-hand side of the assignment operator is
evaluated, but the omitted values will not be assigned to anything.
*Example (omit-component.chpl)*.
The code
::
proc f()
return (1, 2);
var x: int;
(x,_) = f();
defines a function that returns a 2-tuple, declares an integer
variable ``x``, calls the function, assigns the first component in
the returned tuple to ``x``, and ignores the second component in the
returned tuple. The value of ``x`` becomes ``1``.
::
writeln(x);
::
1
.. _Variable_Declarations_in_a_Tuple:
Splitting a Tuple in a Declaration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When multiple variables in a declaration are grouped using tuple
notation, the tuple initialization expression is split into its type
and/or value components. The number of grouped variables must be equal
to the size of the tuple initialization expression. The variables are
initialized in component order.
The syntax of grouped variable declarations is defined
in §\ `8.1 <#Variable_Declarations>`__.
*Example (decl.chpl)*.
The code
::
var (a, (b, c)) = (1, (2, 3));
defines three integer variables ``a``, ``b``, and ``c``. It splits
the tuple ``(1, (2, 3))`` such that ``1`` initializes ``a``, ``2``
initializes ``b``, and ``3`` initializes ``c``.
::
writeln((a, b, c));
::
(1, 2, 3)
Grouping variable declarations using tuple notation allows a 1-tuple to
be destructured by enclosing a single variable declaration in
parentheses.
*Example (onetuple-destruct.chpl)*.
The code
::
var (a) = (1, );
initialize the new variable ``a`` to 1.
::
writeln(a);
::
1
When splitting a tuple into multiple variable declarations, the
underscore token may be used to omit components of the tuple rather than
declaring a new variable for them. In this case, no variables are
defined for the omitted components.
*Example (omit-component-decl.chpl)*.
The code
::
proc f()
return (1, 2);
var (x,_) = f();
defines a function that returns a 2-tuple, calls the function,
declares and initializes variable ``x`` to the first component in the
returned tuple, and ignores the second component in the returned
tuple. The value of ``x`` is initialized to ``1``.
::
writeln(x);
::
1
.. _Indices_in_a_Tuple:
Splitting a Tuple into Multiple Indices of a Loop
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When multiple indices in a loop are grouped using tuple notation, the
tuple returned by the iterator (§`[Iterators] <#Iterators>`__) is split
across the index tuple’s components. The number of indices in the index
tuple must equal the size of the tuple returned by the iterator.
*Example (indices.chpl)*.
The code
::
iter bar() {
yield (1, 1);
yield (2, 2);
}
for (i,j) in bar() do
writeln(i+j);
defines a simple iterator that yields two 2-tuples before completing.
The for-loop uses a tuple notation to group two indices that take
their values from the iterator.
::
2
4
When a tuple is split across an index tuple, indices in the index tuple
(left-hand side) may be omitted. In this case, no indices are defined
for the omitted components.
However even when indices are omitted, the iterator is evaluated as if
an index were defined. Execution proceeds as if the omitted indices are
present but invisible. This means that the loop body controlled by the
iterator may be executed multiple times with the same set of (visible)
indices.
.. _Formal_Argument_Declarations_in_a_Tuple:
Splitting a Tuple into Multiple Formal Arguments in a Function Call
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When multiple formal arguments in a function declaration are grouped
using tuple notation, the actual expression is split into its components
during a function call. The number of grouped formal arguments must be
equal to the size of the actual tuple expression. The actual arguments
are passed in component order to the formal arguments.
The syntax of grouped formal arguments is defined
in §\ `13.2 <#Function_Definitions>`__.
*Example (formals.chpl)*.
The function
::
proc f(x: int, (y, z): (int, int)) {
// body
}
is defined to take an integer value and a 2-tuple of integer values.
The 2-tuple is split when the function is called into two formals. A
call may look like the following:
::
f(1, (2, 3));
An implicit ``where`` clause is created when arguments are grouped using
tuple notation, to ensure that the function is called with an actual
tuple of the correct size. Arguments grouped in tuples may be nested
arbitrarily. Functions with arguments grouped into tuples may not be
called using named-argument passing on the tuple-grouped arguments. In
addition, tuple-grouped arguments may not be specified individually with
types or default values (only in aggregate). They may not be specified
with any qualifier appearing before the group of arguments (or
individual arguments) such as ``inout`` or ``type``. They may not be
followed by ``...`` to indicate that there are a variable number of
them.
*Example (implicit-where.chpl)*.
The function ``f`` defined as
::
proc f((x, (y, z))) {
writeln((x, y, z));
}
is equivalent to the function ``g`` defined as
::
proc g(t) where isTuple(t) && t.size == 2 && isTuple(t(2)) && t(2).size == 2 {
writeln((t(1), t(2)(1), t(2)(2)));
}
except without the definition of the argument name ``t``.
::
f((1, (2, 3)));
g((1, (2, 3)));
::
(1, 2, 3)
(1, 2, 3)
Grouping formal arguments using tuple notation allows a 1-tuple to be
destructured by enclosing a single formal argument in parentheses.
*Example (grouping-Formals.chpl)*.
The empty function
::
proc f((x)) { }
accepts a 1-tuple actual with any component type.
::
f((1, ));
var y: 1*real;
f(y);
When splitting a tuple into multiple formal arguments, the arguments
that are grouped using the tuple notation may be omitted. In this case,
no names are associated with the omitted components. The call is
evaluated as if an argument were defined.
.. _Tuple_Expansion:
Splitting a Tuple via Tuple Expansion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tuples can be expanded in place using the following syntax:
::
tuple-expand-expression:
( ... expression )
In this expression, the tuple defined by ``expression`` is expanded in
place to represent its components. This can only be used in a context
where a comma-separated list of components is valid.
*Example (expansion.chpl)*.
Given two 2-tuples
::
var x1 = (1, 2.0), x2 = ("three", "four");
the following statement
::
var x3 = ((...x1), (...x2));
creates the 4-tuple ``x3`` with the value
``(1, 2.0, "three", "four")``.
::
writeln(x3);
::
(1, 2.0, three, four)
..
*Example (expansion-2.chpl)*.
The following code defines two functions, a function ``first`` that
returns the first component of a tuple and a function ``rest`` that
returns a tuple containing all of the components of a tuple except
for the first:
::
proc first(t) where isTuple(t) {
return t(1);
}
proc rest(t) where isTuple(t) {
proc helper(first, rest...)
return rest;
return helper((...t));
}
::
writeln(first((1, 2, 3)));
writeln(rest((1, 2, 3)));
::
1
(2, 3)
.. _Tuple_Operators:
Tuple Operators
---------------
.. _Tuple_Unary_Operators:
Unary Operators
~~~~~~~~~~~~~~~
The unary operators ``+``, ``-``, ``\~``, and ````! are overloaded on
tuples by applying the operator to each argument component and returning
the results as a new tuple.
The size of the result tuple is the same as the size of the argument
tuple. The type of each result component is the result type of the
operator when applied to the corresponding argument component.
The type of every element of the operand tuple must have a well-defined
operator matching the unary operator being applied. That is, if the
element type is a user-defined type, it must supply an overloaded
definition for the unary operator being used. Otherwise, a compile-time
error will be issued.
.. _Tuple_Binary_Operators:
Binary Operators
~~~~~~~~~~~~~~~~
The binary operators ``+``, ``-``, ``*``, ``/``, ``\%``, ``**``, ``\&``,
``|``, ``^``, ``<<``, and ``>>`` are overloaded on tuples by applying
them to pairs of the respective argument components and returning the
results as a new tuple. The sizes of the two argument tuples must be the
same. These operators are also defined for homogeneous tuples and scalar
values of matching type.
The size of the result tuple is the same as the argument tuple(s). The
type of each result component is the result type of the operator when
applied to the corresponding pair of the argument components.
When a tuple binary operator is used, the same operator must be
well-defined for successive pairs of operands in the two tuples.
Otherwise, the operation is illegal and a compile-time error will
result.
*Example (binary-ops.chpl)*.
The code
::
var x = (1, 1, "1") + (2, 2.0, "2");
creates a 3-tuple of an int, a real and a string with the value
``(3, 3.0, "12")``.
::
writeln(x);
::
(3, 3.0, 12)
.. _Tuple_Relational_Operators:
Relational Operators
~~~~~~~~~~~~~~~~~~~~
The relational operators ``>``, ``>=``, ``<``, ``<=``, ``==``, and
````\ =! are defined over tuples of matching size. They return a single
boolean value indicating whether the two arguments satisfy the
corresponding relation.
The operators ``>``, ``>=``, ``<``, and ``<=`` check the corresponding
lexicographical order based on pair-wise comparisons between the
argument tuples’ components. The operators ``==`` and ````\ =! check
whether the two arguments are pair-wise equal or not. The relational
operators on tuples may be short-circuiting, i.e. they may execute only
the pair-wise comparisons that are necessary to determine the result.
However, just as for other binary tuple operators, the corresponding
operation must be well-defined on each successive pair of operand types
in the two operand tuples. Otherwise, a compile-time error will result.
*Example (relational-ops.chpl)*.
The code
::
var x = (1, 1, 0) > (1, 0, 1);
creates a variable initialized to ``true``. After comparing the first
components and determining they are equal, the second components are
compared to determine that the first tuple is greater than the second
tuple.
::
writeln(x);
::
true
.. _Predefined_Functions_and_Methods_on_Tuples:
Predefined Functions and Methods on Tuples
------------------------------------------
::
proc isHomogeneousTuple(t: tuple) param
Returns true if ``t`` is a homogeneous tuple; otherwise false.
::
proc isTuple(t: $Tuple$) param
Returns true if ``t`` is a tuple; otherwise false.
proc isTupleType(type t) param
Returns true if ``t`` is a tuple of types; otherwise false.
::
proc max(type t) where isTupleType(t)
Returns a tuple of type ``t`` with each component set to the maximum
value that can be stored in its position.
::
proc min(type t) where isTupleType(t)
Returns a tuple of type ``t`` with each component set to the minimum
value that can be stored in its position.
::
proc Tuple.size param
Returns the size of the tuple.
Classes
=======
[Classes]
Classes are data structures with associated state and functions. A
variable of class type either refers to a class instance, or contains a
special ``nil`` value. Note that *object* is another name for a class
instance. Storage for a class instance is not necessarily tied to the
scope of the variable(s) referring to that class instance. It is
possible for multiple variables to refer to the same class instance.
The ``new-expression`` can be used to create an instance of a class
(§`17.3 <#Class_New>`__). Depending on the memory management strategy, a
class instance is either deleted automatically or can be deleted using
the ``delete-statement`` (§`17.8 <#Class_Delete>`__).
A class declaration (§`17.1 <#Class_Declarations>`__) generates a class
type (§`17.1.2 <#Class_Types>`__). A variable of a class type can refer
to an instance of that class or any of its derived classes.
A class is generic if it has generic fields. A class can also be generic
if it inherits from a generic class. Generic classes and fields are
discussed in §\ `24.3 <#Generic_Types>`__.
.. _Class_Declarations:
Class Declarations
------------------
A class is defined with the following syntax:
::
class-declaration-statement:
`class' identifier class-inherit[OPT] { class-statement-list[OPT] }
class-inherit:
: basic-class-type
class-statement-list:
class-statement
class-statement class-statement-list
class-statement:
variable-declaration-statement
method-declaration-statement
type-declaration-statement
empty-statement
A ``class-declaration-statement`` defines a new class type symbol
specified by the identifier. It inherits from the class specified in the
``class-inherit`` clause, when provided (§`17.2 <#Inheritance>`__).
The body of a class declaration consists of a sequence of statements
where each of the statements either defines a variable (called a field),
a procedure or iterator (called a method), or a type alias. In addition,
empty statements are allowed in class declarations, and they have no
effect.
If a class declaration contains a type alias or a parameter field, or it
contains a variable or constant without a specified type and without an
initialization expression, then it declares a generic class type.
Generic classes are described in §\ `24.3 <#Generic_Types>`__.
*Future*.
Privacy controls for classes and records are currently not specified,
as discussion is needed regarding its impact on inheritance, for
instance.
.. _Class_Lifetime_and_Borrows:
Class Lifetime and Borrows
~~~~~~~~~~~~~~~~~~~~~~~~~~
The lifetime of a class instance is the time between its creation and
its deletion. It is legal to access the class fields or methods only
during its lifetime.
Each allocation of a class instance specifies a *memory management
strategy*. Four memory management strategies are available: ``owned``,
``shared``, ``borrowed``, and ``unmanaged``.
| ``owned`` and ``shared`` class instances always have their lifetime
managed by the compiler. In other words, the compiler automatically
calls ``delete`` on these instances to reclaim their memory. For these
instances, ``=`` and copy initialization can result in the transfer or
sharing of ownership. See
| https://chapel-lang.org/docs/builtins/OwnedObject.html
| and
| https://chapel-lang.org/docs/builtins/SharedObject.html
| When ``borrowed`` is used as a memory management strategy in a
``new-expression``, it also creates an instance that has its lifetime
managed by the compiler (§`17.3 <#Class_New>`__).
Class instances that are ``unmanaged`` have their lifetime managed
explicitly and ``delete`` must be used to reclaim their memory.
No matter the memory management strategy used, class types support
*borrowing*. A ``borrowed`` class instance refers to the same class
instance as another variable but has no impact on the lifetime of that
instance. The process of getting such a reference to an instance is
called *borrowing*.
There are several ways to borrow an instance. To borrow explicitly the
instance managed by another variable, call the ``.borrow()`` method.
Additionally, coercions are available that are equivalent to calling the
``.borrow()`` method. For example:
*Example (borrowing.chpl)*.
::
class C { }
proc test() {
var own = new owned C(); // 'own' manages the memory of the instance
var b = own.borrow(); // 'b' refers to the same instance but has no
// impact on the lifetime.
var bc: borrowed C = own; // 'bc' stores the result of own.borrow()
// due to coercion from owned C to C
var c: C = own; // same as above
// since 'C' is equivalent to 'borrowed C'
// Note that these coercions can also apply
// in the context of procedure calls.
// the instance referred to by 'own' is
// deleted here, at the end of the containing
// block.
}
::
test();
The ``.borrow()`` method is available on all class types (including
``unmanaged`` and ``borrowed``) in order to support generic programming.
For nilable class types, it returns the borrowed nilable class type.
Errors due to accessing an instance after the end of its lifetime are
particularly difficult to debug. For this reason, the compiler includes
a component called the lifetime checker. It identifies some cases where
a borrowing variable can be accessed beyond the lifetime of an instance
it refers to.
*Future*.
The details of lifetime checking are not yet finalized or specified.
Additional syntax to specify the lifetimes of function returns will
probably be needed.
.. _Class_Types:
Class Types
~~~~~~~~~~~
A class type is formed by the combination of a basic class type and a
memory management strategy.
::
class-type:
basic-class-type
`owned' basic-class-type
`shared' basic-class-type
`borrowed' basic-class-type
`unmanaged' basic-class-type
A basic class type is given simply by the class name for non-generic
classes. Generic classes must be instantiated to serve as a
fully-specified type, for example to declare a variable. This is done
with type constructors, which are defined in
Section \ `24.3.6 <#Type_Constructors>`__.
::
basic-class-type:
identifier
identifier ( named-expression-list )
A basic class type, including a generic class type that is not fully
specified, may appear in the inheritance lists of other class
declarations.
If no memory management strategy is indicated, the class will be
considered to have generic management.
Variables of class type cannot store ``nil`` unless the class type is
nilable (§`17.1.3 <#Nilable_Classes>`__).
The memory management strategies have the following meaning:
- | ``owned`` the instance will be deleted automatically when the
``owned`` variable goes out of scope, but only one ``owned``
variable can refer to the instance at a time. See
| https://chapel-lang.org/docs/builtins/OwnedObject.html
- | ``shared`` will be deleted when all of the ``shared`` variables
referring to the instance go out of scope. See
| https://chapel-lang.org/docs/builtins/SharedObject.html.
- ``borrowed`` refers to a class instance that has a lifetime managed
by another variable.
- ``unmanaged`` the instance must have ``delete`` called on it
explicitly to reclaim its memory.
It is an error to apply more than one memory management strategy to a
class type. However, in some cases, generic code needs to compute a
variant of the class type using a different memory management strategy.
Casts from the class type to a different memory management strategy are
available for this purpose
(see §`9.2.4 <#Explicit_Class_Conversions>`__).
*Example (duplicate-management.chpl)*.
::
class C { }
var x: borrowed unmanaged C;
::
duplicate-management.chpl:2: error: Type expression uses multiple class kinds: borrowed unmanaged
..
*Example (changing-management.chpl)*.
::
class C { }
type borrowedC = borrowed C;
type ownedC = (borrowedC:owned);
::
writeln(borrowedC:string);
writeln(ownedC:string);
::
borrowed C
owned C
.. _Nilable_Classes:
Nilable Class Types
~~~~~~~~~~~~~~~~~~~
Variables of class type cannot store ``nil`` unless the class type is
nilable. To declare a nilable class type, use the ``?`` operator to
create a nilable type. For example, if ``C`` is a class type, then
``C?`` indicates the nilable class type with generic management. The
``?`` operator can be combined with memory management specifiers as
well. For example, ``borrowed C?`` indicates a nilable class using the
``borrowed`` memory management strategy. Note that the ``?`` operator
applies only to types.
The postfix ````! operator applies to a type or a value. When applied to
a ``borrowed`` or ``unmanaged`` type, it returns the non-nilable version
of that type. When applied to an ``owned`` or ``shared`` type, it
returns the non-nilable borrowed type. When applied to a value, it
asserts that the value is not ``nil`` and returns that value as a
non-nilable type. If the value was in fact ``nil``, it halts. It returns
the borrowed type for ``owned`` or ``shared``.
An alternative to ````! is to use a cast to a non-nilable type. Such a
cast will throw ``NilClassError`` if the value was in fact ``nil``.
See §\ `9.2.4 <#Explicit_Class_Conversions>`__.
Non-nilable class types are implicitly convertible to nilable class
types. See §\ `9.1.3 <#Implicit_Class_Conversions>`__.
Class methods generally expect a receiver of type ``borrowed C``
(see §`17.1.7 <#Class_Methods>`__). Since such a class method call might
involve dynamic dispatch, it is a program error to call a class method
on a class receiver storing ``nil``. The language helps to identify this
error in two different ways, depending on whether or not the module is a
``prototype`` module (§`12.2 <#Prototype_Modules>`__).
For modules that are not prototype modules, the compiler will not
resolve calls to class methods if the receiver has nilable type. If the
programmer knows that the receiver cannot store ``nil`` at that moment,
they can use ````! to assert that the receiver is not ``nil`` and to
convert it to the non-nilable borrowed type. For example:
*Example (nilable-classes-bang.chpl)*.
::
class C {
proc method() { }
}
var c: owned C? = new C();
// The following call is allowed only in prototype modules,
// in which case it is equivalent to c!.method()
c.method();
// This pattern is appropriate in libraries
if c != nil {
c!.method(); // c! converts from 'owned C?' to 'borrowed C'
}
The ``borrow()`` method is an exception. Suppose it is invoked on an
expression of a class type ``C``. It will return ``borrowed C`` for any
non-nilable ``C`` type (e.g. ``owned C``). It will return
``borrowed C?`` for any nilable ``C`` type (e.g. ``C?``).
[Methods_On_Nilable_In_Prototype_Modules]
Within a ``prototype`` module, the compiler will implicitly convert a
nilable method receiver to the non-nilable type by adding a ````! call.
.. _Class_Values:
Class Values
~~~~~~~~~~~~
A class value is either a reference to an instance of a class or ``nil``
(§`17.1.5 <#Class_nil_value>`__). Class instances can be created using a
``new`` expression (§`17.3 <#Class_New>`__).
For a given class type, a legal value of that type is a reference to an
instance of either that class or a class inheriting, directly or
indirectly, from that class. ``nil`` is a legal value of any non-nilable
class type.
The default value of a concrete nilable class type is ``nil``. Generic
class types and non-nilable class types do not have a default value.
*Example (declaration.chpl)*.
::
class C { }
var c : owned C?; // c has class type owned C?, meaning
// the instance can be nil and is deleted automatically
// when it is not.
c = new C(); // Now c refers to an initialized instance of type C.
var c2 = c.borrow(); // The type of c2 is borrowed C?.
// c2 refers to the same object as c.
class D : C {} // Class D is derived from C.
c = new D(); // Now c refers to an object of type D.
// Since c is owned, the previous is deleted.
// the C and D instances allocated above will be reclaimed
// at the end of this block.
::
--no-warnings
When the variable ``c`` is declared, it initially has the value of
``nil``. The next statement assigned to it an instance of the class
``C``. The declaration of variable ``c2`` shows that these steps can
be combined. The type of ``c2`` is also ``borrowed C?``, determined
implicitly from the the initialization expression. Finally, an object
of type ``owned D`` is created and assigned to ``c``.
.. _Class_nil_value:
The *nil* Value
~~~~~~~~~~~~~~~
Chapel provides ``nil`` to indicate the absence of a reference to any
object. Invoking a class method or accessing a field of the ``nil``
value results in a run-time or compile-time error.
``nil`` can be assigned to a variable of any nilable class type. There
is a restriction for using ``nil`` as the default value or the actual
argument of a function formal, or as the initializer for a variable or a
field. Such a use is disallowed when the declared type of the
formal/variable/field is non-nilable or generic, including generic
memory management.
::
nil-expression:
`nil'
.. _Class_Fields:
Class Fields
~~~~~~~~~~~~
A variable declaration within a class declaration defines a *field*
within that class. Each class instance consists of one variable per each
``var`` or ``const`` field in the class.
*Example (defineActor.chpl)*.
The code
::
config param cleanUp = false;
::
class Actor {
var name: string;
var age: uint;
}
defines a new class type called ``Actor`` that has two fields: the
string field ``name`` and the unsigned integer field ``age``.
Field access is described in §\ `17.5 <#Class_Field_Accesses>`__.
*Future*.
``ref`` fields, which are fields corresponding to variable
declarations with ``ref`` or ``const ref`` keywords, are an area of
future work.
.. _Class_Methods:
Class Methods
~~~~~~~~~~~~~
Methods on classes are referred to as to as *class methods*. See the
methods section §\ `[Methods] <#Methods>`__ for more information about
methods.
Within a class method, the type of ``this`` is generally the non-nilable
``borrowed`` variant of the class type. It is different for type methods
(see below) and it might be a different type if the class method is
declared as a secondary method with a type expression
(see `[Secondary_Methods_with_Type_Expressions] <#Secondary_Methods_with_Type_Expressions>`__).
For example:
*Example (class-method-this-type.chpl)*.
::
class C {
proc primaryMethod() {
assert(this.type == borrowed C);
}
}
proc C.secondaryMethod() {
assert(this.type == borrowed C);
}
proc (owned C?).secondaryMethodWithTypeExpression() {
assert(this.type == owned C?);
}
var x:owned C? = new owned C();
x!.primaryMethod(); // within the method, this: borrowed C
x!.secondaryMethod(); // within the method, this: borrowed C
x.secondaryMethodWithTypeExpression(); // within the method, this: owned C?
For type methods on a class, ``this`` will accept any management or
nilability variant of the class type and it will refer to that type in
the body of the method. For example:
*Example (class-type-method-this.chpl)*.
::
class C {
proc type typeMethod() {
writeln(this:string); // print out the type of 'this'
}
}
(C).typeMethod(); // prints 'C'
(owned C).typeMethod(); // prints 'owned C'
(borrowed C?).typeMethod(); // prints 'borrowed C?'
::
C
owned C
borrowed C?
When a type method is defined only in a parent class, the type will be
the corresponding variant of the parent class. For example:
*Example (class-type-method-inherit.chpl)*.
::
class Parent { }
class Child : Parent { }
proc type Parent.typeMethod() {
writeln(this:string); // print out the type 'this'
}
Child.typeMethod(); // prints 'Parent'
(borrowed Child?).typeMethod(); // prints 'borrowed Parent?'
::
Parent
borrowed Parent?
.. _Nested_Classes:
Nested Classes
~~~~~~~~~~~~~~
A class defined within another class or record is a nested class. A
nested class can be referenced only within its immediately enclosing
class or record.
.. _Inheritance:
Inheritance
-----------
A class inherits, or *derives*, from the class specified in the class
declaration’s ``class-inherit`` clause when such clause is present.
Otherwise the class inherits from the predefined ``object`` class
(§`17.2.1 <#The_object_Class>`__). In either case, there is exactly one
*parent* class. There can be many classes that inherit from a particular
parent class.
It is possible for a class to inherit from a generic class. Suppose for
example that a class ``C`` inherits from class ``ParentC``. In this
situation, ``C`` will have type constructor arguments based upon generic
fields in the ``ParentC`` as described in
 `24.3.6 <#Type_Constructors>`__. Furthermore, a fully specified ``C``
will be a subclass of a corresponding fully specified ``ParentC``.
.. _The_object_Class:
The *object* Class
~~~~~~~~~~~~~~~~~~
All classes are derived from the ``object`` class, either directly or
indirectly. If no class name appears in ``class-inherit`` clause, the
class derives implicitly from ``object``. Otherwise, a class derives
from ``object`` indirectly through the class it inherits. A variable of
type ``object`` can hold a reference to an object of any class type.
.. _Accessing_Base_Class_Fields:
Accessing Base Class Fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~
A derived class contains data associated with the fields in its base
classes. The fields can be accessed in the same way that they are
accessed in their base class unless a getter method is overridden in the
derived class, as discussed
in §\ `17.2.4 <#Overriding_Base_Class_Methods>`__.
.. _Shadowing_Base_Class_Fields:
Shadowing Base Class Fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~
A field in the derived class can be declared with the same name as a
field in the base class. Such a field shadows the field in the base
class in that it is always referenced when it is accessed in the context
of the derived class.
*Open issue*.
There is an expectation that there will be a way to reference the
field in the base class but this is not defined at this time.
.. _Overriding_Base_Class_Methods:
Overriding Base Class Methods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a method in a derived class is declared with a signature identical to
that of a method in a base class, then it is said to override the base
class’s method. Such methods are considered for dynamic dispatch. In
particular, dynamic dispatch will be used when the method receiver has a
static type of the base class but refers to an instance of a derived
class type.
In order to have identical signatures, two methods must have the same
the names, intents, types, and order of formal arguments. The return
type of the overriding method must either be the same as the return type
of the base class’s method or be a subclass of the base class method’s
return type.
Methods that override a base class method must be marked with the
``override`` keyword in the ``procedure-kind``. Additionally, methods
marked with ``override`` but for which there is no parent class method
with an identical signature will result in a compiler error.
*Rationale*.
This feature is designed to help avoid cases where class authors
accidentally override a method without knowing it; or fail to
override a method that they intended to due to not meeting the
identical signature condition.
Methods without parentheses are not candidates for dynamic dispatch.
*Rationale*.
Methods without parentheses are primarily used for field accessors. A
default is created if none is specified. The field accessor should
not dispatch dynamically since that would make it impossible to
access a base field within a base method should that field be
shadowed by a subclass.
.. _Class_New:
Class New
---------
To create an instance of a class, use a ``new`` expression. For example:
*Example (class-new.chpl)*.
::
class C {
var x: int;
}
var instance = new C(1);
::
--no-warnings
The new expression can be defined by the following syntax:
::
new-expression:
`new' type-expression ( argument-list )
An initializer for a given class is called by placing the ``new``
operator in front of a type expression. Any initializer arguments follow
the class name in a parenthesized list.
Syntactically, the ``type-expression`` includes ``owned``, ``shared``,
``borrowed``, and ``unmanaged``. However these have important
consequences for class new expressions. In particular, suppose ``C`` is
a ``type-expression`` that results in a class type. Then:
- ``new C()`` is the same as ``new owned C()``
- ``new owned C()`` allocates and initializes an instance that will be
deleted at the end of the current block unless it is transferred to
another ``owned`` variable. It results in something of type
``owned C``.
- ``new shared C()`` allocates and initializes the instance that will
be deleted when the last ``shared`` variable referring to it goes out
of scope. Results in something of type ``shared C``.
- ``new borrowed C()`` allocates and initializes an instance that will
be automatically deleted at the end of the current block. This
process is managed by an ``owned`` temporary. Unlike
``new owned C()``, this results in a value of type ``borrowed C`` and
ownership of the instance cannot be transferred out of the block. In
other words, ``new borrowed C()`` is equivalent to
::
(new owned C()).borrow()
- ``new unmanaged C()`` allocates and initializes an instance that must
have ``delete`` called on it explicitly to avoid a memory leak. It
results in something of type ``unmanaged C``.
See also §\ `17.1.1 <#Class_Lifetime_and_Borrows>`__ and
§\ `17.1.2 <#Class_Types>`__.
.. _Class_Initializers:
Class Initializers
------------------
A ``new`` expression allocates memory for the desired class and invokes
an *initializer* method on the uninitialized memory, passing any
arguments following the class name. An initializer is implemented by a
method named ``init`` and is responsible for initializing the fields of
the class.
Any initializers declared in a program are *user-defined* initializers.
If the program declares no initializers for a class, the compiler must
generate an initializer for that class based on the types and
initialization expressions of fields defined by that class.
.. _User_Defined_Initializers:
User-Defined Initializers
~~~~~~~~~~~~~~~~~~~~~~~~~
A user-defined initializer is an initializer method explicitly declared
in the program. An initializer declaration has the same syntax as a
method declaration, with the restrictions that the name of the method
must be ``init`` and there must not be a return type specifier. When an
initializer is called, the usual function resolution mechanism
(§`13.13 <#Function_Resolution>`__) is applied with the exception that
an initializer may not be virtually dispatched.
A user-defined initializer is responsible for initializing all fields.
An initializer may omit initialization of fields, but all fields that
are initialized must be initialized in declaration order.
Initializers for generic classes (§`24.3 <#Generic_Types>`__) handle
generic fields without default values differently and may need to
satisfy additional requirements. See
Section \ `24.3.9 <#Generic_User_Initializers>`__ for details.
*Example (simpleInitializers.chpl)*.
The following example shows a class with two initializers:
::
class MessagePoint {
var x, y: real;
var message: string;
proc init(x: real, y: real) {
this.x = x;
this.y = y;
this.message = "a point";
}
proc init(message: string) {
this.x = 0;
this.y = 0;
this.message = message;
}
} // class MessagePoint
// create two objects
var mp1 = new MessagePoint(1.0, 2.0);
var mp2 = new MessagePoint("point mp2");
::
writeln(mp1);
writeln(mp2);
::
--no-warnings
::
{x = 1.0, y = 2.0, message = a point}
{x = 0.0, y = 0.0, message = point mp2}
The first initializer lets the user specify the initial coordinates
and the second initializer lets the user specify the initial message
when creating a MessagePoint.
.. _Field_Initialization_Versus_Assignment:
Field Initialization Versus Assignment
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Within the body of an initializer, the first use of a field as the
left-hand side of an assignment statement will be considered
initialization. Subsequent uses of the assignment operator on the field
will invoke regular assignment as defined by the language.
*Example (fieldInitAssignment.chpl)*.
The following example documents the difference between field
initialization and field assignment.
::
class PointDoubleX {
var x, y : real;
proc init(x: real, y: real) {
this.x = x; // initialization
writeln("x = ", this.x); // use of initialized field
this.x = this.x * 2; // assignment, use of initialized field
this.y = y; // initialization
}
}
var p = new PointDoubleX(1.0, 2.0);
::
writeln(p);
::
--no-warnings
::
x = 1.0
{x = 2.0, y = 2.0}
The first statement in the initializer initializes field ``x`` to the
value of the formal ``x``. The second statement simply reads the
value of the initialized field. The third statement reads the value
of the field, doubles it, and *assigns* the result to the field
``x``.
If a field is used before it is initialized, an compile-time error will
be issued.
*Example (usedBeforeInitialized.chpl)*.
In the following code:
::
class Point {
var x, y : real;
proc init(x: real, y: real) {
writeln(this.x); // Error: use of uninitialized field!
this.x = x;
this.y = y;
writeln(this.y);
}
}
var p = new Point(1.0, 2.0);
::
--no-warnings
The first statement in the initializer reads the value of
uninitialized field ``x``, so the compiler will issue an error:
::
usedBeforeInitialized.chpl:4: In initializer:
usedBeforeInitialized.chpl:5: error: field "x" used before it is initialized
.. _Omitting_Field_Initializations:
Omitting Field Initializations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In order to support productive and elegant initializers, the language
allows field initializations to be omitted if the field has a type or if
the field has an initialization expression. The compiler will insert
initialization statements for such fields based on their types and
default values.
*Example (fieldInitOmitted.chpl)*.
In the following code:
::
class LabeledPoint {
var x : real;
var y : real;
var msg : string = 'Unlabeled';
proc init(x: real, y: real) {
this.x = x;
this.y = y;
// compiler inserts "this.msg = 'Unlabeled'";
}
proc init(msg : string) {
// compiler inserts "this.x = 0.0;"
// compiler inserts "this.y = 0.0;"
this.msg = msg;
}
}
var A = new LabeledPoint(2.0, 3.0);
var B = new LabeledPoint("Origin");
::
writeln(A);
writeln(B);
::
--no-warnings
::
{x = 2.0, y = 3.0, msg = Unlabeled}
{x = 0.0, y = 0.0, msg = Origin}
The first initializer initializes the values of fields ``x`` and
``y``, and the compiler inserts initialization for the ``msg`` field
by using its default value. The second initializer initializes the
``msg`` field, and the compiler inserts initialization for fields
``x`` and ``y`` based on the type of those fields
(§`8.1.1 <#Default_Values_For_Types>`__).
In order to reduce ambiguity and to ensure a well-defined order for
side-effects, the language requires that all fields be initialized in
field declaration order. This applies regardless of whether field
initializations are omitted from the initializer body. If fields are
initialized out of order, a compile-time error will be issued.
*Example (fieldsOutOfOrder.chpl)*.
In the following code:
::
class Point3D {
var x = 1.0;
var y = 1.0;
var z = 1.0;
proc init(x: real) {
this.x = x;
// compiler inserts "this.y = 1.0;"
this.z = y * 2.0;
}
proc init(x: real, y: real, z: real) {
this.x = x;
this.z = z;
this.y = y; // Error!
}
}
var A = new Point3D(1.0);
var B = new Point3D(1.0, 2.0, 3.0);
::
--no-warnings
::
fieldsOutOfOrder.chpl:12: In initializer:
fieldsOutOfOrder.chpl:15: error: Field "y" initialized out of order
fieldsOutOfOrder.chpl:15: note: initialization of fields before .init() call must be in field declaration order
The first initializer leverages the well-defined order of omitted
field initialization to use the default value of field ``y`` in order
to explicitly initialize field ``z``.
The second initializer initializes field ``z`` before field ``y``,
causing a compile-time error to be issued.
..
*Rationale*.
Without this rule the compiler could insert default initialization
for field ``y`` before ``z`` is explicitly initialized. The following
statement would then be *assignment* to field ``y``, despite
appearing to be initialization. This subtle difference may be
confusing and surprising, and is avoided by requiring fields to be
initialized in field declaration order.
.. _Limitations_on_Instance_Usage_in_Initializers:
Limitations on Instance Usage in Initializers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As the initializer makes progress, the class instance is incrementally
initialized. In order to prevent usage of uninitialized memory, there
are restrictions on usage of the class instance before it is fully
initialized:
- Methods may not be invoked on partially-initialized instances
- ``this`` may not be passed to functions while partially-initialized
These rules allow all methods and functions to assume that class
instances have been initialized, provided their value is not ``nil``.
*Rationale*.
The compiler could conceivably attempt to analyze methods and
functions to determine which fields are used, and selectively allow
method calls on partially-initialized class instances. Instead, it is
simpler for the language to forbid method calls on
partially-initialized instances.
Methods may be called and ``this`` may be passed to functions only after
the built-in ``complete`` method is invoked. This method may not be
overridden. If any fields have not been initialized by the time the
``complete`` method is invoked, they will be considered omitted and the
compiler will insert initialization statements as described earlier. If
the user does not invoke the ``complete`` method explicitly, the
compiler will insert a call to ``complete`` at the end of the
initializer.
*Rationale*.
Due to support for omitted field initialization, there is potential
for confusion regarding the overall status of initialization. This
confusion is addressed in the design by requiring ``complete`` to
explicitly mark the transition between partially and fully
initialized instances.
..
*Implementors’ note*.
Even if the user explicitly initializes every field, the ``complete``
method is still required to invoke other methods.
*Example (thisDotComplete.chpl)*.
In the following code:
::
class LabeledPoint {
var x, y : real;
var max = 10.0;
var msg : string = 'Unlabeled';
proc init(x: real, y: real) {
this.x = x;
this.y = y;
// compiler inserts initialization for 'max' and 'msg'
this.complete(); // 'this' is now considered to be fully initialized
this.verify();
writeln(this);
}
proc init(msg : string) {
// compiler inserts initialization for fields 'x', 'y', and 'max'
this.msg = msg;
// Illegal: this.verify();
// Implicit 'this.complete();'
}
proc verify() {
if x > max || y > max then
halt("LabeledPoint out of bounds!");
}
}
var A = new LabeledPoint(1.0, 2.0);
var B = new LabeledPoint("Origin");
::
writeln(B);
::
--no-warnings
::
{x = 1.0, y = 2.0, max = 10.0, msg = Unlabeled}
{x = 0.0, y = 0.0, max = 10.0, msg = Origin}
The first initializer leverages the ``complete`` method to initialize
the remaining fields and to allow for the usage of the ``verify``
method. Calling the ``verify`` method or passing ``this`` to
``writeln`` before the ``complete`` method is called would result in
a compile-time error.
The second initializer exists to emphasize the rule that even though
all fields are initialized after the initialization of the ``msg``
field, the compiler does not consider the type initialized until the
``complete`` method is called. If the second initializer tried to
invoke the ``verify`` method, a compile-time error would be issued.
.. _Invoking_Other_Initializers:
Invoking Other Initializers
^^^^^^^^^^^^^^^^^^^^^^^^^^^
In order to allow for code-reuse, an initializer may invoke another
initializer implemented for the same type. Because the invoked
initializer must operate on completely uninitialized memory, a
compile-time error will be issued for field initialization before a call
to ``init``. Because each initializer either explicitly or implicitly
invokes the ``complete`` method, all fields and methods may be used
after such a call to ``init``.
*Example (thisDotInit.chpl)*.
In the following code:
::
class Point3D {
var x, y, z : real;
proc init(x: real, y: real, z: real) {
this.x = x;
this.y = y;
this.z = z;
// implicit 'this.complete();'
}
proc init(u: real) {
this.init(u, u, u);
writeln(this);
}
}
var A = new Point3D(1.0);
::
--no-warnings
::
{x = 1.0, y = 1.0, z = 1.0}
The second initializer leverages the first initializer to initialize
all fields with the same value. After the ``init`` call the type is
fully initialized, the ``complete`` method has been invoked, and so
``this`` can be passed to the ``writeln`` function.
.. _Initializing_Fields_in_Conditional_Statements:
Initializing Fields in Conditional Statements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Fields may be initialized inside of conditional statements, with the
restriction that the same set of fields must be initialized in every
branch. If the user omits any field initializations, the compiler will
insert field initializations up to and including the field furthest in
field declaration order between the conditional branches. If the else
branch of a conditional statement is omitted, the compiler will generate
an empty else branch and insert field initialization statements as
needed.
*Example (initFieldConditional.chpl)*.
In the following code:
::
class Point {
var x, y : real;
var r, theta : real;
proc init(polar : bool, val : real) {
if polar {
// compiler inserts initialization for fields 'x' and 'y'
this.r = val;
} else {
this.x = val;
this.y = val;
// compiler inserts initialization for field 'r'
}
// compiler inserts initialization for field 'theta'
}
}
var A = new Point(true, 5.0);
var B = new Point(false, 1.0);
::
writeln(A);
writeln(B);
::
--no-warnings
::
{x = 0.0, y = 0.0, r = 5.0, theta = 0.0}
{x = 1.0, y = 1.0, r = 0.0, theta = 0.0}
The compiler identifies field ``r`` as the latest field in both
branches, and inserts omitted field initialization statements as
needed to ensure that fields ``x``, ``y``, and ``r`` are all
initialized by the end of the conditional.
Conditionals may also contain calls to parent initializers
(§`17.4.4 <#Initializing_Inherited>`__) and other initializers defined
for the current type, provided that the initialization state is the same
at the end of the conditional statement.
*Example (thisDotInitConditional.chpl)*.
In the following code:
::
class Parent {
var x, y : real;
}
class Child : Parent {
var z : real;
proc init(cond : bool, val : real) {
if cond {
super.init(val, val);
this.z = val;
this.complete();
} else {
this.init(val, val, val);
}
}
proc init(x: real, y: real, z: real) {
super.init(x, y);
this.z = z;
}
}
var c = new Child(true, 5.0);
::
writeln(c);
::
--no-warnings
::
{x = 5.0, y = 5.0, z = 5.0}
The first initializer must invoke the ``complete`` method at the end
of the if-branch in order to match the state at the end of the
else-branch.
Miscellaneous Field Initialization Rules
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Fields may not be initialized within loop statements or parallel
statements.
.. _The_Compiler_Generated_Initializer:
The Compiler-Generated Initializer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A compiler-generated initializer for a class is created automatically if
there are no initializers for that class in the program. The
compiler-generated initializer has one argument for every field in the
class, each of which has a default value equal to the field’s default
value (if present) or the default value of the field’s type (if not).
The order and names of arguments matches the order and names of field
declarations within the class.
Generic fields are discussed in
Section §\ `24.3.8 <#Generic_Compiler_Generated_Initializers>`__.
The compiler-generated initializer will initialize each field to the
value of the corresponding actual argument.
*Example (defaultInitializer.chpl)*.
Given the class
::
class C {
var x: int;
var y: real = 3.14;
var z: string = "Hello, World!";
}
::
var c1 = new C();
var c2 = new C(2);
var c3 = new C(z="");
var c4 = new C(2, z="");
var c5 = new C(0, 0.0, "");
writeln((c1, c2, c3, c4, c5));
::
--no-warnings
::
({x = 0, y = 3.14, z = Hello, World!}, {x = 2, y = 3.14, z = Hello, World!}, {x = 0, y = 3.14, z = }, {x = 2, y = 3.14, z = }, {x = 0, y = 0.0, z = })
there are no user-defined initializers for ``C``, so ``new``
expressions will invoke ``C``\ ’s compiler-generated initializer. The
``x`` argument of the compiler-generated initializer has the default
value ``0``. The ``y`` and ``z`` arguments have the default values
``3.14`` and ``"Hello, World\``"!, respectively.
``C`` instances can be created by calling the compiler-generated
initializer as follows:
- The call ``new C()`` is equivalent to
``new C(0,3.14,"Hello, World\``")!.
- The call ``new C(2)`` is equivalent to
``new C(2,3.14,"Hello, World\``")!.
- The call ``new C(z="")`` is equivalent to ``new C(0,3.14,"")``.
- The call ``new C(2, z="")`` is equivalent to ``new C(2,3.14,"")``.
- The call ``new C(0,0.0,"")`` specifies the initial values for all
fields explicitly.
.. _The_postinit_Method:
The postinit Method
~~~~~~~~~~~~~~~~~~~
The compiler-generated initializer is powerful and flexible, but cannot
satisfy all initialization patterns desired by users. One way for users
to leverage the compiler-generated initializer while adding their own
functionality is to implement a method named ``postinit``. The
``postinit`` method may also be implemented for types with user-defined
initializers.
The compiler will insert a call to the ``postinit`` method after the
initializer invoked by the ``new`` expression finishes, if the method
exists. The ``postinit`` method accepts zero arguments and may not
return anything. Otherwise, this method behaves like any other method.
*Example (postinit.chpl)*.
In the following code:
::
class Point3D {
var x, y : real;
var max = 10.0;
proc postinit() {
verify();
}
proc verify() {
writeln("(", x, ", ", y, ")");
if x > max || y > max then
writeln(" Point out of bounds!");
}
}
var A = new Point3D();
var B = new Point3D(1.0, 2.0);
var C = new Point3D(y=5.0);
var D = new Point3D(50.0, 50.0);
::
--no-warnings
Each of the ``new`` expressions invokes the compiler-generated
initializer, then invokes the ``verify`` method via the ``postinit``
method:
::
(0.0, 0.0)
(1.0, 2.0)
(0.0, 5.0)
(50.0, 50.0)
Point out of bounds!
For classes that inherit, the user may invoke the parent’s ``postinit``
method or let the compiler insert a call automatically
(§`17.4.4.3 <#The_postinit_Method_for_Inheriting_Classes>`__).
.. _Initializing_Inherited:
Initializing Inherited Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
User-defined initializers also allow for control over initialization of
parent classes. All the fields of the parent type must be initialized
before any fields of the child type, otherwise a compile-time error is
issued. This allows for parent fields to be used in the definition of
child fields. An initializer may invoke a parent’s initializer using the
``super`` keyword.
If the user does not explicitly call the parent’s initializer, the
compiler will insert a call to the parent initializer with zero
arguments at the start of the initializer.
*Example (simpleSuperInit.chpl)*.
In the following code:
::
class A {
var a, b : real;
proc init() {
this.init(1.0);
}
proc init(val : real) {
this.a = val;
this.b = val * 2;
}
}
class B : A {
var x, y : real;
proc init(val: real, x: real, y: real) {
super.init(val);
this.x = x;
this.y = y;
}
proc init() {
// implicit super.init();
this.x = a*2;
this.y = b*2;
}
}
var b1 = new B(4.0, 1.0, 2.0);
var b2 = new B();
::
writeln(b1);
writeln(b2);
::
--no-warnings
::
{a = 4.0, b = 8.0, x = 1.0, y = 2.0}
{a = 1.0, b = 2.0, x = 2.0, y = 4.0}
The first initializer explicitly calls an initializer for class
``A``. Once the parent’s initializer is complete, fields of class
``B`` may be initialized.
The second initializer implicitly invokes the parent’s initializer
with zero arguments, and then uses the parent’s fields to initialize
its own fields.
As stated earlier, the compiler will insert a zero-argument call to the
parent’s initializer if the user has not explicitly written one
themselves. The exception to this rule is if the initializer body
invokes another initializer on the current type
(§`17.4.1.4 <#Invoking_Other_Initializers>`__). This other initializer
will either contain an implicit or explicit call to the parent
initializer, and so the calling initializer should not attempt to
initialize the parent itself. This also means that parent fields may not
be accessed before explicit calls to ``init``.
*Example (superInitThisInit.chpl)*.
In the following code:
::
class Parent {
var x, y: real;
}
class Child : Parent {
var z : real;
proc init(x: real, y: real, z: real) {
super.init(x, y);
this.z = z;
}
proc init(z: real) {
this.init(0.0, 0.0, z);
}
}
var c = new Child(5.0);
::
writeln(c);
::
--no-warnings
::
{x = 0.0, y = 0.0, z = 5.0}
The second initializer does not contain an implicit call to the
parent’s initializer because it explicitly invokes another
initializer.
.. _Calling_Methods_on_Parent_Classes:
Calling Methods on Parent Classes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Once ``super.init()`` returns, the dynamic type of ``this`` is the
parent’s type until the ``complete`` method
(§`17.4.1.3 <#Limitations_on_Instance_Usage_in_Initializers>`__) is
invoked (except when the child’s fields are initialized and used). As a
result, the parent’s methods may be called and ``this`` may be passed to
functions as though it were of the parent type.
*Rationale*.
After ``super.init()`` returns the instance is in some
partially-initialized, but valid, state. Allowing ``this`` to be
treated as the parent allows for additional functionality and
flexibility for users.
..
*Example (dynamicThisInit.chpl)*.
In the following code:
::
class Parent {
var x, y : real;
proc foo() {
writeln("Parent.foo");
}
}
class Child : Parent {
var z : real;
proc init(x: real, y: real, z: real) {
super.init(x, y); // parent's compiler-generated initializer
foo(); // Parent.foo()
this.z = z;
this.complete();
foo(); // Child.foo()
}
override proc foo() {
writeln("Child.foo");
}
}
var c = new Child(1.0, 2.0, 3.0);
::
writeln(c);
::
--no-warnings
Once the parent’s initializer is finished, the parent method ``foo``
may be called. After the ``complete`` method is invoked, a call to
``foo`` resolves to the child’s overridden
(§`17.2.4 <#Overriding_Base_Class_Methods>`__) implementation:
::
Parent.foo
Child.foo
{x = 1.0, y = 2.0, z = 3.0}
.. _The_Compiler_Generated_Initializer_for_Inheriting_Classes:
The Compiler Generated Initializer for Inheriting Classes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The compiler-generated initializer for inheriting classes will have
arguments with default values and names based on the field declarations
in the parent class. Formals for the parent type will be listed before
formals for the child type.
*Example (compilerGeneratedInheritanceInit.chpl)*.
In the following code:
::
class Parent {
var x, y: real;
}
class Child : Parent {
var z : real;
}
var A = new Child();
var B = new Child(1.0, 2.0, 3.0); // x=1.0, y=2.0, z=3.0
var C = new Child(y=10.0);
::
writeln(A);
writeln(B);
writeln(C);
::
--no-warnings
::
{x = 0.0, y = 0.0, z = 0.0}
{x = 1.0, y = 2.0, z = 3.0}
{x = 0.0, y = 10.0, z = 0.0}
Any ``new`` expressions using the ``Child`` type can invoke an
initializer with three formals named ``x``, ``y``, and ``z`` that all
have default values based on their types.
.. _The_postinit_Method_for_Inheriting_Classes:
The postinit Method for Inheriting Classes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``postinit`` method on inheriting classes allows users to invoke the
parent’s ``postinit`` method using the ``super`` keyword. If the user
does not explicitly invoke the parent’s ``postinit``, the compiler will
insert the call at the top of the user’s ``postinit`` method. If the
parent type has a ``postinit`` method but the inheriting class does not,
the compiler will generate a ``postinit`` method that simply invokes the
parent’s ``postinit`` method.
*Example (inheritancePostinit.chpl)*.
In the following code:
::
class Parent {
var a, b : real;
proc postinit() {
writeln("Parent.postinit: ", a, ", ", b);
}
}
class Child : Parent {
var x, y : real;
proc postinit() {
// compiler inserts "super.postinit();"
writeln("Child.postinit: ", x, ", ", y);
}
}
var c = new Child(1.0, 2.0, 3.0, 4.0);
::
--no-warnings
The compiler inserts a call to the parent’s ``postinit`` method in
the child’s ``postinit`` method, and invokes the child’s ``postinit``
method after the compiler-generated initializer finishes:
::
Parent.postinit: 1.0, 2.0
Child.postinit: 3.0, 4.0
.. _Class_Field_Accesses:
Field Accesses
--------------
The field in a class is accessed via a field access expression.
::
field-access-expression:
receiver-clause[OPT] identifier
receiver-clause:
expression .
The receiver-clause specifies the *receiver*, which is the class
instance whose field is being accessed. The receiver clause can be
omitted when the field access is within a method. In this case the
receiver is the method’s receiver. The receiver clause can also be
omitted when the field access is within a class declaration. In this
case the receiver is the instance being implicitly defined or
referenced.
The identifier in the field access expression indicates which field is
accessed.
A field can be modified via an assignment statement where the left-hand
side of the assignment is a field access expression.
Accessing a parameter or type field returns a parameter or type,
respectively. In addition to being available for access with a class
instance receiver, parameter and type fields can be accessed from the
instantiated class type itself.
*Example (useActor1.chpl)*.
Given a variable ``anActor`` of type ``Actor`` as defined above, the
code
::
use defineActor;
var anActor = new unmanaged Actor(name="Tommy", age=26);
::
var s: string = anActor.name;
anActor.age = 27;
::
writeln((s, anActor));
if (cleanUp) then delete anActor;
::
-scleanUp=true
::
(Tommy, {name = Tommy, age = 27})
reads the field ``name`` and assigns the value to the variable ``s``,
and assigns the field ``age`` in the object ``anActor`` the value
``27``.
.. _Getter_Methods:
Variable Getter Methods
~~~~~~~~~~~~~~~~~~~~~~~
All field accesses are performed via getters. A getter is a method
without parentheses with the same name as the field. It is defined in
the field’s class and has a ``ref`` return intent
(§`13.7.1 <#Ref_Return_Intent>`__). If the program does not define it,
the default getter, which simply returns the field, is provided.
*Example (getterSetter.chpl)*.
In the code
::
class C {
var setCount: int;
var x: int;
proc x ref {
setCount += 1;
return x;
}
proc x {
return x;
}
}
::
var c = new C();
c.x = 1;
writeln(c.x);
c.x = 2;
writeln(c.x);
c.x = 3;
writeln(c.x);
writeln(c.setCount);
::
--no-warnings
::
1
2
3
3
an explicit variable getter method is defined for field ``x``. It
returns the field ``x`` and increments another field that records the
number of times x was assigned a value.
.. _Class_Method_Calls:
Class Method Calls
------------------
Class method calls are similar to other method calls which are described
in §\ `14.1 <#Method_Calls>`__. However, class method calls are subject
to dynamic dispatch.
The receiver-clause (or its absence) specifies the method’s receiver in
the same way it does for field accesses
§\ `17.5 <#Class_Field_Accesses>`__.
See (§`14.2 <#Method_receiver_and_this>`__) for more details of about
method receivers.
Common Operations
-----------------
.. _Class_Assignment:
Class Assignment
~~~~~~~~~~~~~~~~
Classes are assigned by reference. After an assignment from one variable
of a class type to another, both variables reference the same class
instance. Assignments from an ``owned`` variable to another ``owned`` or
``shared`` variable are an exception. They transfer ownership, leaving
the source variable empty i.e. storing ``nil``.
*Example (owned-assignment.chpl)*.
::
class C { }
::
// assume that C is a class
var a:owned C? = new owned C();
var b:owned C?; // default initialized to store `nil`
b = a; // transfers ownership from a to b
writeln(a); // a is left storing `nil`
::
nil
In contrast, assignment for ``shared`` variables allows both variables
to refer to the same class instance.
The following assignments between variables or expressions with
different memory management strategies are disallowed:
- to ``owned`` from ``shared`` or ``borrowed``, as it would not ensure
unique ownership of the instance
- to ``shared`` from ``borrowed``, as the original owner would be
unaware of the shared ownership
- to ``owned``, ``shared``, or ``borrowed`` from ``unmanaged``, as both
the source and the destination would appear responsible for deleting
the instance
.. _Class_Delete:
Deleting Unmanaged Class Instances
----------------------------------
Memory associated with ``unmanaged`` class instances can be reclaimed
with the ``delete`` statement:
::
delete-statement:
`delete' expression-list ;
where the expression-list specifies the class objects whose memory will
be reclaimed. Prior to releasing their memory, the deinitialization
routines for these objects will be executed
(§`17.8.1 <#Class_Deinitializer>`__). The expression-list can contain
array expressions, in which case each element of that array will be
deleted in parallel using a ``forall`` loop over the array. It is legal
to delete a class variable whose value is ``nil``, though this has no
effect. If a class instance is referenced after it has been deleted, the
behavior is undefined.
*Example (delete.chpl)*.
The following example allocates a new object ``c`` of class type
``C`` and then deletes it.
::
class C {
var i,j,k: int;
}
::
var c : unmanaged C? = nil;
delete c; // Does nothing: c is nil.
c = new unmanaged C(); // Creates a new object.
delete c; // Deletes that object.
// The following statements reference an object after it has been deleted, so
// the behavior of each is "undefined":
// writeln(c.i); // May read from freed memory.
// c.i = 3; // May overwrite freed memory.
// delete c; // May confuse some allocators.
::
--memLeaksByType
::
====================
Leaked Memory Report
==============================================================
Number of leaked allocations
Total leaked memory (bytes)
Description of allocation
==============================================================
==============================================================
.. _Class_Deinitializer:
Class Deinitializer
~~~~~~~~~~~~~~~~~~~
A class author may create a deinitializer to specify additional actions
to be performed when a class instance is deleted. A class deinitializer
is a method named ``deinit()``. It must take no arguments (aside from
the implicit ``this`` argument). If defined, the deinitializer is called
each time a ``delete`` statement is invoked with a valid instance of
that class type. The deinitializer is not called if the argument of
``delete`` evaluates to ``nil``. Note that when an ``owned`` or
``shared`` variable goes out of scope, it may call ``delete`` on a class
instance which in turn will run the deinitializer and then reclaim the
memory.
*Example (classDeinitializer.chpl)*.
::
class C {
var i: int;
proc deinit() { writeln("Bye, bye ", i); }
}
var c : unmanaged C? = nil;
delete c; // Does nothing: c is nil.
c = new unmanaged C(1); // Creates a new instance.
delete c; // Deletes that instance: Writes out "Bye, bye 1"
// and reclaims the memory that was held by c.
{
var own = new owned C(2); // Creates a new owned instance
// The instance is automatically deleted at
// the end of this block, so "Bye, bye 2" is
// output and then the memory is reclaimed.
}
::
Bye, bye 1
Bye, bye 2
Records
=======
[Records]
A record is a data structure that is similar to a class except it has
value semantics, similar to primitive types. Value semantics mean that
assignment, argument passing and function return values are by default
all done by copying. Value semantics also imply that a variable of
record type is associated with only one piece of storage and has only
one type throughout its lifetime. Storage is allocated for a variable of
record type when the variable declaration is executed, and the record
variable is also initialized at that time. When the record variable goes
out of scope, or at the end of the program if it is a global, it is
deinitialized and its storage is deallocated.
A record declaration statement creates a record
type §\ `18.1 <#Record_Declarations>`__. A variable of record type
contains all and only the fields defined by that type
(§`18.1.1 <#Record_Types>`__). Value semantics imply that the type of a
record variable is known at compile time (i.e. it is statically typed).
A record can be created using the ``new`` operator, which allocates
storage, initializes it via a call to a record initializer, and returns
it. A record is also created upon a variable declaration of a record
type.
A record type is generic if it contains generic fields. Generic record
types are discussed in detail in §\ `24.3 <#Generic_Types>`__.
.. _Record_Declarations:
Record Declarations
-------------------
A record type is defined with the following syntax:
::
record-declaration-statement:
simple-record-declaration-statement
external-record-declaration-statement
simple-record-declaration-statement:
`record' identifier { record-statement-list }
record-statement-list:
record-statement
record-statement record-statement-list
record-statement:
variable-declaration-statement
method-declaration-statement
type-declaration-statement
empty-statement
A ``record-declaration-statement`` defines a new type symbol specified
by the identifier. As in a class declaration, the body of a record
declaration can contain variable, method, and type declarations.
If a record declaration contains a type alias or parameter field, or it
contains a variable or constant field without a specified type and
without an initialization expression, then it declares a generic record
type. Generic record types are described
in §\ `24.3 <#Generic_Types>`__.
If the ``extern`` keyword appears before the ``record`` keyword, then an
external record type is declared. An external record is used within
Chapel for type and field resolution, but no corresponding backend
definition is generated. It is presumed that the definition of an
external record is supplied by a library or the execution environment.
See the chapter on interoperability
(§`[Interoperability] <#Interoperability>`__) for more information on
external records.
*Future*.
Privacy controls for classes and records are currently not specified,
as discussion is needed regarding its impact on inheritance, for
instance.
.. _Record_Types:
Record Types
~~~~~~~~~~~~
A record type specifier simply names a record type, using the following
syntax:
::
record-type:
identifier
identifier ( named-expression-list )
A record type specifier may appear anywhere a type specifier is
permitted.
For non-generic records, the record name by itself is sufficient to
specify the type. Generic records must be instantiated to serve as a
fully-specified type, for example to declare a variable. This is done
with type constructors, which are defined in
Section \ `24.3.6 <#Type_Constructors>`__.
.. _Record_Fields:
Record Fields
~~~~~~~~~~~~~
Variable declarations within a record type declaration define fields
within that record type. The presence of at least one parameter field
causes the record type to become generic. Variable fields define the
storage associated with a record.
*Example (defineActorRecord.chpl)*.
The code
::
record ActorRecord {
var name: string;
var age: uint;
}
defines a new record type called ``ActorRecord`` that has two fields:
the string field ``name`` and the unsigned integer field ``age``. The
data contained by a record of this type is exactly the same as that
contained by an instance of the ``Actor`` class defined in the
preceding chapter §\ `17.1.6 <#Class_Fields>`__.
.. _Record_Methods:
Record Methods
~~~~~~~~~~~~~~
A record method is a function or iterator that is bound to a record. See
the methods section §\ `[Methods] <#Methods>`__ for more information
about methods.
Note that the receiver of a record method is passed by ``ref`` or
``const ref`` intent by default, depending on whether or not ``this`` is
modified in the body of the method.
.. _Nested_Record_Types:
Nested Record Types
~~~~~~~~~~~~~~~~~~~
A record defined within another class or record is a nested record. A
nested record can be referenced only within its immediately enclosing
class or record.
.. _Record_Variable_Declarations:
Record Variable Declarations
----------------------------
A record variable declaration is a variable declaration using a record
type. When a variable of record type is declared, storage is allocated
sufficient to store all of the fields defined in that record type.
In the context of a class or record or union declaration, the fields are
allocated within the object as if they had been declared individually.
In this sense, records provide a way to group related fields within a
containing class or record type.
In the context of a function body, a record variable declaration causes
storage to be allocated sufficient to store all of the fields in that
record type. The record variable is initialized with a call to an
initializer (§`17.4 <#Class_Initializers>`__) that accepts zero actual
arguments.
.. _Record_Storage:
Storage Allocation
~~~~~~~~~~~~~~~~~~
Storage for a record variable directly contains the data associated with
the fields in the record, in the same manner as variables of primitive
types directly contain the primitive values. Record storage is reclaimed
when the record variable goes out of scope. No additional storage for a
record is allocated or reclaimed. Field data of one variable’s record is
not shared with data of another variable’s record.
.. _Record_Initialization:
Record Initialization
~~~~~~~~~~~~~~~~~~~~~
A variable of a record type declared without an initialization
expression is initialized through a call to the record’s default
initializer, passing no arguments. The default initializer for a record
is defined in the same way as the default initializer for a class
(§`17.4.2 <#The_Compiler_Generated_Initializer>`__).
To create a record as an expression, i.e. without binding it to a
variable, the ``new`` operator is required. In this case, storage is
allocated and reclaimed as for a record variable declaration
(§`18.2.1 <#Record_Storage>`__), except that the temporary record goes
out of scope at the end of the enclosing block.
The initializers for a record are defined in the same way as those for a
class (§`17.4 <#Class_Initializers>`__). Note that records do not
support inheritance and therefore the initializer rules for inheriting
classes (§`17.4.4 <#Initializing_Inherited>`__) do not apply to record
initializers.
*Example (recordCreation.chpl)*.
The program
::
record TimeStamp {
var time: string = "1/1/1011";
}
var timestampDefault: TimeStamp; // use the default for 'time'
var timestampCustom = new TimeStamp("2/2/2022"); // ... or a different one
writeln(timestampDefault);
writeln(timestampCustom);
var idCounter = 0;
record UniqueID {
var id: int;
proc init() { idCounter += 1; id = idCounter; }
}
var firstID : UniqueID; // invokes zero-argument initializer
writeln(firstID);
writeln(new UniqueID()); // create and use a record value without a variable
writeln(new UniqueID());
produces the output
::
(time = 1/1/1011)
(time = 2/2/2022)
(id = 1)
(id = 2)
(id = 3)
The variable ``timestampDefault`` is initialized with
``TimeStamp``\ ’s default initializer. The expression
``new TimeStamp`` creates a record that is assigned to
``timestampCustom``. It effectively initializes ``timestampCustom``
via a call to the initializer with desired arguments. The records
created with ``new UniqueID()`` are discarded after they are used.
As with classes, the user can provide their own initializers
(§`17.4.1 <#User_Defined_Initializers>`__). If any user-defined
initializers are supplied, the default initializer cannot be called
directly.
.. _Record_Deinitializer:
Record Deinitializer
~~~~~~~~~~~~~~~~~~~~
A record author may specify additional actions to be performed before
record storage is reclaimed by defining a record deinitializer. A record
deinitializer is a method named ``deinit()``. A record deinitializer
takes no arguments (aside from the implicit ``this`` argument). If
defined, the deinitializer is called on a record object after it goes
out of scope and before its memory is reclaimed.
*Example (recordDeinitializer.chpl)*.
::
class C { var x: int; } // A class with nonzero size.
// If the class were empty, whether or not its memory was reclaimed
// would not be observable.
// Defines a record implementing simple memory management.
record R {
var c: unmanaged C;
proc init() {
c = new unmanaged C(0);
}
proc deinit() {
delete c;
}
}
proc foo()
{
var r: R; // Initialized using default initializer.
writeln(r);
// r will go out of scope here.
// Its deinitializer will be called to free the C object it contains.
}
foo();
::
(c = {x = 0})
====================
Leaked Memory Report
==============================================================
Number of leaked allocations
Total leaked memory (bytes)
Description of allocation
==============================================================
==============================================================
::
--memLeaksByType
.. _Record_Arguments:
Record Arguments
----------------
Record formal arguments with the ``in`` intent will be copy-initialized
into the function’s formal argument
(§`18.6.1 <#Copy_Initialization_of_Records>`__).
Record formal arguments with ``inout`` or ``out`` intent will be updated
by the record assignment function (§`18.6.2 <#Record_Assignment>`__).
*Example (paramPassing.chpl)*.
The program
::
record MyColor {
var color: int;
}
proc printMyColor(in mc: MyColor) {
writeln("my color is ", mc.color);
mc.color = 6; // does not affect the caller's record
}
var mc1: MyColor; // 'color' defaults to 0
var mc2: MyColor = mc1; // mc1's value is copied into mc2
mc1.color = 3; // mc1's value is modified
printMyColor(mc2); // mc2 is not affected by assignment to mc1
printMyColor(mc2); // ... or by assignment in printMyColor()
proc modifyMyColor(inout mc: MyColor, newcolor: int) {
mc.color = newcolor;
}
modifyMyColor(mc2, 7); // mc2 is affected because of the 'inout' intent
printMyColor(mc2);
produces
::
my color is 0
my color is 0
my color is 7
The assignment to ``mc1.color`` affects only the record stored in
``mc1``. The record in ``mc2`` is not affected by the assignment to
``mc1`` or by the assignment in ``printMyColor``. ``mc2`` is affected
by the assignment in ``modifyMyColor`` because the intent ``inout``
is used.
.. _Record_Field_Access:
Record Field Access
-------------------
A record field is accessed the same way as a class field
(§`17.5 <#Class_Field_Accesses>`__). When a field access is used as an
rvalue, the value of that field is returned. When it is used as an
lvalue, the value of the record field is updated.
Accessing a parameter or type field returns a parameter or type,
respectively. Also, parameter and type fields can be accessed from an
instantiated record type in addition to from a record value.
.. _Field_Getter_Methods:
Field Getter Methods
~~~~~~~~~~~~~~~~~~~~
As in classes, field accesses are performed via getter methods
(§`17.5.1 <#Getter_Methods>`__). By default, these methods simply return
a reference to the specified field (so they can be written as well as
read). The user may redefine these as needed.
.. _Record_Method_Access:
Record Method Calls
-------------------
Record method calls are written the same way as other method calls
(§`14.1 <#Method_Calls>`__). Unlike class methods, record methods are
always resolved at compile time.
.. _common-operations-1:
Common Operations
-----------------
.. _Copy_Initialization_of_Records:
Copy Initialization of Records
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a new record variable is created based upon an existing variable,
we say that the new variable is *copy initialized* from the existing
variable. For example:
::
var x: MyRecordType;
var y = x; // this is called copy initialization
We say that y is *copy initialized* from x. Since x and y both exist
after y is initialized and they are different record variables, they
must not be observed to alias. That is, modifications to a field in x
should not cause the corresponding field in y to change.
Initializing a record variable with the result of a call returning a
record by value simply captures the result of the call into the variable
and does not cause copy initialization. For example:
::
proc returnsRecord() {
var ret: MyRecordType;
return ret;
}
var z = returnsRecord(); // captures result into z without copy initializing
..
*Future*.
Specifying further details of copy initialization is future work.
.. _Record_Assignment:
Record Assignment
~~~~~~~~~~~~~~~~~
A variable of record type may be updated by assignment. The compiler
provides a default assignment operator for each record type ``R`` having
the signature:
::
proc =(ref lhs:R, rhs:R) : void where lhs.type == rhs.type;
In it, the value of each field of the record on the right-hand side is
assigned to the corresponding field of the record on the left-hand side.
The compiler-provided assignment operator may be overridden as described
in `11.3 <#Assignment_Statements>`__.
The following example demonstrates record assignment.
*Example (assignment.chpl)*.
::
record R {
var i: int;
var x: real;
proc print() { writeln("i = ", this.i, ", x = ", this.x); }
}
var A: R;
A.i = 3;
A.print(); // "i = 3, x = 0.0"
var C: R;
A = C;
A.print(); // "i = 0, x = 0.0"
C.x = 3.14;
A.print(); // "i = 0, x = 0.0"
::
i = 3, x = 0.0
i = 0, x = 0.0
i = 0, x = 0.0
Prior to the first call to ``R.print``, the record ``A`` is created
and initialized to all zeroes. Then, its ``i`` field is set to ``3``.
For the second call to ``R.print``, the record ``C`` is created
assigned to ``A``. Since ``C`` is default-initialized to all zeroes,
those zero values overwrite both values in ``A``.
The next clause demonstrates that ``A`` and ``C`` are distinct
entities, rather than two references to the same object. Assigning
``3.14`` to ``C.x`` does not affect the ``x`` field in ``A``.
.. _Record_Comparison_Operators:
Default Comparison Operators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Default functions to overload ``==`` and ``\``\ =! are defined for
records if none are explicitly defined. These functions have the
following signatures for a record ``R``:
::
proc ==(lhs:R, rhs:R) : bool where lhs.type == rhs.type;
proc !=(lhs:R, rhs:R) : bool where lhs.type == rhs.type;
Each of these compare the fields, one at a time, returning ``false`` if
the property is not satisfied by the given pair of fields.
.. _Class_and_Record_Differences:
Differences between Classes and Records
---------------------------------------
The key differences between records and classes are listed below.
.. _Declaration_Differences:
Declarations
~~~~~~~~~~~~
Syntactically, class and record type declarations are identical, except
that they begin with the ``class`` and ``record`` keywords,
respectively. In contrast to classes, records do not support
inheritance.
.. _Storage_Allocation_Differences:
Storage Allocation
~~~~~~~~~~~~~~~~~~
For a variable of record type, storage necessary to contain the data
fields has a lifetime equivalent to the scope in which it is declared.
No two record variables share the same data. It is not necessary to call
``new`` to create a record.
By contrast, a class variable contains only a reference to a class
instance. A class instance is created through a call to its ``new``
operator. Storage for a class instance, including storage for the data
associated with the fields in the class, is allocated and reclaimed
separately from variables referencing that instance. The same class
instance can be referenced by multiple class variables.
.. _Assignment_Differences:
Assignment
~~~~~~~~~~
Assignment to a class variable is performed by reference, whereas
assignment to a record is performed by value. When a variable of class
type is assigned to another variable of class type, they both become
names for the same object. In contrast, when a record variable is
assigned to another record variable, then contents of the source record
are copied into the target record field-by-field.
When a variable of class type is assigned to a record, matching fields
(matched by name) are copied from the class instance into the
corresponding record fields. Subsequent changes to the fields in the
target record have no effect upon the class instance.
Assignment of a record to a class variable is not permitted.
.. _Argument_Differences:
Arguments
~~~~~~~~~
Record arguments use the ``const ref`` intent by default - in contrast
to class arguments which pass by ``const in`` intent by default.
Similarly, the ``this`` receiver argument is passed by ``const in`` by
default for class methods. In contrast, it is passed by ``ref`` or
``const ref`` by default for record methods.
No *nil* Value
~~~~~~~~~~~~~~
Records do not provide a counterpart of the ``nil`` value. A variable of
record type is associated with storage throughout its lifetime, so
``nil`` has no meaning with respect to records.
.. _Record_Delete_Illegal:
The *delete* operator
~~~~~~~~~~~~~~~~~~~~~
Calling ``delete`` on a record is illegal.
.. _Comparison_Operator_Differences:
Default Comparison Operators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For records, the compiler will supply default comparison operators if
they are not supplied by the user. In contrast, the user cannot redefine
``==`` and ````\ =! for classes. The default comparison operators for a
record examine the arguments’ fields, while the comparison operators for
classes check whether the l.h.s. and r.h.s. refer to the same class
instance or are both ``nil``.
Unions
======
[Unions]
Unions have the semantics of records, however, only one field in the
union can contain data at any particular point in the program’s
execution. Unions are safe so that an access to a field that does not
contain data is a runtime error. When a union is initialized, it is in
an unset state so that no field contains data.
.. _Union_Types:
Union Types
-----------
The syntax of a union type is summarized as follows:
::
union-type:
identifier
The union type is specified by the name of the union type. This
simplification from class and record types is possible because generic
unions are not supported.
.. _Union_Declarations:
Union Declarations
------------------
A union is defined with the following syntax:
::
union-declaration-statement:
`extern'[OPT] `union' identifier { union-statement-list }
union-statement-list:
union-statement
union-statement union-statement-list
union-statement:
type-declaration-statement
procedure-declaration-statement
iterator-declaration-statement
variable-declaration-statement
empty-statement
If the ``extern`` keyword appears before the ``union`` keyword, then an
external union type is declared. An external union is used within Chapel
for type and field resolution, but no corresponding backend definition
is generated. It is presumed that the definition of an external union
type is supplied by a library or the execution environment.
.. _Union_Fields:
Union Fields
~~~~~~~~~~~~
Union fields are accessed in the same way that record fields are
accessed. It is a runtime error to access a field that is not currently
set.
Union fields should not be specified with initialization expressions.
.. _Union_Assignment:
Union Assignment
----------------
Union assignment is by value. The field set by the union on the
right-hand side of the assignment is assigned to the union on the
left-hand side of the assignment and this same field is marked as set.
Ranges
======
[Ranges]
A *range* is a first-class, constant-space representation of a regular
sequence of indices of either integer, boolean, or enumerated type.
Ranges support iteration over the sequences they represent and are the
basis for defining domains (§`[Domains] <#Domains>`__).
Ranges are presented as follows:
- definition of the key range concepts §\ `20.1 <#Range_Concepts>`__
- range types §\ `20.2 <#Range_Types>`__
- range values §\ `20.3 <#Range_Values>`__
- range assignment §\ `20.4.1 <#Range_Assignment>`__
- operators on ranges §\ `20.5 <#Range_Operators>`__
- predefined functions on ranges
§\ `20.6 <#Predefined_Range_Functions>`__
.. _Range_Concepts:
Range Concepts
--------------
A range has four primary properties. Together they define the sequence
of indices that the range represents, or the *represented sequence*, as
follows.
- The *low bound* is either a specific index value or -:math:`\infty`.
- The *high bound* is either a specific index value or
+\ :math:`\infty`. The low and high bounds determine the span of the
represented sequence. Chapel does not represent :math:`\infty`
explicitly. Instead, infinite bound(s) are represented implicitly in
the range’s type (§`20.2 <#Range_Types>`__). When the low and/or high
bound is :math:`\infty`, the represented sequence is unbounded in the
corresponding direction(s).
- The *stride* is a non-zero integer. It defines the distance between
any two adjacent members of the represented sequence. The sign of the
stride indicates the direction of the sequence:
- :math:`stride > 0` indicates an increasing sequence,
- :math:`stride < 0` indicates a decreasing sequence.
- The *alignment* is either a specific index value or is *ambiguous*.
It defines how the represented sequence’s members are aligned
relative to the stride. For a range with a stride other than 1 or -1,
ambiguous alignment means that the represented sequence is undefined.
In such a case, certain operations discussed later result in an
error.
More formally, the represented sequence for the range
:math:`(low, high, stride, alignmt)` contains all indices :math:`ix`
such that:
=========================================================================== ============================================
:math:`low \leq ix \leq high` and :math:`ix \equiv alignmt \pmod{|stride|}` if :math:`alignmt` is not ambiguous
:math:`low \leq ix \leq high` if :math:`stride = 1` or :math:`stride = -1`
the represented sequence is undefined otherwise
=========================================================================== ============================================
The sequence, if defined, is increasing if :math:`stride > 0` and
decreasing if :math:`stride < 0`.
If the represented sequence is defined but there are no indices
satisfying the applicable equation(s) above, the range and its
represented sequence are *empty*. A common case of this occurs when the
high bound is greater than the low bound.
We will say that a value :math:`ix` is *aligned* w.r.t. the range
:math:`(low, high, stride, alignmt)` if:
- :math:`alignmt` is not ambiguous and
:math:`ix \equiv alignmt \pmod{|stride|}`, or
- :math:`stride` is 1 or -1.
Furthermore, :math:`\infty` is never aligned.
Ranges have the following additional properties.
- A range is *ambiguously aligned* if
- its alignment is ambiguous, and
- its stride is neither 1 nor -1.
- The *first index* is the first member of the represented sequence.
A range *has no* first index when the first member is undefined, that
is, in the following cases:
- the range is ambiguously aligned,
- the represented sequence is empty,
- the represented sequence is increasing and the low bound is
-:math:`\infty`,
- the represented sequence is decreasing and the high bound is
+\ :math:`\infty`.
- The *last index* is the last member of the represented sequence.
A range *has no* last index when the last member is undefined, that
is, in the following cases:
- it is ambiguously aligned,
- the represented sequence is empty,
- the represented sequence is increasing and the high bound is
+\ :math:`\infty`,
- the represented sequence is decreasing and the low bound is
-:math:`\infty`.
- The *aligned low bound* is the smallest value that is greater than or
equal to the low bound and is aligned w.r.t. the range, if such a
value exists.
The aligned low bound equals the smallest member of the represented
sequence, when both exist.
- The *aligned high bound* is the largest value that is less than or
equal to the high bound and is aligned w.r.t. the range, if such a
value exists.
The aligned high bound equals the largest member of the represented
sequence, when both exist.
- The range is *iterable*, that is, it is legal to iterate over it, if
it has a first index.
.. _Range_Types:
Range Types
-----------
The type of a range is characterized by three parameters:
- ``idxType`` is the type of the indices of the range’s represented
sequence. However, when the range’s low and/or high bound is
:math:`\infty`, the represented sequence also contains indices that
are not representable by ``idxType``.
``idxType`` must be an integral, boolean, or enumerated type and is
``int`` by default. The range’s low bound and high bound (when they
are not :math:`\infty`) and alignment are of the type ``idxType``.
The range’s stride is of the signed integer type that has the same
bit size as ``idxType`` for integral ranges; for boolean and
enumerated ranges, it is simply ``int``.
- ``boundedType`` indicates which of the range’s bounds are not
:math:`\infty`. ``boundedType`` is an enumeration constant of the
type ``BoundedRangeType``. It is discussed further below.
- ``stridable`` is a boolean that determines whether the range’s stride
can take on values other than 1. ``stridable`` is ``false`` by
default. A range is called *stridable* if its type’s ``stridable`` is
``true``.
``boundedType`` is one of the constants of the following type:
::
enum BoundedRangeType { bounded, boundedLow, boundedHigh, boundedNone };
The value of ``boundedType`` determines which bounds of the range are
specified (making the range “bounded”, as opposed to infinite, in the
corresponding direction(s)) as follows:
- ``bounded``: both bounds are specified.
- ``boundedLow``: the low bound is specified (the high bound is
+\ :math:`\infty`).
- ``boundedHigh``: the high bound is specified (the low bound is
-:math:`\infty`).
- ``boundedNone``: neither bound is specified (both bounds are
:math:`\infty`).
``boundedType`` is ``BoundedRangeType.bounded`` by default.
The parameters ``idxType``, ``boundedType``, and ``stridable`` affect
all values of the corresponding range type. For example, the range’s low
bound is -:math:`\infty` if and only if the ``boundedType`` of that
range’s type is either ``boundedHigh`` or ``boundedNone``.
*Rationale*.
Providing ``boundedType`` and ``stridable`` in a range’s type allows
the compiler to identify the more common cases where the range is
``bounded`` and/or its stride is 1. The compiler can also detect user
and library code that is specialized to these cases. As a result, the
compiler has the opportunity to optimize these cases and the
specialized code more aggressively.
A range type has the following syntax:
::
range-type:
`range' ( named-expression-list )
That is, a range type is obtained as if by invoking the range type
constructor (§`24.3.6 <#Type_Constructors>`__) that has the following
header:
::
proc range(type idxType = int,
param boundedType = BoundedRangeType.bounded,
param stridable = false) type
As a special case, the keyword ``range`` without a parenthesized
argument list refers to the range type with the default values of all
its parameters, i.e., ``range(int, BoundedRangeType.bounded, false)``.
*Example (rangeVariable.chpl)*.
The following declaration declares a variable ``r`` that can
represent ranges of 32-bit integers, with both high and low bounds
specified, and the ability to have a stride other than 1.
::
var r: range(int(32), BoundedRangeType.bounded, stridable=true);
::
writeln(r);
var i32: int(32) = 3;
r = i32..13 by 3 align 1;
writeln(r);
::
1..0
3..13 by 3 align 1
.. _Range_Values:
Range Values
------------
A range value consists of the range’s four primary properties
(§`20.1 <#Range_Concepts>`__): low bound, high bound, stride and
alignment.
.. _Range_Literals:
Range Literals
~~~~~~~~~~~~~~
Range literals are specified with the following syntax.
::
range-literal:
expression .. expression
expression ..
.. expression
..
The expressions to the left and to the right of ``..``, when given, are
called the low bound and the high bound expression, respectively.
The type of a range literal is a range with the following parameters:
- ``idxType`` is determined as follows:
- If both the low bound and the high bound expressions are given and
have the same type, then ``idxType`` is that type.
- If both the low bound and the high bound expressions are given and
an implicit conversion is allowed from one expression’s type to
the other’s, then ``idxType`` is that type.
- If only one bound expression is given and it has an integral,
boolean, or enumerated type, then ``idxType`` is that type.
- If neither bound expression is given, then ``idxType`` is ``int``.
- Otherwise, the range literal is not legal.
- ``boundedType`` is a value of the type ``BoundedRangeType`` that is
determined as follows:
- ``bounded``, if both the low bound and the high bound expressions
are given,
- ``boundedLow``, if only the high bound expression is given,
- ``boundedHigh``, if only the low bound expression is given,
- ``boundedNone``, if neither bound expression is given.
- ``stridable`` is ``false``.
The value of a range literal is as follows:
- The low bound is given by the low bound expression, if present, and
is -:math:`\infty` otherwise.
- The high bound is given by the upper bound expression, if present,
and is +\ :math:`\infty` otherwise.
- The stride is 1.
- The alignment is ambiguous.
.. _Range_Default_Values:
Default Values
~~~~~~~~~~~~~~
The default value for a range type depends on the type’s ``boundedType``
parameter as follows:
- ``1..0`` (an empty range) if ``boundedType`` is ``bounded``
- ``1..`` if ``boundedType`` is ``boundedLow``
- ``..0`` if ``boundedType`` is ``boundedHigh``
- ``..`` if ``boundedType`` is ``boundedNone``
..
*Rationale*.
We use 0 and 1 to represent an empty range because these values are
available for any ``idxType``.
We have not found the natural choice of the default value for
``boundedLow`` and ``boundedHigh`` ranges. The values indicated above
are distinguished by the following property. Slicing the default
value for a ``boundedLow`` range with the default value for a
``boundedHigh`` range (or visa versa) produces an empty range,
matching the default value for a ``bounded`` range
.. _Ranges_Common_Operations:
Common Operations
-----------------
All operations on a range return a new range rather than modifying the
existing one. This supports a coding style in which all ranges are
*immutable* (i.e. declared as ``const``).
*Rationale*.
The intention is to provide ranges as immutable objects.
Immutable objects may be cached without creating coherence concerns.
They are also inherently thread-safe. In terms of implementation,
immutable objects are created in a consistent state and stay that
way: Outside of initializers, internal consistency checks can be
dispensed with.
These are the same arguments as were used to justify making strings
immutable in Java and C#.
.. _Range_Assignment:
Range Assignment
~~~~~~~~~~~~~~~~
Assigning one range to another results in the target range copying the
low and high bounds, stride, and alignment from the source range.
Range assignment is legal when:
- An implicit conversion is allowed from ``idxType`` of the source
range to ``idxType`` of the destination range type,
- the two range types have the same ``boundedType``, and
- either the destination range is stridable or the source range is not
stridable.
.. _Range_Comparisons:
Range Comparisons
~~~~~~~~~~~~~~~~~
Ranges can be compared using equality and inequality.
::
proc ==(r1: range(?), r2: range(?)): bool
Returns ``true`` if the two ranges have the same represented sequence or
the same four primary properties, and ``false`` otherwise.
.. _Iterating_over_Ranges:
Iterating over Ranges
~~~~~~~~~~~~~~~~~~~~~
A range can be used as an iterator expression in a loop. This is legal
only if the range is iterable. In this case the loop iterates over the
members of the range’s represented sequence, in the order defined by the
sequence. If the range is empty, no iterations are executed.
*Cray’s Chapel Implementation*.
An attempt to iterate over a range causes an error if adding stride
to the range’s last index overflows its index type, i.e. if the sum
is greater than the index type’s maximum value, or smaller than its
minimum value.
.. _Iterating_over_Unbounded_Ranges_in_Zippered_Iterations:
Iterating over Unbounded Ranges in Zippered Iterations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When a range with the first index but without the last index is used in
a zippered iteration ( §`11.9.1 <#Zipper_Iteration>`__), it generates as
many indices as needed to match the other iterator(s).
*Example (zipWithUnbounded.chpl)*.
The code
::
for i in zip(1..5, 3..) do
write(i, "; ");
::
writeln();
produces the output
::
(1, 3); (2, 4); (3, 5); (4, 6); (5, 7);
.. _Range_Promotion_of_Scalar_Functions:
Range Promotion of Scalar Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Range values may be passed to a scalar function argument whose type
matches the range’s index type. This results in a promotion of the
scalar function as described in §\ `27.5 <#Promotion>`__.
*Example (rangePromotion.chpl)*.
Given a function ``addOne(x:int)`` that accepts ``int`` values and a
range ``1..10``, the function ``addOne()`` can be called with
``1..10`` as its actual argument which will result in the function
being invoked for each value in the range.
::
proc addOne(x:int) {
return x + 1;
}
var A:[1..10] int;
A = addOne(1..10);
::
writeln(A);
::
2 3 4 5 6 7 8 9 10 11
The last statement is equivalent to:
::
forall (a,i) in zip(A,1..10) do
a = addOne(i);
.. _Range_Operators:
Range Operators
---------------
The following operators can be applied to range expressions and are
described in this section: stride (``by``), alignment (``align``), count
(``\#``) and slicing (``\(\)`` or ``\[\]``). Chapel also defines a set
of functions that operate on ranges. They are described in
§\ `20.6 <#Predefined_Range_Functions>`__.
::
range-expression:
expression
strided-range-expression
counted-range-expression
aligned-range-expression
sliced-range-expression
.. _By_Operator_For_Ranges:
By Operator
~~~~~~~~~~~
The ``by`` operator selects a subsequence of the range’s represented
sequence, optionally reversing its direction. The operator takes two
arguments, a base range and an integral step. It produces a new range
whose represented sequence contains each :math:`|`\ step\ :math:`|`-th
element of the base range’s represented sequence. The operator reverses
the direction of the represented sequence if step\ :math:`<`\ 0. If the
resulting sequence is increasing, it starts at the base range’s aligned
low bound, if it exists. If the resulting sequence is decreasing, it
starts at the base range’s aligned high bound, if it exists. Otherwise,
the base range’s alignment is used to determine which members of the
represented sequence to retain. If the base range’s represented sequence
is undefined, the resulting sequence is undefined, too.
The syntax of the ``by`` operator is:
::
strided-range-expression:
range-expression `by' step-expression
step-expression:
expression
The type of the step must be a signed or unsigned integer of the same
bit size as the base range’s ``idxType``, or an implicit conversion must
be allowed to that type from the step’s type. It is an error for the
step to be zero.
*Future*.
We may consider allowing the step to be of any integer type, for
maximum flexibility.
The type of the result of the ``by`` operator is the type of the base
range, but with the ``stridable`` parameter set to ``true``.
Formally, the result of the ``by`` operator is a range with the
following primary properties:
- The low and upper bounds are the same as those of the base range.
- The stride is the product of the base range’s stride and the step,
cast to the base range’s stride type before multiplying.
- The alignment is:
- the aligned low bound of the base range, if such exists and the
stride is positive;
- the aligned high bound of the base range, if such exists and the
stride is negative;
- the same as that of the base range, otherwise.
..
*Example (rangeByOperator.chpl)*.
In the following declarations, range ``r1`` represents the odd
integers between 1 and 20. Range ``r2`` strides ``r1`` by two and
represents every other odd integer between 1 and 20: 1, 5, 9, 13 and
17.
::
var r1 = 1..20 by 2;
var r2 = r1 by 2;
::
writeln(r1);
writeln(r2);
::
1..20 by 2
1..20 by 4
*Rationale*.
*Why isn’t the high bound specified first if the stride is negative?*
The reason for this choice is that the ``by`` operator is binary, not
ternary. Given a range ``R`` initialized to ``1..3``, we want
``R by -1`` to contain the ordered sequence :math:`3,2,1`. But then
``R by -1`` would be different from ``3..1 by -1`` even though it
should be identical by substituting the value in R into the
expression.
.. _Align_Operator_For_Ranges:
Align Operator
~~~~~~~~~~~~~~
The ``align`` operator can be applied to any range, and creates a new
range with the given alignment.
The syntax for the ``align`` operator is:
::
aligned-range-expression:
range-expression `align' expression
The type of the resulting range expression is the same as that of the
range appearing as the left operand, but with the ``stridable``
parameter set to ``true``. An implicit conversion from the type of the
right operand to the index type of the operand range must be allowed.
The resulting range has the same low and high bounds and stride as the
source range. The alignment equals the ``align`` operator’s right
operand and therefore is not ambiguous.
*Example (alignedStride.chpl)*.
::
var r1 = 0 .. 10 by 3 align 0;
for i in r1 do
write(" ", i); // Produces "0 3 6 9".
writeln();
var r2 = 0 .. 10 by 3 align 1;
for i in r2 do
write(" ", i); // Produces "1 4 7 10".
writeln();
::
0 3 6 9
1 4 7 10
When the stride is negative, the same indices are printed in reverse:
*Example (alignedNegStride.chpl)*.
::
var r3 = 0 .. 10 by -3 align 0;
for i in r3 do
write(" ", i); // Produces "9 6 3 0".
writeln();
var r4 = 0 .. 10 by -3 align 1;
for i in r4 do
write(" ", i); // Produces "10 7 4 1".
writeln();
::
9 6 3 0
10 7 4 1
To create a range aligned relative to its ``first`` index, use the
``offset`` method (§`[Range_Offset_Method] <#Range_Offset_Method>`__).
.. _Count_Operator:
Count Operator
~~~~~~~~~~~~~~
The ``\#`` operator takes a range and an integral count and creates a
new range containing the specified number of indices. The low or high
bound of the left operand is preserved, and the other bound adjusted to
provide the specified number of indices. If the count is positive,
indices are taken from the start of the range; if the count is negative,
indices are taken from the end of the range. The count must be less than
or equal to the ``length`` of the range.
::
counted-range-expression:
range-expression # expression
The type of the count expression must be a signed or unsigned integer of
the same bit size as the base range’s ``idxType``, or an implicit
conversion must be allowed to that type from the count’s type.
The type of the result of the ``\#`` operator is the type of the range
argument.
Depending on the sign of the count and the stride, the high or low bound
is unchanged and the other bound is adjusted so that it is
:math:`c * stride - 1` units away. Specifically:
- If the count times the stride is positive, the low bound is preserved
and the high bound is adjusted to be one less than the low bound plus
that product.
- If the count times the stride is negative, the high bound is
preserved and the low bound is adjusted to be one greater than the
high bound plus that product.
..
*Rationale*.
Following the principle of preserving as much information from the
original range as possible, we must still choose the other bound so
that exactly *count* indices lie within the range. Making the two
bounds lie :math:`count * stride - 1` apart will achieve this,
regardless of the current alignment of the range.
This choice also has the nice symmetry that the alignment can be
adjusted without knowing the bounds of the original range, and the
same number of indices will be produced:
::
r # 4 align 0 // Contains four indices.
r # 4 align 1 // Contains four indices.
r # 4 align 2 // Contains four indices.
r # 4 align 3 // Contains four indices.
It is an error to apply the count operator with a positive count to a
range that has no first index. It is also an error to apply the count
operator with a negative count to a range that has no last index. It is
an error to apply the count operator to a range that is ambiguously
aligned.
*Example (rangeCountOperator.chpl)*.
The following declarations result in equivalent ranges.
::
var r1 = 1..10 by -2 # -3;
var r2 = ..6 by -2 # 3;
var r3 = -6..6 by -2 # 3;
var r4 = 1..#6 by -2;
::
writeln(r1 == r2 \&\& r2 == r3 \&\& r3 == r4);
writeln((r1, r2, r3, r4));
::
true
(1..6 by -2, 1..6 by -2, 1..6 by -2, 1..6 by -2)
Each of these ranges represents the ordered set of three indices: 6,
4, 2.
.. _Range_Arithmetic:
Arithmetic Operators
~~~~~~~~~~~~~~~~~~~~
The following arithmetic operators are defined on ranges and integral
types:
::
proc +(r: range, s: integral): range
proc +(s: integral, r: range): range
proc -(r: range, s: integral): range
The ``+`` and ``-`` operators apply the scalar via the operator to the
range’s low and high bounds, producing a shifted version of the range.
If the operand range is unbounded above or below, the missing bounds are
ignored. The index type of the resulting range is the type of the value
that would result from an addition between the scalar value and a value
with the range’s index type. The bounded and stridable parameters for
the result range are the same as for the input range.
The stride of the resulting range is the same as the stride of the
original. The alignment of the resulting range is shifted by the same
amount as the high and low bounds. It is permissible to apply the shift
operators to a range that is ambiguously aligned. In that case, the
resulting range is also ambiguously aligned.
*Example (rangeAdd.chpl)*.
The following code creates a bounded, non-stridable range ``r`` which
has an index type of ``int`` representing the indices
:math:`{0, 1, 2, 3}`. It then uses the ``+`` operator to create a
second range ``r2`` representing the indices :math:`{1, 2, 3, 4}`.
The ``r2`` range is bounded, non-stridable, and is represented by
indices of type ``int``.
::
var r = 0..3;
var r2 = r + 1; // 1..4
::
writeln((r, r2));
::
(0..3, 1..4)
.. _Range_Slicing:
Range Slicing
~~~~~~~~~~~~~
Ranges can be *sliced* using other ranges to create new sub-ranges. The
resulting range represents the intersection between the two ranges’
represented sequences. The stride and alignment of the resulting range
are adjusted as needed to make this true. ``idxType`` and the sign of
the stride of the result are determined by the first operand.
Range slicing is specified by the syntax:
::
sliced-range-expression:
range-expression ( range-expression )
range-expression [ range-expression ]
If either of the operand ranges is ambiguously aligned, then the
resulting range is also ambiguously aligned. In this case, the result is
valid only if the strides of the operand ranges are relatively prime.
Otherwise, an error is generated at run time.
*Rationale*.
If the strides of the two operand ranges are relatively prime, then
they are guaranteed to have some elements in their intersection,
regardless whether their relative alignment can be determined. In
that case, the bounds and stride in the resulting range are valid
with respect to the given inputs. The alignment can be supplied later
to create a valid range.
If the strides are not relatively prime, then the result of the
slicing operation would be completely ambiguous. The only reasonable
action for the implementation is to generate an error.
If the resulting sequence cannot be expressed as a range of the original
type, the slice expression evaluates to the empty range ``1..0``. This
can happen, for example, when the operands represent all odd and all
even numbers, or when the first operand is an unbounded range with
unsigned ``idxType`` and the second operand represents only negative
numbers.
*Example (rangeSlicing.chpl)*.
In the following example, ``r`` represents the integers from 1 to 20
inclusive. Ranges ``r2`` and ``r3`` are defined using range slices
and represent the indices from 3 to 20 and the odd integers between 1
and 20 respectively. Range ``r4`` represents the odd integers between
1 and 20 that are also divisible by 3.
::
var r = 1..20;
var r2 = r[3..];
var r3 = r[1.. by 2];
var r4 = r3[0.. by 3];
::
writeln((r, r2, r3, r4));
::
(1..20, 3..20, 1..20 by 2, 1..20 by 6 align 3)
.. _Predefined_Range_Functions:
Predefined Functions on Ranges
------------------------------
.. _Range_Type_Accessors:
Range Type Parameters
~~~~~~~~~~~~~~~~~~~~~
::
proc range.boundedType : BoundedRangeType
Returns the ``boundedType`` parameter of the range’s type.
::
proc range.idxType : type
Returns the ``idxType`` parameter of the range’s type.
::
proc range.stridable : bool
Returns the ``stridable`` parameter of the range’s type.
.. _Range_Properties:
Range Properties
~~~~~~~~~~~~~~~~
Most of the methods in this subsection report on the range properties
defined in §\ `20.1 <#Range_Concepts>`__. A range’s represented sequence
can be examined, for example, by iterating over the range in a for loop
§\ `11.9 <#The_For_Loop>`__.
*Open issue*.
The behavior of the methods that report properties that may be
undefined, :math:`\infty`, or ambiguous, may change.
::
proc range.aligned : bool
Reports whether the range’s alignment is unambiguous.
::
proc range.alignedHigh : idxType
Returns the range’s aligned high bound. If the aligned high bound is
undefined (does not exist), the behavior is undefined.
*Example (alignedHigh.chpl)*.
The following code:
::
var r = 0..20 by 3;
writeln(r.alignedHigh);
produces the output
::
18
::
proc range.alignedLow : idxType
Returns the range’s aligned low bound. If the aligned low bound is
undefined (does not exist), the behavior is undefined.
::
proc range.alignment : idxType
Returns the range’s alignment. If the alignment is ambiguous, the
behavior is undefined. See also ``aligned``.
::
proc range.first : idxType
Returns the range’s first index. If the range has no first index, the
behavior is undefined. See also ``hasFirst``.
::
proc range.hasFirst(): bool
Reports whether the range has the first index.
::
proc range.hasHighBound() param: bool
Reports whether the range’s high bound is *not* +\ :math:`\infty`.
::
proc range.hasLast(): bool
Reports whether the range has the last index.
::
proc range.hasLowBound() param: bool
Reports whether the range’s low bound is *not* -:math:`\infty`.
::
proc range.high : idxType
Returns the range’s high bound. If the high bound is +\ :math:`\infty`,
the behavior is undefined. See also ``hasHighBound``.
::
proc range.isAmbiguous(): bool
Reports whether the range is ambiguously aligned.
::
proc range.last : idxType
Returns the range’s last index. If the range has no last index, the
behavior is undefined. See also ``hasLast``.
::
proc range.length : idxType
Returns the number of indices in the range’s represented sequence. If
the represented sequence is infinite or is undefined, an error is
generated.
::
proc range.low : idxType
Returns the range’s low bound. If the low bound is -:math:`\infty`, the
behavior is undefined. See also ``hasLowBound``.
::
proc range.size : idxType
Same as :math:`range`.length.
::
proc range.stride : int(numBits(idxType))
Returns the range’s stride. This will never return 0. If the range is
not stridable, this will always return 1.
.. _Range_Queries:
Other Queries
~~~~~~~~~~~~~
::
proc range.boundsCheck(r2: range(?)): bool
Returns ``false`` if either range is ambiguously aligned. Returns
``true`` if range ``r2`` lies entirely within this range and ``false``
otherwise.
::
proc ident(r1: range(?), r2: range(?)): bool
Returns ``true`` if the two ranges are the same in every respect: i.e.
the two ranges have the same ``idxType``, ``boundedType``,
``stridable``, ``low``, ``high``, ``stride`` and ``alignment`` values.
::
proc range.indexOrder(i: idxType): idxType
If ``i`` is a member of the range’s represented sequence, returns an
integer giving the ordinal index of ``i`` within the sequence using
0-based indexing. Otherwise, returns ``(-1):idxType``. It is an error to
invoke ``indexOrder`` if the represented sequence is not defined or the
range does not have the first index.
*Example*.
The following calls show the order of index 4 in each of the given
ranges:
::
(0..10).indexOrder(4) == 4
(1..10).indexOrder(4) == 3
(3..5).indexOrder(4) == 1
(0..10 by 2).indexOrder(4) == 2
(3..5 by 2).indexOrder(4) == -1
::
proc range.member(i: idxType): bool
Returns ``true`` if the range’s represented sequence contains ``i``,
``false`` otherwise. It is an error to invoke ``member`` if the
represented sequence is not defined.
::
proc range.member(other: range): bool
Reports whether ``other`` is a subrange of the receiver. That is, if the
represented sequences of the receiver and ``other`` are defined and the
receiver’s sequence contains all members of the ``other``\ ’s sequence.
.. _Range_Transformations:
Range Transformations
~~~~~~~~~~~~~~~~~~~~~
::
proc range.alignHigh()
Sets the high bound of this range to its aligned high bound, if it is
defined. Generates an error otherwise.
::
proc range.alignLow()
Sets the low bound of this range to its aligned low bound, if it is
defined. Generates an error otherwise.
::
proc range.expand(i: idxType)
Returns a new range whose bounds are extended by :math:`i` units on each
end. If :math:`i <
0` then the resulting range is contracted by its absolute value. In
symbols, given that the operand range is represented by the tuple
:math:`(l,h,s,a)`, the result is :math:`(l-i,h+i,s,a)`. The stride and
alignment of the original range are preserved. If the operand range is
ambiguously aligned, then so is the resulting range.
::
proc range.exterior(i: idxType)
Returns a new range containing the indices just outside the low or high
bound of the range (low if :math:`i < 0` and high otherwise). The stride
and alignment of the original range are preserved. Let the operand range
be denoted by the tuple :math:`(l,h,s,a)`. Then:
- if :math:`i < 0`, the result is :math:`(l+i,l-1,s,a)`,
- if :math:`i > 0`, the result is :math:`(h+1,h+i,s,a)`, and
- if :math:`i = 0`, the result is :math:`(l,h,s,a)`.
If the operand range is ambiguously aligned, then so is the resulting
range.
::
proc range.interior(i: idxType)
Returns a new range containing the indices just inside the low or high
bound of the range (low if :math:`i < 0` and high otherwise). The stride
and alignment of the original range are preserved. Let the operand range
be denoted by the tuple :math:`(l,h,s,a)`. Then:
- if :math:`i < 0`, the result is :math:`(l,l-(i-1),s,a)`,
- if :math:`i > 0`, the result is :math:`(h-(i-1),h,s,a)`, and
- if :math:`i = 0`, the result is :math:`(l,h,s,a)`.
This differs from the behavior of the count operator, in that
``interior()`` preserves the alignment, and it uses the low and high
bounds rather than ``first`` and ``last`` to establish the bounds of the
resulting range. If the operand range is ambiguously aligned, then so is
the resulting range.
::
proc range.offset(n: idxType)
[Range_Offset_Method]
Returns a new range whose alignment is this range’s first index plus
``n``. The new alignment, therefore, is not ambiguous. If the range has
no first index, a run-time error is generated.
::
proc range.translate(i: integral)
Returns a new range with its ``low``, ``high`` and ``alignment`` values
adjusted by :math:`i`. The ``stride`` value is preserved. If the range’s
alignment is ambiguous, the behavior is undefined.
Domains
=======
[Domains]
A *domain* is a first-class representation of an index set. Domains are
used to specify iteration spaces, to define the size and shape of arrays
(§`[Arrays] <#Arrays>`__), and to specify aggregate operations like
slicing. A domain can specify a single- or multi-dimensional rectangular
iteration space or represent a set of indices of a given type. Domains
can also represent a subset of another domain’s index set, using either
a dense or sparse representation. A domain’s indices may potentially be
distributed across multiple locales as described
in §\ `[Domain_Maps] <#Domain_Maps>`__, thus supporting global-view data
structures.
In the next subsection, we introduce the key characteristics of domains.
In §\ `21.2 <#Base_Domain_Types_and_Values>`__, we discuss the types and
values that can be associated with a base domain.
In §\ `21.3 <#Simple_Subdomain_Types_and_Values>`__, we discuss the
types and values of simple subdomains that can be created from those
base domains. In §\ `21.4 <#Sparse_Subdomain_Types_and_Values>`__, we
discuss the types and values of sparse subdomains. The remaining
sections describe the important manipulations that can be performed with
domains, as well as the predefined operators and functions defined for
domains.
Domain Overview
---------------
There are three *kinds* of domain, distinguished by their subset
dependencies: *base domains*, *subdomains* and *sparse subdomains*. A
base domain describes an index set spanning one or more dimensions. A
subdomain creates an index set that is a subset of the indices in a base
domain or another subdomain. Sparse subdomains are subdomains which can
represent sparse index subsets efficiently. Simple subdomains are
subdomains that are not sparse. These relationships can be represented
as follows:
::
domain-type:
base-domain-type
simple-subdomain-type
sparse-subdomain-type
Domains can be further classified according to whether they are
*regular* or *irregular*. A regular domain represents a rectangular
iteration space and can have a compact representation whose size is
independent of the number of indices. Rectangular domains, with the
exception of sparse subdomains, are regular.
An irregular domain can store an arbitrary set of indices of an
arbitrary but homogeneous index type. Irregular domains typically
require space proportional to the number of indices being represented.
All *associative* domain types and their subdomains (including sparse
subdomains) are irregular. Sparse subdomains of regular domains are also
irregular.
An index set can be either *ordered* or *unordered* depending on whether
its members have a well-defined order relationship. All regular domains
are ordered. All associative domains are unordered.
The type of a domain describes how a domain is represented and the
operations that can be performed upon it, while its value is the set of
indices it represents. In addition to storing a value, each domain
variable has an identity that distinguishes it from other domains that
may have the same type and value. This identity is used to define the
domain’s relationship with subdomains, index
types (§\ `21.5 <#Index_Types>`__), and
arrays (§\ `22.11 <#Association_of_Arrays_to_Domains>`__).
The runtime representation of a domain is controlled by its domain map.
Domain maps are presented in §\ `[Domain_Maps] <#Domain_Maps>`__.
.. _Base_Domain_Types_and_Values:
Base Domain Types and Values
----------------------------
Base domain types can be classified as regular or irregular. Dense and
strided rectangular domains are regular domains. Irregular base domain
types include all of the associative domain types.
::
base-domain-type:
rectangular-domain-type
associative-domain-type
These base domain types are discussed in turn in the following
subsections.
Rectangular Domains
~~~~~~~~~~~~~~~~~~~
Rectangular domains describe multidimensional rectangular index sets.
They are characterized by a tensor product of ranges and represent
indices that are tuples of an integral type. Because their index sets
can be represented using ranges, regular domain values typically require
only :math:`O(1)` space.
Rectangular Domain Types
^^^^^^^^^^^^^^^^^^^^^^^^
Rectangular domain types are parameterized by three things:
- ``rank`` a positive ``int`` value indicating the number of dimensions
that the domain represents;
- ``idxType`` a type member representing the index type for each
dimension; and
- ``stridable`` a ``bool`` parameter indicating whether any of the
domain’s dimensions will be characterized by a strided range.
If ``rank`` is :math:`1`, the index type represented by a rectangular
domain is ``idxType``. Otherwise, the index type is the homogeneous
tuple type ``rank*idxType``. If unspecified, ``idxType`` defaults to
``int`` and ``stridable`` defaults to ``false``.
*Open issue*.
We may represent a rectangular domain’s index type as rank*idxType
even if rank is 1. This would eliminate a lot of code currently used
to support the special (rank == 1) case.
The syntax of a rectangular domain type is summarized as follows:
::
rectangular-domain-type:
`domain' ( named-expression-list )
where ``named-expression-list`` permits the values of ``rank``,
``idxType``, and ``stridable`` to be specified using standard type
signature.
*Example (typeFunctionDomain.chpl)*.
The following declarations both create an uninitialized rectangular
domain with three dimensions, with ``int`` indices:
::
var D1 : domain(rank=3, idxType=int, stridable=false);
var D2 : domain(3);
::
writeln(D1);
writeln(D2);
::
{1..0, 1..0, 1..0}
{1..0, 1..0, 1..0}
.. _Rectangular_Domain_Values:
Rectangular Domain Values
^^^^^^^^^^^^^^^^^^^^^^^^^
Each dimension of a rectangular domain is a range of type
``range(idxType, BoundedRangeType.bounded, stridable)``. The index set
for a rank 1 domain is the set of indices described by its singleton
range. The index set for a rank \ :math:`n` domain is the set of all
``n*idxType`` tuples described by the tensor product of its ranges. When
expanded (as by an iterator), rectangular domain indices are ordered
according to the lexicographic order of their values. That is, the index
with the highest rank is listed first and changes most slowly. [3]_
*Future*.
Domains defined using unbounded ranges may be supported.
Literal rectangular domain values are represented by a comma-separated
list of range expressions of matching ``idxType`` enclosed in curly
braces:
::
rectangular-domain-literal:
{ range-expression-list }
range-expression-list:
range-expression
range-expression, range-expression-list
The type of a rectangular domain literal is defined as follows:
- ``rank`` = the number of range expressions in the literal;
- ``idxType`` = the type of the range expressions;
- ``stridable`` = ``true`` if any of the range expressions are
stridable, otherwise ``false``.
If the index types in the ranges differ and all of them can be promoted
to the same type, then that type is used as the ``idxType``. Otherwise,
the domain literal is invalid.
*Example*.
The expression ``\{1..5, 1..5\}`` defines a rectangular domain with
type ``domain(rank=2,`` ``idxType=int,`` ``stridable=false)``. It is
a :math:`5 \times 5` domain with the indices:
.. math:: (1, 1), (1, 2), \ldots, (1, 5), (2, 1), \ldots (5, 5).
A domain expression may contain bounds which are evaluated at runtime.
*Example*.
In the code
::
var D: domain(2) = {1..n, 1..n};
``D`` is defined as a two-dimensional, nonstridable rectangular
domain with an index type of ``2*int`` and is initialized to contain
the set of indices :math:`(i,j)` for all :math:`i` and :math:`j` such
that :math:`i \in {1, 2, \ldots, n}` and
:math:`j \in {1, 2, \ldots, n}`.
The default value of a domain type is the ``rank`` default range values
for type:
``range(idxType, BoundedRangeType.bounded, stridable)``
..
*Example (rectangularDomain.chpl)*.
The following creates a two-dimensional rectangular domain and then
uses this to declare an array. The array indices are iterated over
using the domain’s ``dim()`` method, and each element is filled with
some value. Then the array is printed out.
Thus, the code
::
var D : domain(2) = {1..2, 1..7};
var A : [D] int;
for i in D.dim(1) do
for j in D.dim(2) do
A[i,j] = 7 * i**2 + j;
writeln(A);
produces
::
8 9 10 11 12 13 14
29 30 31 32 33 34 35
Associative Domains
~~~~~~~~~~~~~~~~~~~
Associative domains represent an arbitrary set of indices of a given
type and can be used to describe sets or to create dictionary-style
arrays (hash tables). The type of indices of an associative domain, or
its ``idxType``, can be any primitive type except ``void`` or any class
type.
.. _Associative_Domain_Types:
Associative Domain Types
^^^^^^^^^^^^^^^^^^^^^^^^
An associative domain type is parameterized by ``idxType``, the type of
the indices that it stores. The syntax is as follows:
::
associative-domain-type:
`domain' ( associative-index-type )
`domain' ( `opaque' )
associative-index-type:
type-expression
The three expansions of ``associative-domain-type`` correspond to the
three kinds of associative domain listed below.
#. In general, ``associative-index-type`` determines ``idxType`` of the
associative domain type.
#. Opaque domains are a special case, indicated by the type ``opaque``.
Anonymous values of the type ``opaque`` are used as index values in
this case.
When an associative domain is used as the index set of an array, the
relation between the indices and the array elements can be thought of as
a map between the values of the index set and the elements stored in the
array. Opaque domains can be used to build unstructured arrays that are
similar to pointer-based data structures in conventional languages.
.. _Associative_Domain_Values:
Associative Domain Values
^^^^^^^^^^^^^^^^^^^^^^^^^
An associative domain’s value is simply the set of all index values that
the domain describes. The iteration order over the indices of an
associative domain is undefined.
Specification of an associative domain literal value follows a similar
syntax as rectangular domain literal values. What differentiates the two
are the types of expressions specified in the comma separated list. Use
of values of a type other than ranges will result in the construction of
an associative domain.
::
associative-domain-literal:
{ associative-expression-list }
associative-expression-list:
non-range-expression
non-range-expression, associative-expression-list
non-range-expression:
expression
It is required that the types of the values used in constructing an
associative domain literal value be of the same type. If the types of
the indices does not match a compiler error will be issued.
*Future*.
Due to implementation of == over arrays it is currently not possible
to use arrays as indices within an associative domain.
..
*Open issue*.
Assignment of an associative domain literal results in a warning
message being printed alerting the user that whole-domain assignment
has been serialized. This results from the resize operation over
associative arrays not being parsafe.
*Example (associativeDomain.chpl)*.
The following example illustrates construction of an associative
domain containing string indices "bar" and "foo". Note that due to
internal hashing of indices the order in which the values of the
associative domain are iterated is not the same as their
specification order.
This code
::
var D : domain(string) = {"bar", "foo"};
writeln(D);
::
--no-warnings
produces the output
::
{foo, bar}
If uninitialized, the default value of an associative domain is the
empty index set.
Indices can be added to or removed from an associative domain as
described in §\ `21.8.6 <#Adding_and_Removing_Domain_Indices>`__.
.. _Simple_Subdomain_Types_and_Values:
Simple Subdomain Types and Values
---------------------------------
A subdomain is a domain whose indices are guaranteed to be a subset of
those described by another domain known as its *parent domain*. A
subdomain has the same type as its parent domain, and by default it
inherits the domain map of its parent domain. All domain types support
subdomains.
Simple subdomains are subdomains which are not sparse. Sparse subdomains
are discussed in the following section
(§`21.4 <#Sparse_Subdomain_Types_and_Values>`__). A simple subdomain
inherits its representation (regular or irregular) from its base domain
(or base subdomain). A sparse subdomain is always irregular, even if its
base domain is regular.
In all other respects, the two kinds of subdomain behave identically. In
this specification, “subdomain” refers to both simple and sparse
subdomains, unless it is specifically distinguished as one or the other.
*Rationale*.
Subdomains are provided in Chapel for a number of reasons: to
facilitate the ability of the compiler or a reader to reason about
the inter-relationship of distinct domain variables; to support the
author’s ability to omit redundant domain mapping specifications; to
support the compiler’s ability to reason about the relative alignment
of multiple domains; and to improve the compiler’s ability to prove
away bounds checks for array accesses.
.. _Simple_Subdomain_Types:
Simple Subdomain Types
~~~~~~~~~~~~~~~~~~~~~~
A simple subdomain type is specified using the following syntax:
::
simple-subdomain-type:
`subdomain' ( domain-expression )
This declares that ``domain-expression`` is the parent domain of this
subdomain type. A simple subdomain specifies a subdomain with the same
underlying representation as its base domain.
*Open issue*.
An open semantic issue for subdomains is when a subdomain’s subset
property should be re-verified once its parent domain is reassigned
and whether this should be done aggressively or lazily.
Simple Subdomain Values
~~~~~~~~~~~~~~~~~~~~~~~
The value of a simple subdomain is the set of all index values that the
subdomain describes.
The default value of a simple subdomain type is the same as the default
value of its parent’s type (§`21.2.1.2 <#Rectangular_Domain_Values>`__,
§\ `21.2.2.2 <#Associative_Domain_Values>`__).
A simple subdomain variable can be initialized or assigned to with a
tuple of values of the parent’s ``idxType``. Indices can also be added
to or removed from a simple subdomain as described in
§\ `21.8.6 <#Adding_and_Removing_Domain_Indices>`__. It is an error to
attempt to add an index to a subdomain that is not also a member of the
parent domain.
.. _Sparse_Subdomain_Types_and_Values:
Sparse Subdomain Types and Values
---------------------------------
::
sparse-subdomain-type:
`sparse' `subdomain'[OPT] ( domain-expression )
This declaration creates a sparse subdomain. *Sparse subdomains* are
irregular domains that describe an arbitrary subset of a domain, even if
the parent domain is a regular domain. Sparse subdomains are useful in
Chapel for defining *sparse arrays* in which a single element value
(usually “zero”) occurs frequently enough that it is worthwhile to avoid
storing it redundantly. The set difference between a sparse subdomain’s
index set and that of parent domain is the set of indices for which the
sparse array will store this replicated value.
See §\ `22.10 <#Sparse_Arrays>`__ for details about sparse arrays.
Sparse Subdomain Types
~~~~~~~~~~~~~~~~~~~~~~
Each root domain type has a unique corresponding sparse subdomain type.
Sparse subdomains whose parent domains are also sparse subdomains share
the same type.
.. _Sparse_Domain_Values:
Sparse Subdomain Values
~~~~~~~~~~~~~~~~~~~~~~~
A sparse subdomain’s value is simply the set of all index values that
the domain describes. If the parent domain defines an iteration order
over its indices, the sparse subdomain inherits that order.
There is no literal syntax for a sparse subdomain. However, a variable
of a sparse subdomain type can be initialized using a tuple of values of
the parent domain’s index type.
The default value for a sparse subdomain value is the empty set.
*Example*.
The following code declares a two-dimensional dense domain ``D``,
followed by a two dimensional sparse subdomain of ``D`` named
``SpsD``. Since ``SpsD`` is uninitialized, it will initially describe
an empty set of indices from ``D``.
::
const D: domain(2) = {1..n, 1..n};
var SpsD: sparse subdomain(D);
.. _Index_Types:
Domain Index Types
------------------
Each domain value has a corresponding compiler-provided *index type*
which can be used to represent values belonging to that domain’s index
set. Index types are described using the following syntax:
::
index-type:
`index' ( domain-expression )
A variable with a given index type is constrained to take on only values
available within the domain on which it is defined. This restriction
allows the compiler to prove away the bound checking that code safety
considerations might otherwise require. Due to the subset relationship
between a base domain and its subdomains, a variable of an index type
defined with respect to a subdomain is also necessarily a valid index
into the base domain.
Since an index types are known to be legal for a given domain, it may
also afford the opportunity to represent that index using an optimized
format that doesn’t simply store the index variable’s value. This fact
could be used to support accelerated access to arrays declared over that
domain. For example, iteration over an index type could be implemented
using memory pointers and strides, rather than explicitly calculating
the offset of each index within the domain.
These potential optimizations may make it less expensive to index into
arrays using index type variables of their domains or subdomains.
In addition, since an index type is associated with a specific domain or
subdomain, it carries more semantic weight than a generic index. For
example, one could iterate over a rectangular domain with integer bounds
using an ``int(n)`` as the index variable. However, it would be more
precise to use a variable of the domain’s index type.
*Open issue*.
An open issue for index types is what the semantics should be for an
index type value that is live across a modification to its domain’s
index set—particularly one that shrinks the index set. Our hypothesis
is that most stored indices will either have short lifespans or
belong to constant or monotonically growing domains. But these
semantics need to be defined nevertheless.
.. _Iteration_over_Domains:
Iteration Over Domains
----------------------
All domains support iteration via standard ``for``, ``forall``, and
``coforall`` loops. These loops iterate over all of the indices that the
domain describes. If the domain defines an iteration order of its
indices, then the indices are visited in that order.
The type of the iterator variable for an iteration over a domain named
``D`` is that domain’s index type, ``index(D)``.
.. _Domain_Arguments:
Domains as Arguments
--------------------
This section describes the semantics of passing domains as arguments to
functions.
Formal Arguments of Domain Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a domain value is passed to a formal argument of compatible domain
type by default intent, it is passed by reference in order to preserve
the domain’s identity.
.. _Domain_Promotion_of_Scalar_Functions:
Domain Promotion of Scalar Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Domain values may be passed to a scalar function argument whose type
matches the domain’s index type. This results in a promotion of the
scalar function as defined in §\ `27.5 <#Promotion>`__.
*Example*.
Given a function ``foo()`` that accepts real floating point values
and an associative domain ``D`` of type ``domain(real)``, ``foo`` can
be called with ``D`` as its actual argument which will result in the
function being invoked for each value in the index set of ``D``.
..
*Example*.
Given an array ``A`` with element type ``int`` declared over a
one-dimensional domain ``D`` with ``idxType`` ``int``, the array
elements can be assigned their corresponding index values by writing:
::
A = D;
This is equivalent to:
::
forall (a,i) in zip(A,D) do
a = i;
Domain Operations
-----------------
Chapel supplies predefined operators and functions that can be used to
manipulate domains. Unless otherwise noted, these operations are
applicable to a domain of any type, whether a base domain or a
subdomain.
.. _Domain_Assignment:
Domain Assignment
~~~~~~~~~~~~~~~~~
All domain types support domain assignment.
::
domain-expression:
domain-literal
domain-name
domain-assignment-expression
domain-striding-expression
domain-alignment-expression
domain-slice-expression
domain-literal:
rectangular-domain-literal
associative-domain-literal
domain-assignment-expression:
domain-name = domain-expression
domain-name:
identifier
Domain assignment is by value and causes the target domain variable to
take on the index set of the right-hand side expression. In practice,
the right-hand side expression is often another domain value; a tuple of
ranges (for regular domains); or a tuple of indices or a loop that
enumerates indices (for irregular domains). If the domain variable being
assigned was used to declare arrays, these arrays are reallocated as
discussed in §\ `22.11 <#Association_of_Arrays_to_Domains>`__.
It is an error to assign a stridable domain to an unstridable domain
without an explicit conversion.
*Example*.
The following three assignments show ways of assigning indices to a
sparse domain, ``SpsD``. The first assigns the domain two index
values, ``(1,1)`` and ``(n,n)``. The second assigns the domain all of
the indices along the diagonal from
``(1,1)``\ :math:`\ldots`\ ``(n,n)``. The third invokes an iterator
that is written to ``yield`` indices read from a file named
“inds.dat”. Each of these assignments has the effect of replacing the
previous index set with a completely new set of values.
::
SpsD = ((1,1), (n,n));
SpsD = [i in 1..n] (i,i);
SpsD = readIndicesFromFile("inds.dat");
.. _Domain_Striding:
Domain Striding
~~~~~~~~~~~~~~~
The ``by`` operator can be applied to a rectangular domain value in
order to create a strided rectangular domain value. The right-hand
operand to the ``by`` operator can either be an integral value or an
integral tuple whose size matches the domain’s rank.
::
domain-striding-expression:
domain-expression `by' expression
The type of the resulting domain is the same as the original domain but
with ``stridable`` set to true. In the case of an integer stride value,
the value of the resulting domain is computed by applying the integer
value to each range in the value using the ``by`` operator. In the case
of a tuple stride value, the resulting domain’s value is computed by
applying each tuple component to the corresponding range using the
``by`` operator.
.. _Domain_Alignment:
Domain Alignment
~~~~~~~~~~~~~~~~
The ``align`` operator can be applied to a rectangular domain value in
order to change the alignment of a rectangular domain value. The
right-hand operand to the ``align`` operator can either be an integral
value or an integral tuple whose size matches the domain’s rank.
::
domain-alignment-expression:
domain-expression `align' expression
The type of the resulting domain is the same as the original domain but
with ``stridable`` set to true. In the case of an integer alignment
value, the value of the resulting domain is computed by applying the
integer value to each range in the value using the ``align`` operator.
In the case of a tuple alignment value, the resulting domain’s value is
computed by applying each tuple component to the corresponding range
using the ``align`` operator.
.. _Domain_Slicing:
Domain Slicing
~~~~~~~~~~~~~~
Slicing is the application of an index set to a domain. It can be
written using either parentheses or square brackets. The index set can
be defined with either a domain or a list of ranges.
::
domain-slice-expression:
domain-expression [ slicing-index-set ]
domain-expression ( slicing-index-set )
slicing-index-set:
domain-expression
range-expression-list
The result of slicing, or a *slice*, is a new domain value that
represents the intersection of the index set of the domain being sliced
and the index set being applied. The type and domain map of the slice
match the domain being sliced.
Slicing can also be performed on an array, resulting in aliasing a
subset of the array’s elements (§`22.6 <#Array_Slicing>`__).
Domain-based Slicing
^^^^^^^^^^^^^^^^^^^^
If the brackets or parentheses contain a domain value, its index set is
applied for slicing.
*Open issue*.
Can we say that it is an alias in the case of sparse/associative?
.. _Range_Based_Slicing:
Range-based Slicing
^^^^^^^^^^^^^^^^^^^
When slicing rectangular domains or arrays, the brackets or parentheses
can contain a list of ``rank`` ranges. These ranges can either be
bounded or unbounded. When unbounded, they inherit their bounds from the
domain or array being sliced. The Cartesian product of the ranges’ index
sets is applied for slicing.
*Example*.
The following code declares a two dimensional rectangular domain
``D``, and then a number of subdomains of ``D`` by slicing into ``D``
using bounded and unbounded ranges. The ``InnerD`` domain describes
the inner indices of D, ``Col2OfD`` describes the 2nd column of
``D``, and ``AllButLastRow`` describes all of ``D`` except for the
last row.
::
const D: domain(2) = {1..n, 1..n},
InnerD = D[2..n-1, 2..n-1],
Col2OfD = D[.., 2..2],
AllButLastRow = D[..n-1, ..];
.. _Rank_Change_Slicing:
Rank-Change Slicing
^^^^^^^^^^^^^^^^^^^
For multidimensional rectangular domains and arrays, substituting
integral values for one or more of the ranges in a range-based slice
will result in a domain or array of lower rank.
The result of a rank-change slice on an array is an alias to a subset of
the array’s elements as described
in §\ `22.6.1 <#Rectangular_Array_Slicing>`__.
The result of rank-change slice on a domain is a subdomain of the domain
being sliced. The resulting subdomain’s type will be the same as the
original domain, but with a ``rank`` equal to the number of dimensions
that were sliced by ranges rather than integers.
.. _Count_Operator_Domains:
Count Operator
~~~~~~~~~~~~~~
The ``\#`` operator can be applied to dense rectangular domains with a
tuple argument whose size matches the rank of the domain (or optionally
an integer in the case of a 1D domain). The operator is equivalent to
applying the ``\#`` operator to the component ranges of the domain and
then using them to slice the domain as in
Section \ `21.8.4.2 <#Range_Based_Slicing>`__.
.. _Adding_and_Removing_Domain_Indices:
Adding and Removing Domain Indices
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
All irregular domain types support the ability to incrementally add and
remove indices from their index sets. This can either be done using
``add(i:idxType)`` and ``remove(i:idxType)`` methods on a domain
variable or by using the ``+=`` and ``-=`` assignment operators. It is
legal to add the same index to an irregular domain’s index set twice,
but illegal to remove an index that does not belong to the domain’s
index set.
*Open issue*.
These remove semantics seem dangerous in a parallel context; maybe
add flags to both the method versions of the call that say whether
they should balk or not? Or add exceptions...
As with normal domain assignments, arrays declared in terms of a domain
being modified in this way will be reallocated as discussed
in §\ `22.11 <#Association_of_Arrays_to_Domains>`__.
Predefined Methods on Domains
-----------------------------
This section gives a brief description of the library functions provided
for Domains. These are categorized by the type of domain to which they
apply: all, regular or irregular. Within each subsection, entries are
listed in alphabetical order.
Methods on All Domain Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The methods in this subsection can be applied to any domain.
::
proc domain.clear()
Resets this domain’s index set to the empty set.
*Example (clearAssociativeDomain)*.
This function provides a way to produce an empty associative domain.
When run, the code
::
enum Counter { one, two, three };
var D : domain ( Counter ) = {Counter.one, Counter.two};
writeln("D has ", D.numIndices, " indices.");
D.clear();
writeln("D has ", D.numIndices, " indices.");
prints out
::
D has 2 indices.
D has 0 indices.
::
proc domain.dist : dmap
Returns the domain map that implements this domain
*Example (getDomainMap)*.
In the code
::
use BlockDist;
proc foo(d : domain) where isSubtype(d.dist.type, Block) {
writeln("Block-distributed domain");
}
proc foo(d : domain) {
writeln("Unknown distribution");
}
var D = {1..10} dmapped Block({1..10});
foo(D);
``dist`` is used in a where-clause to determine the type of the
argument’s distribution. The output is:
::
Block-distributed domain
::
proc domain.idxType type
Returns the domain type’s ``idxType``. This function is not available on
opaque domains.
::
proc domain.indexOrder(i: index(domain)): idxType
If ``i`` is a member of the domain, returns the ordinal value of ``i``
using a total ordering of the domain’s indices using 0-based indexing.
Otherwise, it returns ``(-1):idxType``. For rectangular domains, this
ordering will be based on a row-major ordering of the indices; for other
domains, the ordering may be implementation-defined and unstable as
indices are added and removed from the domain.
::
proc isIrregularDom(d: domain) param
Returns a param ``true`` if the given domain is irregular, false
otherwise.
::
proc isOpaqueDom(d: domain) param
Returns a param ``true`` if the given domain is opaque, false otherwise.
::
proc isRectangularDom(d: domain) param
Returns a param ``true`` if the given domain is rectangular, false
otherwise.
::
proc isSparseDom(d: domain) param
Returns a param ``true`` if the given domain is sparse, false otherwise.
::
proc domain.member(i)
Returns true if the given index ``i`` is a member of this domain’s index
set, and false otherwise.
*Open issue*.
We would like to call the type of i above idxType, but it’s not true
for rectangular domains. That observation provides some motivation to
normalize the behavior.
::
proc domain.numIndices: capType
Returns the number of indices in the domain as a value of the capacity
type.
Methods on Regular Domains
~~~~~~~~~~~~~~~~~~~~~~~~~~
The methods described in this subsection can be applied to regular
domains only.
::
proc domain.dim(d: int): range
Returns the range of indices described by dimension ``d`` of the domain.
*Example*.
In the code
::
for i in D.dim(1) do
for j in D.dim(2) do
writeln(A(i,j));
domain ``D`` is iterated over by two nested loops. The first
dimension of ``D`` is iterated over in the outer loop. The second
dimension is iterated over in the inner loop.
::
proc domain.dims(): rank*range
Returns a tuple of ranges describing the dimensions of the domain.
::
proc domain.expand(off: integral): domain
proc domain.expand(off: rank*integral): domain
Returns a new domain that is the current domain expanded in dimension
``d`` if ``off`` or ``off(d)`` is positive or contracted in dimension
``d`` if ``off`` or ``off(d)`` is negative.
::
proc domain.exterior(off: integral): domain
proc domain.exterior(off: rank*integral): domain
Returns a new domain that is the exterior portion of the current domain
with ``off`` or ``off(d)`` indices for each dimension ``d``. If ``off``
or ``off(d)`` is negative, compute the exterior from the low bound of
the dimension; if positive, compute the exterior from the high bound.
::
proc domain.high: index(domain)
Returns the high index of the domain as a value of the domain’s index
type.
::
proc domain.interior(off: integral): domain
proc domain.interior(off: rank*integral): domain
Returns a new domain that is the interior portion of the current domain
with ``off`` or ``off(d)`` indices for each dimension ``d``. If ``off``
or ``off(d)`` is negative, compute the interior from the low bound of
the dimension; if positive, compute the interior from the high bound.
::
proc domain.low: index(domain)
Returns the low index of the domain as a value of the domain’s index
type.
::
proc domain.rank param : int
Returns the rank of the domain.
::
proc domain.size: capType
Same as :math:`Domain`.numIndices.
::
proc domain.stridable param : bool
Returns whether or not the domain is stridable.
::
proc domain.stride: int(numBits(idxType)) where rank == 1
proc domain.stride: rank*int(numBits(idxType))
Returns the stride of the domain as the domain’s stride type (for 1D
domains) or a tuple of the domain’s stride type (for multidimensional
domains).
::
proc domain.translate(off: integral): domain
proc domain.translate(off: rank*integral): domain
Returns a new domain that is the current domain translated by ``off`` or
``off(d)`` for each dimension ``d``.
Methods on Irregular Domains
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following methods are available only on irregular domain types.
::
proc +(d: domain, i: index(d))
proc +(i, d: domain) where i: index(d)
Adds the given index to the given domain. If the given index is already
a member of that domain, it is ignored.
::
proc +(d1: domain, d2: domain)
Merges the index sets of the two domain arguments.
::
proc -(d: domain, i: index(d))
Removes the given index from the given domain. It is an error if the
domain does not contain the given index.
::
proc -(d1: domain, d2: domain)
Removes the indices in domain ``d2`` from those in ``d1``. It is an
error if ``d2`` contains indices which are not also in ``d1``.
::
proc requestCapacity(s: int)
Resizes the domain internal storage to hold at least ``s`` indices.
Arrays
======
[Arrays]
An *array* is a map from a domain’s indices to a collection of variables
of homogeneous type. Since Chapel domains support a rich variety of
index sets, Chapel arrays are also richer than the traditional linear or
rectilinear array types in conventional languages. Like domains, arrays
may be distributed across multiple locales without explicitly
partitioning them using Chapel’s Domain
Maps (§\ `[Domain_Maps] <#Domain_Maps>`__).
.. _Array_Types:
Array Types
-----------
An array type is specified by the identity of the domain that it is
declared over and the element type of the array. Array types are given
by the following syntax:
::
array-type:
[ domain-expression ] type-expression
The ``domain-expression`` must specify a domain that the array can be
declared over. If the ``domain-expression`` is a domain literal, the
curly braces around the literal may be omitted.
*Example (decls.chpl)*.
In the code
::
const D: domain(2) = {1..10, 1..10};
var A: [D] real;
::
writeln(D);
writeln(A);
::
{1..10, 1..10}
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
``A`` is declared to be an arithmetic array over rectangular domain
``D`` with elements of type ``real``. As a result, it represents a
2-dimensional :math:`10 \times 10` real floating point variables
indexed using the indices
:math:`(1, 1), (1, 2), \ldots, (1, 10), (2, 1), \ldots, (10, 10)`.
An array’s element type can be referred to using the member symbol
``eltType``.
*Example (eltType.chpl)*.
In the following example, ``x`` is declared to be of type ``real``
since that is the element type of array ``A``.
::
const D: domain(2) = {1..10, 1..10};
::
var A: [D] real;
var x: A.eltType;
::
writeln(x.type:string);
writeln(A.eltType:string);
::
real(64)
real(64)
.. _Array_Values:
Array Values
------------
An array’s value is the collection of its elements’ values. Assignments
between array variables are performed by value as described
in §\ `22.5 <#Array_Assignment>`__. Chapel semantics are defined so that
the compiler will never need to insert temporary arrays of the same size
as a user array variable.
Array literal values can be either rectangular or associative,
corresponding to the underlying domain which defines its indices.
::
array-literal:
rectangular-array-literal
associative-array-literal
.. _Rectangular_Array_Literals:
Rectangular Array Literals
~~~~~~~~~~~~~~~~~~~~~~~~~~
Rectangular array literals are specified by enclosing a comma separated
list of expressions representing values in square brackets. A 1-based
domain will automatically be generated for the given array literal. The
type of the array’s values will be the type of the first element listed.
A trailing comma is allowed.
::
rectangular-array-literal:
[ expression-list ]
[ expression-list , ]
..
*Example (adecl-literal.chpl)*.
The following example declares a 5 element rectangular array literal
containing strings, then subsequently prints each string element to
the console.
::
var A = ["1", "2", "3", "4", "5"];
for i in 1..5 do
writeln(A[i]);
::
1
2
3
4
5
*Future*.
Provide syntax which allows users to specify the domain for a
rectangular array literal.
..
*Future*.
Determine the type of a rectangular array literal based on the most
promoted type, rather than the first element’s type.
*Example (decl-with-anon-domain.chpl)*.
The following example declares a 2-element array ``A`` containing
3-element arrays of real numbers. ``A`` is initialized using array
literals.
::
var A: [1..2] [1..3] real = [[1.1, 1.2, 1.3], [2.1, 2.2, 2.3]];
::
writeln(A.domain);
::
{1..2}
..
*Open issue*.
We would like to differentiate syntactically between array literals
for an array of arrays and a multi-dimensional array.
An rectangular array’s default value is for each array element to be
initialized to the default value of the element type.
.. _Associative_Array_Literals:
Associative Array Literals
~~~~~~~~~~~~~~~~~~~~~~~~~~
Associative array values are specified by enclosing a comma separated
list of index-to-value bindings within square brackets. It is expected
that the indices in the listing match in type and, likewise, the types
of values in the listing also match. A trailing comma is allowed.
::
associative-array-literal:
[ associative-expr-list ]
[ associative-expr-list , ]
associative-expr-list:
index-expr => value-expr
index-expr => value-expr, associative-expr-list
index-expr:
expression
value-expr:
expression
..
*Open issue*.
Currently it is not possible to use other associative domains as
values within an associative array literal.
*Example (adecl-assocLiteral.chpl)*.
The following example declares a 5 element associative array literal
which maps integers to their corresponding string representation. The
indices and their corresponding values are then printed.
::
var A = [1 => "one", 10 => "ten", 3 => "three", 16 => "sixteen"];
for da in zip (A.domain, A) do
writeln(da);
::
\#!/usr/bin/env sh
testname=$1
outfile=$2
sort $outfile > $outfile.2
mv $outfile.2 $outfile
::
(1, one)
(10, ten)
(16, sixteen)
(3, three)
.. _Array_Runtime_Representation:
Runtime Representation of Array Values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The runtime representation of an array in memory is controlled by its
domain’s domain map. Through this mechanism, users can reason about and
control the runtime representation of an array’s elements. See
 §`[Domain_Maps] <#Domain_Maps>`__ for more details.
.. _Array_Indexing:
Array Indexing
--------------
Arrays can be indexed using index values from the domain over which they
are declared. Array indexing is expressed using either parentheses or
square brackets. This results in a reference to the element that
corresponds to the index value.
*Example (array-indexing.chpl)*.
Given:
::
var A: [1..10] real;
the first two elements of A can be assigned the value 1.2 and 3.4
respectively using the assignment:
::
A(1) = 1.2;
A[2] = 3.4;
::
writeln(A.domain);
writeln(A);
::
{1..10}
1.2 3.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Except for associative arrays, if an array is indexed using an index
that is not part of its domain’s index set, the reference is considered
out-of-bounds and a runtime error will occur, halting the program.
.. _Rectangular_Array_Indexing:
Rectangular Array Indexing
~~~~~~~~~~~~~~~~~~~~~~~~~~
Since the indices for multidimensional rectangular domains are tuples,
for convenience, rectangular arrays can be indexed using the list of
integer values that make up the tuple index. This is semantically
equivalent to creating a tuple value out of the integer values and using
that tuple value to index the array. For symmetry, 1-dimensional
rectangular arrays can be accessed using 1-tuple indices even though
their index type is an integral value. This is semantically equivalent
to de-tupling the integral value from the 1-tuple and using it to index
the array.
*Example (array-indexing-2.chpl)*.
Given:
::
var A: [1..5, 1..5] real;
var ij: 2*int = (1, 1);
the elements of array A can be indexed using any of the following
idioms:
::
A(ij) = 1.1;
A((1, 2)) = 1.2;
A(1, 3) = 1.3;
A[ij] = -1.1;
A[(1, 4)] = 1.4;
A[1, 5] = 1.5;
::
writeln(ij);
writeln(A);
::
(1, 1)
-1.1 1.2 1.3 1.4 1.5
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
..
*Example (index-using-var-arg-tuple.chpl)*.
The code
::
proc f(A: [], is...)
return A(is);
::
var B: [1..5] int;
[i in 1..5] B(i) = i;
var C: [1..5,1..5] int;
[(i,j) in {1..5,1..5}] C(i,j) = i+i*j;
writeln(f(B, 3));
writeln(f(C, 3, 3));
::
3
12
defines a function that takes an array as the first argument and a
variable-length argument list. It then indexes into the array using
the tuple that captures the actual arguments. This function works
even for one-dimensional arrays because one-dimensional arrays can be
indexed into by 1-tuples.
.. _Associative_Array_Indexing:
Associative Array Indexing
~~~~~~~~~~~~~~~~~~~~~~~~~~
Indices can be added to associative arrays through the array’s domain.
*Example (assoc-add-index.chpl)*.
Given:
::
var D : domain(string);
var A : [D] int;
the array A initially contains no elements. We can change that by
adding indices to the domain D:
::
D.add("a");
D.add("b");
The array A can now be indexed with indices "a" and "b":
::
A["a"] = 1;
A["b"] = 2;
var x = A["a"];
.. _Iteration_over_Arrays:
Iteration over Arrays
---------------------
All arrays support iteration via standard ``for``, ``forall`` and
``coforall`` loops. These loops iterate over all of the array elements
as described by its domain. A loop of the form:
::
[for|forall|coforall] a in A do
...a...
is semantically equivalent to:
::
[for|forall|coforall] i in A.domain do
...A[i]...
The iterator variable for an array iteration is a reference to the array
element type.
.. _Array_Assignment:
Array Assignment
----------------
Array assignment is by value. Arrays can be assigned arrays, ranges,
domains, iterators, or tuples as long as the two expressions are
compatible in terms of number of dimensions and shape.
*Example (assign.chpl)*.
If ``A`` is an array variable and ``B`` is an expression of array,
range, domain, or tuple type, or an iterator, then the assignment
::
var A: [1..3] int;
var B: [1..3] int;
A = -1;
B = 1;
::
writeln(A);
writeln(B);
::
A = B;
::
writeln(A);
writeln(B);
A = -2;
B = 2;
writeln(A);
writeln(B);
is equivalent to
::
[(a,b) in zip(A,B)] a = b;
::
writeln(A);
writeln(B);
::
-1 -1 -1
1 1 1
1 1 1
1 1 1
-2 -2 -2
2 2 2
2 2 2
2 2 2
If the zipper iteration is illegal, then the assignment is illegal.
This means, for example, that a range cannot be assigned to a
multidimensional rectangular array because the two expressions don’t
match in shape and can’t be zipped together. Notice that the
assignment is implemented using parallelism when possible, and
serially otherwise.
Arrays can be assigned tuples of values of their element type if the
tuple contains the same number of elements as the array. For
multidimensional arrays, the tuple must be a nested tuple such that the
nesting depth is equal to the rank of the array and the shape of this
nested tuple must match the shape of the array. The values are assigned
element-wise.
Arrays can also be assigned single values of their element type. In this
case, each element in the array is assigned this value.
*Example (assign-2.chpl)*.
If ``e`` is an expression of the element type of the array or a type
that can be implicitly converted to the element type of the array,
then the assignment
::
var A: [1..4] uint;
writeln(A);
var e: uint = 77;
::
A = e;
::
writeln(A);
e = 33;
is equivalent to
::
forall a in A do
a = e;
::
writeln(A);
::
0 0 0 0
77 77 77 77
33 33 33 33
.. _Array_Slicing:
Array Slicing
-------------
An array can be sliced using a domain that has the same type as the
domain over which it was declared. The result of an array slice is an
alias to the subset of the array elements from the original array
corresponding to the slicing domain’s index set.
*Example (slicing.chpl)*.
Given the definitions
::
config const n = 2;
::
var OuterD: domain(2) = {0..n+1, 0..n+1};
var InnerD: domain(2) = {1..n, 1..n};
var A, B: [OuterD] real;
::
writeln(OuterD);
writeln(InnerD);
B = 1;
the assignment given by
::
A[InnerD] = B[InnerD];
::
writeln(A);
writeln(B);
::
{0..3, 0..3}
{1..2, 1..2}
0.0 0.0 0.0 0.0
0.0 1.0 1.0 0.0
0.0 1.0 1.0 0.0
0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0
assigns the elements in the interior of ``B`` to the elements in the
interior of ``A``.
.. _Rectangular_Array_Slicing:
Rectangular Array Slicing
~~~~~~~~~~~~~~~~~~~~~~~~~
A rectangular array can be sliced by any rectangular domain that is a
subdomain of the array’s defining domain. If the subdomain relationship
is not met, an out-of-bounds error will occur. The result is a subarray
whose indices are those of the slicing domain and whose elements are an
alias of the original array’s.
Rectangular arrays also support slicing by ranges directly. If each
dimension is indexed by a range, this is equivalent to slicing the array
by the rectangular domain defined by those ranges. These range-based
slices may also be expressed using partially unbounded or completely
unbounded ranges. This is equivalent to slicing the array’s defining
domain by the specified ranges to create a subdomain as described
in §\ `22.6 <#Array_Slicing>`__ and then using that subdomain to slice
the array.
.. _Rectangular_Array_Slicing_With_Rank_Change:
Rectangular Array Slicing with a Rank Change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For multidimensional rectangular arrays, slicing with a rank change is
supported by substituting integral values within a dimension’s range for
an actual range. The resulting array will have a rank less than the
rectangular array’s rank and equal to the number of ranges that are
passed in to take the slice.
*Example (array-decl.chpl)*.
Given an array
::
config const n = 4;
::
var A: [1..n, 1..n] int;
::
writeln(A);
::
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
the slice ``A[1..n, 1]`` is a one-dimensional array whose elements
are the first column of ``A``.
.. _Count_Operator_Arrays:
Count Operator
--------------
The ``\#`` operator can be applied to dense rectangular arrays with a
tuple argument whose size matches the rank of the array (or optionally
an integer in the case of a 1D array). The operator is equivalent to
applying the ``\#`` operator to the array’s domain and using the result
to slice the array as described in
Section \ `22.6.1 <#Rectangular_Array_Slicing>`__.
.. _Array_Arguments_To_Functions:
Array Arguments to Functions
----------------------------
By default, arrays are passed to function by ``ref`` or ``const ref``
depending on whether or not the formal argument is modified. The ``in``,
``inout``, and ``out`` intent can create copies of arrays.
When a formal argument has array type, the element type of the array can
be omitted and/or the domain of the array can be queried or omitted. In
such cases, the argument is generic and is discussed
in §\ `24.1.7 <#Formal_Arguments_of_Generic_Array_Types>`__.
If a formal array argument specifies a domain as part of its type
signature, the domain of the actual argument must represent the same
index set. If the formal array’s domain was declared using an explicit
domain map, the actual array’s domain must use an equivalent domain map.
.. _Array_Promotion_of_Scalar_Functions:
Array Promotion of Scalar Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Arrays may be passed to a scalar function argument whose type matches
the array’s element type. This results in a promotion of the scalar
function as defined in §\ `27.5 <#Promotion>`__.
*Example (whole-array-ops.chpl)*.
Whole array operations is a special case of array promotion of scalar
functions. In the code
::
var A, B, C: [1..3] real;
A = -1;
B = 2;
C = 3;
::
A = B + C;
::
writeln(A);
::
5.0 5.0 5.0
if ``A``, ``B``, and ``C`` are arrays, this code assigns each element
in ``A`` the element-wise sum of the elements in ``B`` and ``C``.
.. _Returning_Arrays_from_Functions:
Returning Arrays from Functions
-------------------------------
Arrays return by value by default. The ``ref`` and ``const ref`` return
intents can be used to return a reference to an array.
Similarly to array arguments, the element type and/or domain of an array
return type can be omitted.
.. _Sparse_Arrays:
Sparse Arrays
-------------
Sparse arrays in Chapel are those whose domain is sparse. A sparse array
differs from other array types in that it stores a single value
corresponding to multiple indices. This value is commonly referred to as
the *zero value*, but we refer to it as the *implicitly replicated
value* or *IRV* since it can take on any value of the array’s element
type in practice including non-zero numeric values, a class reference, a
record or tuple value, etc.
An array declared over a sparse domain can be indexed using any of the
indices in the sparse domain’s parent domain. If it is read using an
index that is not part of the sparse domain’s index set, the IRV value
is returned. Otherwise, the array element corresponding to the index is
returned.
Sparse arrays can only be written at locations corresponding to indices
in their domain’s index set. In general, writing to other locations
corresponding to the IRV value will result in a runtime error.
By default a sparse array’s IRV is defined as the default value for the
array’s element type. The IRV can be set to any value of the array’s
element type by assigning to a pseudo-field named ``IRV`` in the array.
*Example (sparse-error.chpl)*.
The following code example declares a sparse array, ``SpsA`` using
the sparse domain ``SpsD`` (For this example, assume that
``n``\ :math:`>`\ 1). Line 2 assigns two indices to ``SpsD``\ ’s
index set and then lines 3–4 store the values 1.1 and 9.9 to the
corresponding values of ``SpsA``. The IRV of ``SpsA`` will initially
be 0.0 since its element type is ``real``. However, the fifth line
sets the IRV to be the value 5.5, causing ``SpsA`` to represent the
value 1.1 in its low corner, 9.9 in its high corner, and 5.5
everywhere else. The final statement is an error since it attempts to
assign to ``SpsA`` at an index not described by its domain, ``SpsD``.
::
config const n = 5;
const D = {1..n, 1..n};
::
var SpsD: sparse subdomain(D);
var SpsA: [SpsD] real;
SpsD = ((1,1), (n,n));
SpsA(1,1) = 1.1;
SpsA(n,n) = 9.9;
SpsA.IRV = 5.5;
SpsA(1,n) = 0.0; // ERROR!
::
sparse-error.chpl:9: error: halt reached - attempting to assign a 'zero' value in a sparse array: (1, 5)
.. _Association_of_Arrays_to_Domains:
Association of Arrays to Domains
--------------------------------
When an array is declared, it is linked during execution to the domain
identity over which it was declared. This linkage is invariant for the
array’s lifetime and cannot be changed.
When indices are added or removed from a domain, the change impacts the
arrays declared over this particular domain. In the case of adding an
index, an element is added to the array and initialized to the IRV for
sparse arrays, and to the default value for the element type for dense
arrays. In the case of removing an index, the element in the array is
removed.
When a domain is reassigned a new value, its arrays are also impacted.
Values that correspond to indices in the intersection of the old and new
domain are preserved in the arrays. Values that could only be indexed by
the old domain are lost. Values that can only be indexed by the new
domain have elements added to the new array, initialized to the IRV for
sparse arrays, and to the element type’s default value for other array
types.
For performance reasons, there is an expectation that a method will be
added to domains to allow non-preserving assignment, *i.e.*, all values
in the arrays associated with the assigned domain will be lost. Today
this can be achieved by assigning the array’s domain an empty index set
(causing all array elements to be deallocated) and then re-assigning the
new index set to the domain.
An array’s domain can only be modified directly, via the domain’s name
or an alias created by passing it to a function via default intent. In
particular, the domain may not be modified via the array’s ``.domain``
method, nor by using the domain query syntax on a function’s formal
array
argument (§\ `24.1.7 <#Formal_Arguments_of_Generic_Array_Types>`__).
*Rationale*.
When multiple arrays are declared using a single domain, modifying
the domain affects all of the arrays. Allowing an array’s domain to
be queried and then modified suggests that the change should only
affect that array. By requiring the domain to be modified directly,
the user is encouraged to think in terms of the domain distinctly
from a particular array.
In addition, this choice has the beneficial effect that arrays
declared via an anonymous domain have a constant domain. Constant
domains are considered a common case and have potential compilation
benefits such as eliminating bounds checks. Therefore making this
convenient syntax support a common, optimizable case seems prudent.
.. _Predefined_Functions_and_Methods_on_Arrays:
Predefined Functions and Methods on Arrays
------------------------------------------
There is an expectation that this list of predefined methods will grow.
::
proc Array.eltType type
Returns the element type of the array.
::
proc Array.rank param
Returns the rank of the array.
::
proc Array.domain: this.domain
Returns the domain of the given array. This domain is constant, implying
that the domain cannot be resized by assigning to its domain field, only
by modifying the domain directly.
::
proc Array.numElements: this.domain.dim_type
Returns the number of elements in the array.
::
proc reshape(A: Array, D: Domain): Array
Returns a copy of the array containing the same values but in the shape
of the new domain. The number of indices in the domain must equal the
number of elements in the array. The elements of the array are copied
into the new array using the default iteration orders over both arrays.
::
proc Array.size: this.domain.dim_type
Same as :math:`Array`.numElements.
Iterators
=========
[Iterators]
An *iterator* is a function that can generate, or *yield*, multiple
values (consecutively or in parallel) via its yield statements.
*Open issue*.
The parallel iterator story is under development. It is expected that
the specification will be expanded regarding parallel iterators soon.
.. _Iterator_Function_Definitions:
Iterator Definitions
--------------------
The syntax to declare an iterator is given by:
::
iterator-declaration-statement:
privacy-specifier[OPT] `iter' iterator-name argument-list[OPT] yield-intent[OPT] yield-type[OPT] where-clause[OPT]
iterator-body
iterator-name:
identifier
yield-intent:
`const'
`const ref'
`ref'
`param'
`type'
yield-type:
: type-expression
iterator-body:
block-statement
yield-statement
The syntax of an iterator declaration is similar to a procedure
declaration, with some key differences:
- The keyword ``iter`` is used instead of the keyword ``proc``.
- The name of the iterator cannot overload any operator.
- ``yield`` statements may appear in the body of an iterator, but not
in a procedure.
- A ``return`` statement in the body of an iterator is not allowed to
have an expression.
- The intent and type specified after the argument list refer to the
type yielded, not the type returned (see previous bullet). However,
they are syntactically the same as a ``return-intent`` and a
``return-type``.
..
*Open issue*.
Iterators that yield types or params are not currently supported.
.. _The_Yield_Statement:
The Yield Statement
-------------------
The yield statement can only appear in iterators. The syntax of the
yield statement is given by
::
yield-statement:
`yield' expression ;
When an iterator is executed and a ``yield`` is encountered, the value
of the yield expression is returned to the iterator’s callsite. However,
the state of execution of the iterator is logically saved such that its
execution continues from the point immediately following the ``yield``
statement. A yield statement in an iterator that yields references must
yield an lvalue expression.
When a ``return`` is encountered, the iterator finishes without yielding
another index value. The ``return`` statements appearing in an iterator
are not permitted to have a return expression. An iterator also
completes after the last statement in the iterator is executed. An
iterator need not contain any yield statements.
.. _Iterator_Calls:
Iterator Calls
--------------
Iterators are invoked using regular call expressions:
::
iteratable-call-expression:
call-expression
All details of iterator calls, including argument passing, function
resolution, the use of parentheses versus brackets to delimit the
parameter list, and so on, are identical to procedure calls as described
in Chapter \ `[Functions] <#Functions>`__.
However, the result of an iterator call depends upon its context, as
described below.
.. _Iterators_in_For_and_Forall_Loops:
Iterators in For and Forall Loops
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When an iterator is accessed via a for or forall loop, the iterator is
evaluated alongside the loop body in an interleaved manner. For each
iteration, the iterator yields a value and the body is executed.
.. _Iterators_as_Arrays:
Iterators as Arrays
~~~~~~~~~~~~~~~~~~~
If an iterator function is captured into a new variable declaration or
assigned to an array, the iterator is iterated over in total and the
expression evaluates to a one-dimensional arithmetic array that contains
the values returned by the iterator on each iteration.
*Example (as-arrays.chpl)*.
Given this iterator
::
iter squares(n: int): int {
for i in 1..n do
yield i * i;
}
var A = squares(5);
::
writeln(A);
then the variable A will be an array storing:
::
1 4 9 16 25
.. _Iterators_and_Generics:
Iterators and Generics
~~~~~~~~~~~~~~~~~~~~~~
An iterator call expression can be passed to a generic function argument
that has neither a declared type nor default value
(§`24.1.3 <#Formal_Arguments_without_Types>`__). In this case the
iterator is passed without being evaluated. Within the generic function
the corresponding formal argument can be used as an iterator, e.g. in
for loops. The arguments to the iterator call expression, if any, are
evaluated at the call site, i.e. prior to passing the iterator to the
generic function.
.. _Recursive_Iterators:
Recursive Iterators
~~~~~~~~~~~~~~~~~~~
Recursive iterators are allowed. A recursive iterator invocation is
typically made by iterating over it in a loop.
*Example (recursive.chpl)*.
A post-order traversal of a tree data structure could be written like
this:
::
class Tree {
var data: string;
var left, right: unmanaged Tree?;
proc deinit() {
if left then delete left;
if right then delete right;
}
}
var tree = new unmanaged Tree("a", new unmanaged Tree("b"), new unmanaged Tree("c", new unmanaged Tree("d"), new unmanaged Tree("e")));
::
iter postorder(tree: Tree?): string {
if tree != nil {
for child in postorder(tree!.left) do
yield child;
for child in postorder(tree!.right) do
yield child;
yield tree.data;
}
}
::
proc Tree.writeThis(x)
{
var first = true;
for node in postorder(this) {
if first then first = false;
else x.write(" ");
write(node);
}
}
writeln("Tree Data");
writeln(tree);
delete tree;
By contrast, using calls ``postorder(tree.left)`` and
``postorder(tree.right)`` as stand-alone statements would result in
generating temporary arrays containing the outcomes of these
recursive calls, which would then be discarded.
::
Tree Data
b d e c a
.. _Iterator_Promotion_of_Scalar_Functions:
Iterator Promotion of Scalar Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Iterator calls may be passed to a scalar function argument whose type
matches the iterator’s yielded type. This results in a promotion of the
scalar function as described in §\ `27.5 <#Promotion>`__.
*Example (iteratorPromotion.chpl)*.
Given a function ``addOne(x:int)`` that accepts ``int`` values and an
iterator ``firstN()`` that yields ``int`` values, ``addOne()`` can be
called with ``firstN()`` as its actual argument. This pattern creates
a new iterator that yields the result of applying ``addOne()`` to
each value yielded by ``firstN()``.
::
proc addOne(x:int) {
return x + 1;
}
iter firstN(n:int) {
for i in 1..n {
yield i;
}
}
for number in addOne(firstN(10)) {
writeln(number);
}
::
2
3
4
5
6
7
8
9
10
11
.. _Parallel_Iterators:
Parallel Iterators
------------------
Iterators used in explicit forall-statements or -expressions must be
parallel iterators. Reductions, scans and promotion over serial
iterators will be serialized.
Parallel iterators are defined for standard constructs in Chapel such as
ranges, domains, and arrays, thereby allowing these constructs to be
used with forall-statements and -expressions.
The left-most iteratable expression in a forall-statement or -expression
determines the number of tasks, the iterations each task executes, and
the locales on which these tasks execute. For ranges, default domains,
and default arrays, these values can be controlled via configuration
constants (§\ `27.7 <#data_parallel_knobs>`__).
Domains and arrays defined using distributed domain maps will typically
implement forall loops with multiple tasks on multiple locales. For
ranges, default domains, and default arrays, all tasks are executed on
the current locale.
A more detailed definition of parallel iterators is forthcoming.
Generics
========
[Generics]
Chapel supports generic functions and types that are parameterizable
over both types and parameters. The generic functions and types look
similar to non-generic functions and types already discussed.
.. _Generic_Functions:
Generic Functions
-----------------
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.
These conditions are discussed in the next sections.
.. _Formal_Type_Arguments:
Formal Type Arguments
~~~~~~~~~~~~~~~~~~~~~
If a formal argument is specified with intent ``type``, then a type must
be passed to the function at the call site. A copy of the function is
instantiated for each unique type that is passed to this function at a
call site. The formal argument has the semantics of a type alias.
*Example (build2tuple.chpl)*.
The following code defines a function that takes two types at the
call site and returns a 2-tuple where the types of the components of
the tuple are defined by the two type arguments and the values are
specified by the types default values.
proc build2Tuple(type t, type tt) var x1: t; var x2: tt; return (x1,
x2);
This function is instantiated with “normal” function call syntax
where the arguments are types:
::
var t2 = build2Tuple(int, string);
t2 = (1, "hello");
::
writeln(t2);
::
(1, hello)
A formal type argument can include a formal type (a colon followed by a
type). This pattern is sometimes useful to create generic functions
accepting type arguments that only apply to a specific group of types.
*Example (typeColonArgument.chpl)*.
Suppose that we’d like to define a function that accepts a type
argument and returns 1 represented in that type.
::
proc getOne(type t:numeric) {
return 1:t;
}
Now calls to this function will resolve to the appropriate version
based upon the argument type supplied.
::
var anInt8 = getOne(int(8));
var aReal = getOne(real);
::
writeln(anInt8.type:string, " ", anInt8);
writeln(aReal.type:string, " ", aReal);
::
int(8) 1
real(64) 1.0
.. _Formal_Parameter_Arguments:
Formal Parameter Arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~
If a formal argument is specified with intent ``param``, then a
parameter must be passed to the function at the call site. A copy of the
function is instantiated for each unique parameter that is passed to
this function at a call site. The formal argument is a parameter.
*Example (fillTuple.chpl)*.
The following code defines a function that takes an integer parameter
``p`` at the call site as well as a regular actual argument of
integer type ``x``. The function returns a homogeneous tuple of size
``p`` where each component in the tuple has the value of ``x``.
::
proc fillTuple(param p: int, x: int) {
var result: p*int;
for param i in 1..p do
result(i) = x;
return result;
}
::
writeln(fillTuple(3,3));
::
(3, 3, 3)
The function call ``fillTuple(3, 3)`` returns a 3-tuple where each
component contains the value ``3``.
.. _Formal_Arguments_without_Types:
Formal Arguments without Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the type of a formal argument is omitted, the type of the formal
argument is taken to be the type of the actual argument passed to the
function at the call site. A copy of the function is instantiated for
each unique actual type.
*Example (fillTuple2.chpl)*.
The example from the previous section can be extended to be generic
on a parameter as well as the actual argument that is passed to it by
omitting the type of the formal argument ``x``. Additionally the
parameter argument can allow any type be passed. The following code
defines a function that returns a homogeneous tuple of size ``p``
where each component in the tuple is initialized to ``x``:
::
proc fillTuple(param p, x) {
var result: p*x.type;
for param i in 1..p do
result(i) = x;
return result;
}
::
var x = fillTuple(3, 3.14);
writeln(x);
writeln(x.type:string);
::
(3.14, 3.14, 3.14)
3*real(64)
In this function, the type of the tuple is taken to be the type of
the actual argument. The call ``fillTuple(3, 3.14)`` returns a
3-tuple of real values ``(3.14, 3.14, 3.14)``. The return type is
``(real, real, real)``.
.. _Formal_Arguments_with_Queried_Types:
Formal Arguments with Queried Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the type of a formal argument is specified as a queried type, the
type of the formal argument is taken to be the type of the actual
argument passed to the function at the call site. A copy of the function
is instantiated for each unique actual type. The queried type has the
semantics of a type alias.
*Example (fillTuple3.chpl)*.
The example from the previous section can be rewritten to use a
queried type for clarity:
::
proc fillTuple(param p: int, x: ?t) {
var result: p*t;
for param i in 1..p do
result(i) = x;
return result;
}
::
var x = fillTuple(3, 3.14);
writeln(x);
writeln(x.type:string);
::
(3.14, 3.14, 3.14)
3*real(64)
..
*Example (query.chpl)*.
Type queries can also be used to constrain the types of other
function arguments and/or the return type. In this example, the type
query on the first argument establishes type constraints on the other
arguments and also determines the return type.
The code
::
writeln(sumOfThree(1,2,3));
writeln(sumOfThree(4.0,5.0,3.0));
proc sumOfThree(x: ?t, y:t, z:t):t {
var sum: t;
sum = x + y + z;
return sum;
}
produces the output
::
6
12.0
.. _Formal_Arguments_of_Generic_Type:
Formal Arguments of Generic Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the type of a formal argument is a generic type, there must exist an
instantiation of that type that the actual argument can be implicitly
coerced to (§`9.1 <#Implicit_Conversions>`__). A copy of the function is
instantiated for each unique instantiation of the formal’s type.
*Example*.
The following code defines a function ``writeTop`` that takes an
actual argument that is a generic stack
(see §`24.6 <#Example_Generic_Stack>`__) and outputs the top element
of the stack. The function is generic on the type of its argument.
::
proc writeTop(s: Stack) {
write(s.top.item);
}
Types and parameters may be queried from the types of formal arguments
as well. In the example above, the formal argument’s type could also be
specified as ``Stack(?t)`` in which case the symbol ``t`` is equivalent
to ``s.itemType``.
Note that generic types which have default values for all of their
generic fields, *e.g. range*, are not generic when simply specified and
require a query to mark the argument as generic. For simplicity, the
identifier may be omitted.
*Example*.
The following code defines a class with a type field that has a
default value. Function ``f`` is defined to take an argument of this
class type where the type field is instantiated to the default.
Function ``g``, on the other hand, is generic on its argument because
of the use of the question mark.
::
class C {
type t = int;
}
proc f(c: C) {
// c.type is always int
}
proc g(c: C(?)) {
// c.type may not be int
}
.. _Formal_Arguments_of_Partially_Generic_Type:
Formal Arguments of Partially Generic Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The generic type for a formal argument may be specified with some
queries or generic types and some concrete types or values. Using
concrete types or values in this manner makes the argument *partially
concrete* for the purpose of function resolution.
*Example (nested-type-queries.chpl)*.
Given the code:
::
class C {
type elementType;
type indexType;
type containerType;
}
class Container {
type containedType;
}
proc f(c: C(real,?t,?u)) {
// ...
}
The function ``f`` can only apply when the ``c.elementType==real``.
It’s also possible to use a generic type as an argument to ``C``. The
following function, ``g``, can only apply when ``c.containerType`` is
an instance of ``Container``:
::
proc g(c: C(?t,?u,Container)) {
// ...
}
::
var cc = new borrowed Container(int);
var c = new borrowed C(real, int, cc.type);
f(c);
g(c);
Similarly, a tuple type with query arguments forms a *partially
concrete* argument.
*Example*.
The function definition
::
proc f(tuple: (?t,real)) {
// body
}
specifies that ``tuple.size == 2 \&\& tuple(2).type == real``.
Homogeneous tuple arguments of generic type are also supported:
*Example (partially-concrete-star-tuple.chpl)*.
::
record Number {
var n;
}
proc f(tuple: 2*Number) {
}
::
f( (new Number(1), new Number(2)) );
specifies that ``f`` accepts a tuple with 2 elements, where each
element has the same type, and that type is instantiation of
``Number``.
Note that specifying a tuple consisting entirely of queried types does
create a *partially concrete argument* because the size of the tuple is
constrained.
*Example (partially-concrete-tuple-ambiguity.chpl)*.
The following program results in an ambiguity error:
::
proc f(tuple: (?,real)) {
}
proc f(tuple: (?,?)) {
}
f( (1.0, 2.0) );
since the ``tuple`` arguments in both versions of ``f`` are
*partially concrete*.
::
\#!/usr/bin/env sh
\# This prediff exists to avoid underscores in the output
\# which confuse tex
testname=$1
outfile=$2
head -n 1 $outfile > $outfile.2
mv $outfile.2 $outfile
::
partially-concrete-tuple-ambiguity.chpl:5: error: ambiguous call 'f(2*real(64))'
.. _Formal_Arguments_of_Generic_Array_Types:
Formal Arguments of Generic Array Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the type of a formal argument is an array where either the domain or
the element type is queried or omitted, the type of the formal argument
is taken to be the type of the actual argument passed to the function at
the call site. If the domain is omitted, the domain of the formal
argument is taken to be the domain of the actual argument.
A queried domain may not be modified via the name to which it is bound
(see §`22.11 <#Association_of_Arrays_to_Domains>`__ for rationale).
.. _Function_Visibility_in_Generic_Functions:
Function Visibility in Generic Functions
----------------------------------------
When resolving function calls made within generic functions, there is an
additional source of visible functions. Besides functions visible to the
generic function’s point of declaration, visible functions are also
taken from one of the call sites at which the generic function is
instantiated for each particular instantiation. The specific call site
chosen is arbitrary and it is referred to as the *point of
instantiation*.
*Example (point-of-instantiation.chpl)*.
Consider the following code which defines a generic function ``bar``:
::
module M1 {
record R {
var x: int;
proc foo() { }
}
}
module M2 {
proc bar(x) {
x.foo();
}
}
module M3 {
use M1, M2;
proc main() {
var r: R;
bar(r);
}
}
In the function ``main``, the variable ``r`` is declared to be of
type ``R`` defined in module ``M1`` and a call is made to the generic
function ``bar`` which is defined in module ``M2``. This is the only
place where ``bar`` is called in this program and so it becomes the
point of instantiation for ``bar`` when the argument ``x`` is of type
``R``. Therefore, the call to the ``foo`` method in ``bar`` is
resolved by looking for visible functions from within ``main`` and
going through the use of module ``M1``.
If the generic function is only called indirectly through dynamic
dispatch, the point of instantiation is defined as the point at which
the derived type (the type of the implicit ``this`` argument) is defined
or instantiated (if the derived type is generic).
*Rationale*.
Visible function lookup in Chapel’s generic functions is handled
differently than in C++’s template functions in that there is no
split between dependent and independent types.
Also, dynamic dispatch and instantiation is handled differently.
Chapel supports dynamic dispatch over methods that are generic in
some of its formal arguments.
Note that the Chapel lookup mechanism is still under development and
discussion. Comments or questions are appreciated.
.. _Generic_Types:
Generic Types
-------------
Generic types comprise built-in generic types, generic classes, and
generic records.
.. _Built_in_Generic_types:
Built-in Generic Types
~~~~~~~~~~~~~~~~~~~~~~
The types ``integral``, ``numeric`` and ``enum`` are generic types that
can only be instantiated with, respectively, the signed and unsigned
integral types, all of the numeric types, and all enumerated types. The
type ``enumerated`` is currently available as a synonym for ``enum``.
The type ``record`` can be instantiated with any record type.
The memory management strategies ``owned``, ``shared``, ``borrowed``,
and ``unmanaged`` (see §`17.1.2 <#Class_Types>`__) are also generic
types that can be instantiated with any class using that memory
management strategy. These types indicate generic nilability.
The types ``class`` and ``class?``, on their own or in combination with
memory management strategies, are also generic types. They can be
instantiated as follows:
- ``class`` can instantiate with any non-nilable class using any memory
management strategy
- ``class?`` can instantiate with any class using any memory management
strategy but will use the nilable variant of that class in an
instantiation. When used as an argument type, a value of non-nilable
class type will be implicitly converted to the nilable type on the
call. As a result, a formal of type ``class?`` can accept an actual
of any class type.
- ``owned`` can instantiate with any ``owned`` class - taking the
nilability from whatever it instantiated from.
- ``owned class`` can instantiate with any non-nilable ``owned`` class.
- ``owned class?`` can instantiate from any nilable ``owned`` class. As
with ``class?``, it can also instantiate from a non-nilable ``owned``
class, in which case a implicit conversion would occur in a call.
- ``shared``, ``shared class``, ``shared class?`` behave similarly to
the above but with ``shared`` management strategy.
- ``borrowed``, ``borrowed class``, ``borrowed class?`` behave
similarly to the above but with ``borrowed`` management strategy.
- ``unmanaged``, ``unmanaged class``, ``unmanaged class?`` behave
similarly to the above but with ``unmanaged`` management strategy.
Generic Classes and Records
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The remainder of this section §\ `24.3 <#Generic_Types>`__ specifies
generic class and record types that are not built-in types
(§`24.3.1 <#Built_in_Generic_types>`__).
A class or record is generic if it contains one or more generic fields.
A generic field is one of:
- a specified or unspecified type alias,
- a parameter field, or
- a ``var`` or ``const`` field that has no type and no initialization
expression.
For each generic field, the class or record is parameterized over:
- the type bound to the type alias,
- the value of the parameter field, or
- the type of the ``var`` or ``const`` field, respectively.
Correspondingly, the class or record is instantiated with a set of types
and parameter values, one type or value per generic field.
.. _Type_Aliases_in_Generic_Types:
Type Aliases in Generic Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a class or record defines a type alias, the class or record is
generic over the type that is bound to that alias. Such a type alias is
accessed as if it were a field from either a class or record instance or
from the instantiated class or record type itself. Similar to a
parameter field, it cannot be assigned except in its declaration.
The type alias becomes an argument with intent ``type`` to the
compiler-generated initializer
(§`24.3.8 <#Generic_Compiler_Generated_Initializers>`__) for its class
or record. This makes the compiler-generated initializer generic. The
type alias also becomes an argument with intent ``type`` to the type
constructor (§`24.3.6 <#Type_Constructors>`__). If the type alias
declaration binds it to a type, that type becomes the default for these
arguments, otherwise they have no defaults.
The class or record is instantiated by binding the type alias to the
actual type passed to the corresponding argument of a user-defined
(§`24.3.9 <#Generic_User_Initializers>`__) or compiler-generated
initializer or type constructor. If that argument has a default, the
actual type can be omitted, in which case the default will be used
instead.
*Example (NodeClass.chpl)*.
The following code defines a class called ``Node`` that implements a
linked list data structure. It is generic over the type of the
element contained in the linked list.
::
class Node {
type eltType;
var data: eltType;
var next: unmanaged Node(eltType)?;
}
::
var n: unmanaged Node(real) = new unmanaged Node(real, 3.14);
writeln(n.data);
writeln(n.next);
writeln(n.next.type:string);
delete n;
::
3.14
nil
unmanaged Node(real(64))?
The call ``new Node(real, 3.14)`` creates a node in the linked list
that contains the value ``3.14``. The ``next`` field is set to nil.
The type specifier ``Node`` is a generic type and cannot be used to
define a variable. The type specifier ``Node(real)`` denotes the type
of the ``Node`` class instantiated over ``real``. Note that the type
of the ``next`` field is specified as ``Node(eltType)``; the type of
``next`` is the same type as the type of the object that it is a
field of.
.. _Parameters_in_Generic_Types:
Parameters in Generic Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a class or record defines a parameter field, the class or record is
generic over the value that is bound to that field. The field can be
accessed from a class or record instance or from the instantiated class
or record type itself.
The parameter becomes an argument with intent ``param`` to the
compiler-generated initializer
(§`24.3.8 <#Generic_Compiler_Generated_Initializers>`__) for that class
or record. This makes the compiler-generated initializer generic. The
parameter also becomes an argument with intent ``param`` to the type
constructor (§`24.3.6 <#Type_Constructors>`__). If the parameter
declaration has an initialization expression, that expression becomes
the default for these arguments, otherwise they have no defaults.
The class or record is instantiated by binding the parameter to the
actual value passed to the corresponding argument of a user-defined
(§`24.3.9 <#Generic_User_Initializers>`__) or compiler-generated
initializer or type constructor. If that argument has a default, the
actual value can be omitted, in which case the default will be used
instead.
*Example (IntegerTuple.chpl)*.
The following code defines a class called ``IntegerTuple`` that is
generic over an integer parameter which defines the number of
components in the class.
::
class IntegerTuple {
param size: int;
var data: size*int;
}
::
var x = new unmanaged IntegerTuple(3);
writeln(x.data);
delete x;
::
(0, 0, 0)
The call ``new IntegerTuple(3)`` creates an instance of the
``IntegerTuple`` class that is instantiated over parameter ``3``. The
field ``data`` becomes a 3-tuple of integers. The type of this class
instance is ``IntegerTuple(3)``. The type specified by
``IntegerTuple`` is a generic type.
.. _Fields_without_Types:
Fields without Types
~~~~~~~~~~~~~~~~~~~~
If a ``var`` or ``const`` field in a class or record has no specified
type or initialization expression, the class or record is generic over
the type of that field. The field becomes an argument with default
intent to the compiler-generated initializer
(§`24.3.8 <#Generic_Compiler_Generated_Initializers>`__). That argument
has no specified type and no default value. This makes the
compiler-generated initializer generic. The field also becomes an
argument with ``type`` intent and no default to the type constructor
(§`24.3.6 <#Type_Constructors>`__). Correspondingly, an actual value
must always be passed to the default initializer argument and an actual
type to the type constructor argument.
The class or record is instantiated by binding the type of the field to
the type of the value passed to the corresponding argument of a
user-defined (§`24.3.9 <#Generic_User_Initializers>`__) or
compiler-generated initializer
(§`24.3.8 <#Generic_Compiler_Generated_Initializers>`__). When the type
constructor is invoked, the class or record is instantiated by binding
the type of the field to the actual type passed to the corresponding
argument.
*Example (fieldWithoutType.chpl)*.
The following code defines another class called ``Node`` that
implements a linked list data structure. It is generic over the type
of the element contained in the linked list. This code does not
specify the element type directly in the class as a type alias but
rather omits the type from the ``data`` field.
::
class Node {
var data;
var next: unmanaged Node(data.type)? = nil;
}
A node with integer element type can be defined in the call to the
initializer. The call ``new Node(1)`` defines a node with the value
``1``. The code
::
var list = new unmanaged Node(1);
list.next = new unmanaged Node(2);
::
writeln(list.data);
writeln(list.next.data);
delete list.next;
delete list;
::
1
2
defines a two-element list with nodes containing the values ``1`` and
``2``. The type of each object could be specified as ``Node(int)``.
.. _Type_Constructors:
The Type Constructor
~~~~~~~~~~~~~~~~~~~~
A type constructor is automatically created for each class or record. A
type constructor is a type function (§`13.7.5 <#Type_Return_Intent>`__)
that has the same name as the class or record. It takes one argument per
the class’s or record’s generic field, including fields inherited from
the superclasses, if any. The formal argument has intent ``type`` for a
type alias field and is a parameter for a parameter field. It accepts
the type to be bound to the type alias and the value to be bound to the
parameter, respectively. For a generic ``var`` or ``const`` field, the
corresponding formal argument also has intent ``type``. It accepts the
type of the field, as opposed to a value as is the case for a parameter
field. The formal arguments occur in the same order as the fields are
declared and have the same names as the corresponding fields. Unlike the
compiler-generated initializer, the type constructor has only those
arguments that correspond to generic fields.
A call to a type constructor accepts actual types and parameter values
and returns the type of the class or record that is instantiated
appropriately for each field
(§`24.3.3 <#Type_Aliases_in_Generic_Types>`__,
§\ `24.3.4 <#Parameters_in_Generic_Types>`__,
§\ `24.3.5 <#Fields_without_Types>`__). Such an instantiated type must
be used as the type of a variable, array element, non-generic formal
argument, and in other cases where uninstantiated generic class or
record types are not allowed.
When a generic field declaration has an initialization expression or a
type alias is specified, that initializer becomes the default value for
the corresponding type constructor argument. Uninitialized fields,
including all generic ``var`` and ``const`` fields, and unspecified type
aliases result in arguments with no defaults; actual types or values for
these arguments must always be provided when invoking the type
constructor.
.. _Generic_Methods:
Generic Methods
~~~~~~~~~~~~~~~
All methods bound to generic classes or records, including initializers,
are generic over the implicit ``this`` argument. This is in addition to
being generic over any other argument that is generic.
.. _Generic_Compiler_Generated_Initializers:
The Compiler-Generated Initializer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If no user-defined initializers are supplied for a given generic class,
the compiler generates one following in a manner similar to that for
concrete classes (§`17.4.2 <#The_Compiler_Generated_Initializer>`__).
However, the compiler-generated initializer for a generic class or
record (§`17.4.2 <#The_Compiler_Generated_Initializer>`__) is generic
over each argument that corresponds to a generic field, as specified
above.
The argument has intent ``type`` for a type alias field and has intent
``param`` for a parameter field. It accepts the type to be bound to the
type alias and the value to be bound to the parameter, respectively.
This is the same as for the type constructor. For a generic ``var`` or
``const`` field, the corresponding formal argument has the default
intent and accepts the value for the field to be initialized with. The
type of the field is inferred automatically to be the type of the
initialization value.
The default values for the generic arguments of the compiler-generated
initializer are the same as for the type constructor
(§`24.3.6 <#Type_Constructors>`__). For example, the arguments
corresponding to the generic ``var`` and ``const`` fields, if any, never
have defaults, so the corresponding actual values must always be
provided.
.. _Generic_User_Initializers:
User-Defined Initializers
~~~~~~~~~~~~~~~~~~~~~~~~~
If a generic field of a class or record does not have a default value or
type alias, each user-defined initializer for that class must explicitly
initialize that field.
*Example (initializersForGenericFields.chpl)*.
In the following code:
::
class MyGenericClass {
type t1;
param p1;
const c1;
var v1;
var x1: t1; // this field is not generic
type t5 = real;
param p5 = "a string";
const c5 = 5.5;
var v5 = 555;
var x5: t5; // this field is not generic
proc init(c1, v1, type t1, param p1) {
this.t1 = t1;
this.p1 = p1;
this.c1 = c1;
this.v1 = v1;
// compiler inserts initialization for remaining fields
}
proc init(type t5, param p5, c5, v5, x5,
type t1, param p1, c1, v1, x1) {
this.t1 = t1;
this.p1 = p1;
this.c1 = c1;
this.v1 = v1;
this.x1 = x1;
this.t5 = t5;
this.p5 = p5;
this.c5 = c5;
this.v5 = v5;
this.x5 = x5;
}
} // class MyGenericClass
var g1 = new MyGenericClass(11, 111, int, 1);
var g2 = new MyGenericClass(int, "this is g2", 3.3, 333, 3333,
real, 2, 222, 222.2, 22);
::
writeln(g1.p1);
writeln(g1.p5);
writeln(g1);
writeln(g2.p1);
writeln(g2.p5);
writeln(g2);
::
--no-warnings
::
1
a string
{c1 = 11, v1 = 111, x1 = 0, c5 = 5.5, v5 = 555, x5 = 0.0}
2
this is g2
{c1 = 222, v1 = 222.2, x1 = 22.0, c5 = 3.3, v5 = 333, x5 = 3333}
The initializers are required to initialize fields ``t1``, ``p1``,
``c1``, and ``v1``. Otherwise, field initializations may be omitted
according to previously-described initializer semantics.
.. _User_Defined_Compiler_Errors:
User-Defined Compiler Diagnostics
---------------------------------
The special compiler diagnostic function calls ``compilerError`` and
``compilerWarning`` generate compiler diagnostic of the indicated
severity if the function containing these calls may be called when the
program is executed and the function call is not eliminated by parameter
folding.
The compiler diagnostic is defined by the actual arguments which must be
string parameters. The diagnostic points to the spot in the Chapel
program from which the function containing the call is called.
Compilation halts if a ``compilerError`` is encountered whereas it will
continue after encountering a ``compilerWarning``.
*Example (compilerDiagnostics.chpl)*.
The following code shows an example of using user-defined compiler
diagnostics to generate warnings and errors:
::
proc foo(x, y) {
if (x.type != y.type) then
compilerError("foo() called with non-matching types: ",
x.type:string, " != ", y.type:string);
writeln("In 2-argument foo...");
}
proc foo(x) {
compilerWarning("1-argument version of foo called");
writeln("In generic foo!");
}
::
foo(3.4);
foo("hi");
foo(1, 2);
foo(1.2, 3.4);
foo("hi", "bye");
::
compilerDiagnostics.chpl:12: warning: 1-argument version of foo called
compilerDiagnostics.chpl:13: warning: 1-argument version of foo called
In generic foo!
In generic foo!
In 2-argument foo...
In 2-argument foo...
In 2-argument foo...
The first routine generates a compiler error whenever the compiler
encounters a call to it where the two arguments have different types.
It prints out an error message indicating the types of the arguments.
The second routine generates a compiler warning whenever the compiler
encounters a call to it.
Thus, if the program foo.chpl contained the following calls:
::
foo(3.4);
foo("hi");
foo(1, 2);
foo(1.2, 3.4);
foo("hi", "bye");
foo(1, 2.3);
foo("hi", 2.3);
compiling the program would generate output like:
::
foo.chpl:1: warning: 1-argument version of foo called with type: real
foo.chpl:2: warning: 1-argument version of foo called with type: string
foo.chpl:6: error: foo() called with non-matching types: int != real
.. _Creating_General_and_Specialized_Versions_of_a_Function:
Creating General and Specialized Versions of a Function
-------------------------------------------------------
The Chapel language facility supports three mechanisms for using generic
functions along with concrete functions. These mechanisms allow users to
create a general generic implementation and also a special
implementation for specific concrete types.
The first mechanism applies to functions. According to the function
resolution rules described in §\ `13.13 <#Function_Resolution>`__,
functions accepting concrete arguments are selected in preference to
those with a totally generic argument. So, creating a second version of
a generic function that declares a concrete type will cause the concrete
function to be used where possible:
*Example (specializeGenericFunction.chpl)*.
::
proc foo(x) {
writeln("in generic foo(x)");
}
proc foo(x:int) {
writeln("in specific foo(x:int)");
}
var myReal:real;
foo(myReal); // outputs "in generic foo(x)"
var myInt:int;
foo(myInt); // outputs "in specific foo(x:int)"
::
in generic foo(x)
in specific foo(x:int)
This program will run the generic foo function if the argument is a
real, but it runs the specific version for int if the argument is an
int.
The second mechanism applies when working with methods on generic types.
When declaring a secondary method, the receiver type can be a
parenthesized expression. In that case, the compiler will evaluate the
parenthesized expression at compile time in order to find the concrete
receiver type. Then, the resolution rules described above will cause the
concrete method to be selected when applicable. For example:
*Example (specializeGenericMethod.chpl)*.
::
record MyNode {
var field; // since no type is specified here, MyNode is a generic type
}
proc MyNode.foo() {
writeln("in generic MyNode.foo()");
}
proc (MyNode(int)).foo() {
writeln("in specific MyNode(int).foo()");
}
var myRealNode = new MyNode(1.0);
myRealNode.foo(); // outputs "in generic MyNode.foo()"
var myIntNode = new MyNode(1);
myIntNode.foo(); // outputs "in specific MyNode(int).foo()"
::
in generic MyNode.foo()
in specific MyNode(int).foo()
The third mechanism is to use a where clause. Where clauses limit a
generic method to particular cases. Unlike the previous two cases, a
where clause can be used to declare special implementation of a function
that works with some set of types - in other words, the special
implementation can still be a generic function. See also
§\ `13.10 <#Where_Clauses>`__.
.. _Example_Generic_Stack:
Example: A Generic Stack
------------------------
*Example (genericStack.chpl)*.
::
class MyNode {
type itemType; // type of item
var item: itemType; // item in node
var next: unmanaged MyNode(itemType)?; // reference to next node (same type)
}
record Stack {
type itemType; // type of items
var top: unmanaged MyNode(itemType)?; // top node on stack linked list
proc push(item: itemType) {
top = new unmanaged MyNode(itemType, item, top);
}
proc pop() {
if isEmpty then
halt("attempt to pop an item off an empty stack");
var oldTop = top;
var oldItem = top.item;
top = top.next;
delete oldTop;
return oldItem;
}
proc isEmpty return top == nil;
}
::
var s: Stack(int);
s.push(1);
s.push(2);
s.push(3);
while !s.isEmpty do
writeln(s.pop());
::
3
2
1
Input and Output
================
[Input_and_Output]
See Library Documentation
-------------------------
| Chapel includes an extensive library for input and output that is
documented in the standard library documentation. See
| https://chapel-lang.org/docs/modules/standard/IO.html
| and
| https://chapel-lang.org/docs/builtins/ChapelIO.html.
Task Parallelism and Synchronization
====================================
[Task_Parallelism_and_Synchronization]
Chapel supports both task parallelism and data parallelism. This chapter
details task parallelism as follows:
- §\ `26.1 <#Task_parallelism>`__ introduces tasks and task
parallelism.
- §\ `26.2 <#Begin>`__ describes the begin statement, an unstructured
way to introduce concurrency into a program.
- §\ `26.3 <#Synchronization_Variables>`__ describes synchronization
variables, an unstructured mechanism for synchronizing tasks.
- §\ `26.4 <#Atomic_Variables>`__ describes atomic variables, a
mechanism for supporting atomic operations.
- §\ `26.5 <#Cobegin>`__ describes the cobegin statement, a structured
way to introduce concurrency into a program.
- §\ `26.6 <#Coforall>`__ describes the coforall loop, another
structured way to introduce concurrency into a program.
- §\ `26.7 <#Task_Intents>`__ specifies how variables from outer scopes
are handled within ``begin``, ``cobegin`` and ``coforall``
statements.
- §\ `26.8 <#Sync_Statement>`__ describes the sync statement, a
structured way to control parallelism.
- §\ `26.9 <#Serial>`__ describes the serial statement, a structured
way to suppress parallelism.
- §\ `26.10 <#Atomic_Statement>`__ describes the atomic statement, a
construct to support atomic transactions.
.. _Task_parallelism:
Tasks and Task Parallelism
--------------------------
A Chapel *task* is a distinct context of execution that may be running
concurrently with other tasks. Chapel provides a simple construct, the
``begin`` statement, to create tasks, introducing concurrency into a
program in an unstructured way. In addition, Chapel introduces two type
qualifiers, ``sync`` and ``single``, for synchronization between tasks.
Chapel provides two constructs, the ``cobegin`` and ``coforall``
statements, to introduce concurrency in a more structured way. These
constructs create multiple tasks but do not continue until these tasks
have completed. In addition, Chapel provides two constructs, the
``sync`` and ``serial`` statements, to insert synchronization and
suppress parallelism. All four of these constructs can be implemented
through judicious uses of the unstructured task-parallel constructs
described in the previous paragraph.
Tasks are considered to be created when execution reaches the start of a
``begin``, ``cobegin``, or ``coforall`` statement. When the tasks are
actually executed depends on the Chapel implementation and run-time
execution state.
A task is represented as a call to a *task function*, whose body
contains the Chapel code for the task. Variables defined in outer scopes
are considered to be passed into a task function by default intent,
unless a different *task intent* is specified explicitly by a
``task-intent-clause``.
Accesses to the same variable from different tasks are subject to the
Memory Consistency Model
(§`[Memory_Consistency_Model] <#Memory_Consistency_Model>`__). Such
accesses can result from aliasing due to ``ref`` argument intents or
task intents, among others.
.. _Begin:
The Begin Statement
-------------------
The begin statement creates a task to execute a statement. The syntax
for the begin statement is given by
::
begin-statement:
`begin' task-intent-clause[OPT] statement
Control continues concurrently with the statement following the begin
statement.
*Example (beginUnordered.chpl)*.
The code
::
begin writeln("output from spawned task");
writeln("output from main task");
::
\#!/usr/bin/env sh
testname=$1
outfile=$2
sort $outfile > $outfile.2
mv $outfile.2 $outfile
::
output from main task
output from spawned task
executes two ``writeln`` statements that output the strings to the
terminal, but the ordering is purposely unspecified. There is no
guarantee as to which statement will execute first. When the begin
statement is executed, a new task is created that will execute the
``writeln`` statement within it. However, execution will continue
immediately after task creation with the next statement.
A begin statement creates a single task function, whose body is the body
of the begin statement. The handling of the outer variables within the
task function and the role of ``task-intent-clause`` are defined in
§\ `26.7 <#Task_Intents>`__.
Yield and return statements are not allowed in begin blocks. Break and
continue statements may not be used to exit a begin block.
.. _Synchronization_Variables:
Synchronization Variables
-------------------------
Synchronization variables have a logical state associated with the
value. The state of the variable is either *full* or *empty*. Normal
reads of a synchronization variable cannot proceed until the variable’s
state is full. Normal writes of a synchronization variable cannot
proceed until the variable’s state is empty.
Chapel supports two types of synchronization variables: sync and single.
Both types behave similarly, except that a single variable may only be
written once. Consequently, when a sync variable is read, its state
transitions to empty, whereas when a single variable is read, its state
does not change. When either type of synchronization variable is
written, its state transitions to full.
``sync`` and ``single`` are type qualifiers and precede the type of the
variable’s value in the declaration. Sync and single are supported for
all Chapel primitive types ( §`7.1 <#Primitive_Types>`__) except
complex. They are also supported for enumerated types
( §`7.2 <#Enumerated_Types>`__) and variables of class type
( §`17.1.2 <#Class_Types>`__). For sync variables of class type, the
full/empty state applies to the reference to the class object, not to
its member fields.
*Rationale*.
It is only well-formed to apply full-empty semantics to types that
have no more than a single logical value. Booleans, integers, real
and imaginary numbers, enums, and class references all meet this
criteria. Since it is possible to read/write the individual elements
of a complex value, it’s not obvious how the full-empty semantics
would interact with such operations. While one could argue that
record types with a single field could also be included, the user can
more directly express such cases by declaring the field itself to be
of sync type.
If a task attempts to read or write a synchronization variable that is
not in the correct state, the task is suspended. When the variable
transitions to the correct state, the task is resumed. If there are
multiple tasks blocked waiting for the state transition, one is
non-deterministically selected to proceed and the others continue to
wait if it is a sync variable; all tasks are selected to proceed if it
is a single variable.
A synchronization variable is specified with a sync or single type given
by the following syntax:
::
sync-type:
`sync' type-expression
single-type:
`single' type-expression
If a synchronization variable declaration has an initialization
expression, then the variable is initially full, otherwise it is
initially empty.
*Example (beginWithSyncVar.chpl)*.
The code
::
class Tree {
var isLeaf: bool;
var left, right: unmanaged Tree?;
var value: int;
proc sum():int {
if (isLeaf) then
return value;
var x(*\texttt{\$}*): sync int;
begin x(*\texttt{\$}*) = left.sum();
var y = right.sum();
return x(*\texttt{\$}*) + y;
}
}
::
var tree: unmanaged Tree = new unmanaged Tree(false, new unmanaged Tree(false, new unmanaged Tree(true, nil, nil, 1),
new unmanaged Tree(true, nil, nil, 1), 1),
new unmanaged Tree(false, new unmanaged Tree(true, nil, nil, 1),
new unmanaged Tree(true, nil, nil, 1), 1), 1);
writeln(tree.sum());
proc Tree.deinit() {
if isLeaf then return;
delete left;
delete right;
}
delete tree;
::
4
the sync variable ``x$\mbox{\texttt{\$}}$`` is assigned by an
asynchronous task created with the begin statement. The task
returning the sum waits on the reading of ``x$\mbox{\texttt{\$}}$``
until it has been assigned. By convention, synchronization variables
end in ``$`` to provide a visual cue to the programmer indicating
that the task may block.
..
*Example (syncCounter.chpl)*.
Sync variables are useful for tallying data from multiple tasks as
well. If all updates to an initialized sync variable are via compound
assignment operators (or equivalently, traditional assignments that
read and write the variable once), the full/empty state of the sync
variable guarantees that the reads and writes will be interleaved in
a manner that makes the updates atomic. For example, the code:
::
var count(*\texttt{\$}*): sync int = 0;
cobegin {
count(*\texttt{\$}*) += 1;
count(*\texttt{\$}*) += 1;
count(*\texttt{\$}*) += 1;
}
::
writeln("count is: ", count(*\texttt{\$}*).readFF());
::
count is: 3
creates three tasks that increment ``count$\mbox{\texttt{\$}}$``. If
``count$\mbox{\texttt{\$}}$`` were not a sync variable, this code
would be unsafe because two tasks could then read the same value
before either had written its updated value, causing one of the
increments to be lost.
*Example (singleVar.chpl)*.
The following code implements a simple split-phase barrier using a
single variable.
::
config const n = 44;
proc work(i) {
// do nothing
}
::
var count(*\texttt{\$}*): sync int = n; // counter which also serves as a lock
var release(*\texttt{\$}*): single bool; // barrier release
forall t in 1..n do begin {
work(t);
var myc = count(*\texttt{\$}*); // read the count, set state to empty
if myc!=1 {
write(".");
count(*\texttt{\$}*) = myc-1; // update the count, set state to full
// we could also do some work here before blocking
release(*\texttt{\$}*);
} else {
release(*\texttt{\$}*) = true; // last one here, release everyone
writeln("done");
}
}
::
...........................................done
In each iteration of the forall loop after the work is completed, the
task reads the ``count$\mbox{\texttt{\$}}$`` variable, which is used
to tally the number of tasks that have arrived. All tasks except the
last task to arrive will block while trying to read the variable
``release$\mbox{\texttt{\$}}$``. The last task to arrive will write
to ``release$\mbox{\texttt{\$}}$``, setting its state to full at
which time all the other tasks can be unblocked and run.
If a formal argument with a default intent either has a synchronization
type or the formal is generic
(§`24.1.5 <#Formal_Arguments_of_Generic_Type>`__) and the actual has a
synchronization type, the actual must be an lvalue and is passed by
reference. In these cases the formal itself is an lvalue, too. The
actual argument is not read or written during argument passing; its
state is not changed or waited on. The qualifier ``sync`` or ``single``
without the value type can be used to specify a generic formal argument
that requires a ``sync`` or ``single`` actual.
When the actual argument is a ``sync`` or ``single`` and the
corresponding formal has the actual’s base type or is implicitly
converted from that type, a normal read of the actual is performed when
the call is made, and the read value is passed to the formal.
.. _Functions_on_Synchronization_Variables:
Predefined Single and Sync Methods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following methods are defined for variables of sync and single type.
::
proc (sync t).readFE(): t
Returns the value of the sync variable. This method blocks until the
sync variable is full. The state of the sync variable is set to empty
when this method completes. This method implements the normal read of a
``sync`` variable.
::
proc (sync t).readFF(): t
proc (single t).readFF(): t
Returns the value of the sync or single variable. This method blocks
until the sync or single variable is full. The state of the sync or
single variable remains full when this method completes. This method
implements the normal read of a ``single`` variable.
::
proc (sync t).readXX(): t
proc (single t).readXX(): t
Returns the value of the sync or single variable. This method is
non-blocking and the state of the sync or single variable is unchanged
when this method completes.
::
proc (sync t).writeEF(v: t)
proc (single t).writeEF(v: t)
Assigns ``v`` to the value of the sync or single variable. This method
blocks until the sync or single variable is empty. The state of the sync
or single variable is set to full when this method completes. This
method implements the normal write of a ``sync`` or ``single`` variable.
::
proc (sync t).writeFF(v: t)
Assigns ``v`` to the value of the sync variable. This method blocks
until the sync variable is full. The state of the sync variable remains
full when this method completes.
::
proc (sync t).writeXF(v: t)
Assigns ``v`` to the value of the sync variable. This method is
non-blocking and the state of the sync variable is set to full when this
method completes.
::
proc (sync t).reset()
Assigns the default value of type ``t`` to the value of the sync
variable. This method is non-blocking and the state of the sync variable
is set to empty when this method completes.
::
proc (sync t).isFull: bool
proc (single t).isFull: bool
Returns ``true`` if the sync or single variable is full and ``false``
otherwise. This method is non-blocking and the state of the sync or
single variable is unchanged when this method completes.
Note that ``writeEF`` and ``readFE``/``readFF`` methods (for ``sync``
and ``single`` variables, respectively) are implicitly invoked for
normal writes and reads of synchronization variables.
*Example (syncMethods.chpl)*.
Given the following declarations
::
{ // }
::
var x(*\texttt{\$}*): sync int;
var y(*\texttt{\$}*): single int;
var z: int;
the code
::
x(*\texttt{\$}*) = 5;
y(*\texttt{\$}*) = 6;
z = x(*\texttt{\$}*) + y(*\texttt{\$}*);
::
writeln((x(*\texttt{\$}*).readXX(), y(*\texttt{\$}*).readFF(), z));
// {
}
{ // }
var x(*\texttt{\$}*): sync int;
var y(*\texttt{\$}*): single int;
var z: int;
is equivalent to
::
x(*\texttt{\$}*).writeEF(5);
y(*\texttt{\$}*).writeEF(6);
z = x(*\texttt{\$}*).readFE() + y(*\texttt{\$}*).readFF();
::
writeln((x(*\texttt{\$}*).readXX(), y(*\texttt{\$}*).readFF(), z));
// {
}
::
(5, 6, 11)
(5, 6, 11)
.. _Atomic_Variables:
Atomic Variables
----------------
Atomic variables are variables that support atomic operations. Chapel
currently supports atomic operations for bools, all supported sizes of
signed and unsigned integers, as well as all supported sizes of reals.
*Rationale*.
The choice of supported atomic variable types as well as the atomic
operations was strongly influenced by the C11 standard.
Atomic is a type qualifier that precedes the variable’s type in the
declaration. Atomic operations are supported for bools, and all sizes of
ints, uints, and reals.
An atomic variable is specified with an atomic type given by the
following syntax:
::
atomic-type:
`atomic' type-expression
.. _Functions_on_Atomic_Variables:
Predefined Atomic Methods
~~~~~~~~~~~~~~~~~~~~~~~~~
The following methods are defined for variables of atomic type. Note
that not all operations are supported for all atomic types. The
supported types are listed for each method.
Most of the predefined atomic methods accept an optional argument named
``order`` of type memoryOrder. The ``order`` argument is used to specify
the ordering constraints of atomic operations. The supported memoryOrder
values are:
- memoryOrder.relaxed
- memoryOrder.acquire
- memoryOrder.release
- memoryOrder.acqRel
- memoryOrder.seqCst
Unless specified, the default for the memoryOrder parameter is
memoryOrder.seqCst.
*Implementors’ note*.
Not all architectures or implementations may support all memoryOrder
values. In these cases, the implementation should default to a more
conservative ordering than specified.
::
proc (atomic T).read(order:memoryOrder = memoryOrder.seqCst): T
Reads and returns the stored value. Defined for all atomic types.
::
proc (atomic T).write(v: T, order:memoryOrder = memoryOrder.seqCst)
Stores ``v`` as the new value. Defined for all atomic types.
::
proc (atomic T).exchange(v: T, order:memoryOrder = memoryOrder.seqCst): T
Stores ``v`` as the new value and returns the original value. Defined
for all atomic types.
::
proc (atomic T).compareAndSwap(e: t, v: T, order:memoryOrder = memoryOrder.seqCst): bool
Stores ``v`` as the new value, if and only if the original value is
equal to ``e``. Returns ``true`` if ``v`` was stored, ``false``
otherwise. Defined for all atomic types.
::
proc (atomic T).add(v: T, order:memoryOrder = memoryOrder.seqCst)
proc (atomic T).sub(v: T, order:memoryOrder = memoryOrder.seqCst)
proc (atomic T).or(v: T, order:memoryOrder = memoryOrder.seqCst)
proc (atomic T).and(v: T, order:memoryOrder = memoryOrder.seqCst)
proc (atomic T).xor(v: T, order:memoryOrder = memoryOrder.seqCst)
Applies the appropriate operator (+@, -@, \|@, &@, ^@) to the original
value and ``v`` and stores the result. All of the methods are defined
for integral atomic types. Only add and sub (+@, -@) are defined for
``real`` atomic types. None of the methods are defined for the ``bool``
atomic type.
*Future*.
In the future we may overload certain operations such as +=@ to call
the above methods automatically for atomic variables.
::
proc (atomic T).fetchAdd(v: T, order:memoryOrder = memoryOrder.seqCst): T
proc (atomic T).fetchSub(v: T, order:memoryOrder = memoryOrder.seqCst): T
proc (atomic T).fetchOr(v: T, order:memoryOrder = memoryOrder.seqCst): T
proc (atomic T).fetchAnd(v: T, order:memoryOrder = memoryOrder.seqCst): T
proc (atomic T).fetchXor(v: T, order:memoryOrder = memoryOrder.seqCst): T
Applies the appropriate operator (+@, -@, \|@, &@, ^@) to the original
value and ``v``, stores the result, and returns the original value. All
of the methods are defined for integral atomic types. Only add and sub
(+@, -@) are defined for ``real`` atomic types. None of the methods are
defined for the ``bool`` atomic type.
::
proc (atomic bool).testAndSet(order:memoryOrder = memoryOrder.seqCst): bool
Stores ``true`` as the new value and returns the old value. Equivalent
to ``exchange(true)``. Only defined for the ``bool`` atomic type.
::
proc (atomic bool).clear(order:memoryOrder = memoryOrder.seqCst)
Stores ``false`` as the new value. Equivalent to ``write(false)``. Only
defined for the ``bool`` atomic type.
::
proc (atomic T).waitFor(v: T)
Waits until the stored value is equal to ``v``. The implementation may
yield the running task while waiting. Defined for all atomic types.
.. _Cobegin:
The Cobegin Statement
---------------------
The cobegin statement is used to introduce concurrency within a block.
The ``cobegin`` statement syntax is
::
cobegin-statement:
`cobegin' task-intent-clause[OPT] block-statement
A new task and a corresponding task function are created for each
statement in the ``block-statement``. Control continues when all of the
tasks have finished. The handling of the outer variables within each
task function and the role of ``task-intent-clause`` are defined in
§\ `26.7 <#Task_Intents>`__.
Return statements are not allowed in cobegin blocks. Yield statement may
only be lexically enclosed in cobegin blocks in parallel
iterators (§\ `23.4 <#Parallel_Iterators>`__). Break and continue
statements may not be used to exit a cobegin block.
*Example (cobeginAndEquivalent.chpl)*.
The cobegin statement
::
var s1, s2: sync int;
proc stmt1() { s1; }
proc stmt2() { s2; s1 = 1; }
proc stmt3() { s2 = 1; }
::
cobegin {
stmt1();
stmt2();
stmt3();
}
is equivalent to the following code that uses only begin statements
and single variables to introduce concurrency and synchronize:
::
var s1(*\texttt{\$}*), s2(*\texttt{\$}*), s3(*\texttt{\$}*): single bool;
begin { stmt1(); s1(*\texttt{\$}*) = true; }
begin { stmt2(); s2(*\texttt{\$}*) = true; }
begin { stmt3(); s3(*\texttt{\$}*) = true; }
s1(*\texttt{\$}*); s2(*\texttt{\$}*); s3(*\texttt{\$}*);
Each begin statement is executed concurrently but control does not
continue past the final line above until each of the single variables
is written, thereby ensuring that each of the functions has finished.
.. _Coforall:
The Coforall Loop
-----------------
The coforall loop is a variant of the cobegin statement in loop form.
The syntax for the coforall loop is given by
::
coforall-statement:
`coforall' index-var-declaration `in' iteratable-expression task-intent-clause[OPT] `do' statement
`coforall' index-var-declaration `in' iteratable-expression task-intent-clause[OPT] block-statement
`coforall' iteratable-expression task-intent-clause[OPT] `do' statement
`coforall' iteratable-expression task-intent-clause[OPT] block-statement
The ``coforall`` loop creates a separate task for each iteration of the
loop. Control continues with the statement following the ``coforall``
loop after all tasks corresponding to the iterations of the loop have
completed.
The single task function created for a ``coforall`` and invoked by each
task contains the loop body. The handling of the outer variables within
the task function and the role of ``task-intent-clause`` are defined in
§\ `26.7 <#Task_Intents>`__.
Return statements are not allowed in coforall blocks. Yield statement
may only be lexically enclosed in coforall blocks in parallel
iterators (§\ `23.4 <#Parallel_Iterators>`__). Break and continue
statements may not be used to exit a coforall block.
*Example (coforallAndEquivalent.chpl)*.
The coforall statement
::
iter iterator() { for i in 1..3 do yield i; }
proc body() { }
::
coforall i in iterator() {
body();
}
is equivalent to the following code that uses only begin statements
and sync and single variables to introduce concurrency and
synchronize:
::
var runningCount(*\texttt{\$}*): sync int = 1;
var finished(*\texttt{\$}*): single bool;
for i in iterator() {
runningCount(*\texttt{\$}*) += 1;
begin {
body();
var tmp = runningCount(*\texttt{\$}*);
runningCount(*\texttt{\$}*) = tmp-1;
if tmp == 1 then finished(*\texttt{\$}*) = true;
}
}
var tmp = runningCount(*\texttt{\$}*);
runningCount(*\texttt{\$}*) = tmp-1;
if tmp == 1 then finished(*\texttt{\$}*) = true;
finished(*\texttt{\$}*);
Each call to ``body()`` executes concurrently because it is in a
begin statement. The sync variable
``runningCount$\mbox{\texttt{\$}}$`` is used to keep track of the
number of executing tasks plus one for the main task. When this
variable reaches zero, the single variable
``finished$\mbox{\texttt{\$}}$`` is used to signal that all of the
tasks have completed. Thus control does not continue past the last
line until all of the tasks have completed.
.. _Task_Intents:
Task Intents
------------
If a variable is referenced within the lexical scope of a ``begin``,
``cobegin``, or ``coforall`` statement and is declared outside that
statement, it is subject to *task intents*. That is, it is considered to
be passed as an actual argument to the corresponding task function at
task creation time. All references to the variable within the task
function implicitly refer to a *shadow variable*, i.e. the task
function’s corresponding formal argument.
When the task construct is inside a method on a record and accesses a
field of ``this``, the field is treated as a regular variable. That is,
it is passed as an actual argument to the task function and all
references to the field within the task function implicitly refer to the
corresponding shadow variable.
Each formal argument of a task function has the default argument intent
by default. For variables of primitive and class types, this has the
effect of capturing the value of the variable at task creation time and
referencing that value instead of the original variable within the
lexical scope of the task construct.
A formal can be given another argument intent explicitly by listing it
with that intent in the optional ``task-intent-clause``. For example,
for variables of most types, the ``ref`` intent allows the task
construct to modify the corresponding original variable or to read its
updated value after concurrent modifications.
The syntax of the task intent clause is:
::
task-intent-clause:
`with' ( task-intent-list )
task-intent-list:
task-intent-item
task-intent-item, task-intent-list
task-intent-item:
formal-intent identifier
task-private-var-decl
| where the following intents can be used as a ``formal-intent``:
``ref``, ``in``, ``const``, ``const in``, ``const ref``.
``task-private-var-decl`` is defined in
§\ `27.4 <#Task_Private_Variables>`__. In addition,
``task-intent-item`` may define a ``reduce`` intent. Reduce intents
are described in the *Reduce Intents* technical note in the online
documentation:
| https://chapel-lang.org/docs/technotes/reduceIntents.html
The implicit treatment of outer scope variables as the task function’s
formal arguments applies to both module level and local variables. It
applies to variable references within the lexical scope of a task
construct, but does not extend to its dynamic scope, i.e., to the
functions called from the task(s) but declared outside of the lexical
scope. The loop index variables of a ``coforall`` statement are not
subject to such treatment within that statement; however, they are
subject to such treatment within nested task constructs, if any.
*Rationale*.
The primary motivation for task intents is to avoid some races on
scalar/record variables, which are possible when one task modifies a
variable and another task reads it. Without task intents, for
example, it would be easy to introduce and overlook a bug illustrated
by this simplified example:
::
{
var i = 0;
while i < 10 {
begin {
f(i);
}
i += 1;
}
}
If all the tasks created by the ``begin`` statement start executing
only after the ``while`` loop completes, and ``i`` within the
``begin`` is treated as a reference to the original ``i``, there will
be ten tasks executing ``f(10)``. However, the user most likely
intended to generate ten tasks executing ``f(0)``, ``f(1)``, ...,
``f(9)``. Task intents ensure that, regardless of the timing of task
execution.
Another motivation for task intents is that referring to a captured
copy in a task is often more efficient than referring to the original
variable. That’s because the copy is a local constant, e.g. it could
be placed in a register when it fits. Without task intents,
references to the original variable would need to be implemented
using a pointer dereference. This is less efficient and can hinder
optimizations in the surrounding code, for example loop-invariant
code motion.
Furthermore, in the above example the scope where ``i`` is declared
may exit before all the ten tasks complete. Without task intents, the
user would have to protect ``i`` to make sure its lexical scope
doesn’t exit before the tasks referencing it complete.
We decided to treat ``cobegin`` and ``coforall`` statements the same
way as ``begin``. This is for consistency and to make the
race-avoidance benefit available to more code.
We decided to apply task intents to module level variables, in
addition to local variables. Again, this is for consistency. One
could view module level variables differently than local variables
(e.g. a module level variable is “always available”), but we favored
consistency over such an approach.
We decided not to apply task intents to “closure” variables, i.e.,
the variables in the dynamic scope of a task construct. This is to
keep this feature manageable, so that all variables subject to task
intents can be obtained by examining just the lexical scope of the
task construct. In general, the set of closure variables can be hard
to determine, unwieldy to implement and reason about, it is unclear
what to do with extern functions, etc.
We do not provide ``inout`` or ``out`` as task intents because they
will necessarily create a data race in a ``cobegin`` or ``coforall``.
``type`` and ``param`` intents are not available either as they do
not seem useful as task intents.
..
*Future*.
For a given intent, we would also like to provide a blanket clause,
which would apply the intent to all variables. An example of syntax
for a blanket ``ref`` intent would be ``ref *``.
.. _Sync_Statement:
The Sync Statement
------------------
The sync statement acts as a join of all dynamically encountered begins
from within a statement. The syntax for the sync statement is given by
::
sync-statement:
`sync' statement
`sync' block-statement
Return statements are not allowed in sync statement blocks. Yield
statement may only be lexically enclosed in sync statement blocks in
parallel iterators (§\ `23.4 <#Parallel_Iterators>`__). Break and
continue statements may not be used to exit a sync statement block.
*Example (syncStmt1.chpl)*.
The sync statement can be used to wait for many dynamically created
tasks.
::
config const n = 9;
proc work() {
write(".");
}
::
sync for i in 1..n do begin work();
::
writeln("done");
::
.........done
The for loop is within a sync statement and thus the tasks created in
each iteration of the loop must complete before the continuing past
the sync statement.
..
*Example (syncStmt2.chpl)*.
The sync statement
::
proc stmt1() { }
proc stmt2() { }
::
sync {
begin stmt1();
begin stmt2();
}
is similar to the following cobegin statement
::
cobegin {
stmt1();
stmt2();
}
except that if begin statements are dynamically encountered when
``stmt1()`` or ``stmt2()`` are executed, then the former code will
wait for these begin statements to complete whereas the latter code
will not.
.. _Serial:
The Serial Statement
--------------------
The ``serial`` statement can be used to dynamically disable parallelism.
The syntax is:
::
serial-statement:
`serial' expression[OPT] `do' statement
`serial' expression[OPT] block-statement
where the optional ``expression`` evaluates to a boolean value. If the
expression is omitted, it is as though ’true’ were specified. Whatever
the expression’s value, the statement following it is evaluated. If the
expression is true, any dynamically encountered code that would normally
create new tasks within the statement is instead executed by the
original task without creating any new ones. In effect, execution is
serialized. If the expression is false, code within the statement will
generates task according to normal Chapel rules.
*Example (serialStmt1.chpl)*.
In the code
::
config const lo = 9;
config const hi = 23;
proc work(i) {
if \_\_primitive("task\_get\_serial") then
writeln("serial ", i);
}
::
proc f(i) {
serial i<13 {
cobegin {
work(i);
work(i);
}
}
}
for i in lo..hi {
f(i);
}
::
serial 9
serial 9
serial 10
serial 10
serial 11
serial 11
serial 12
serial 12
the serial statement in procedure f() inhibits concurrent execution
of work() if the variable i is less than 13.
..
*Example (serialStmt2.chpl)*.
The code
::
proc stmt1() { write(1); }
proc stmt2() { write(2); }
proc stmt3() { write(3); }
proc stmt4() { write(4); }
var n = 3;
::
serial {
begin stmt1();
cobegin {
stmt2();
stmt3();
}
coforall i in 1..n do stmt4();
}
is equivalent to
::
stmt1();
{
stmt2();
stmt3();
}
for i in 1..n do stmt4();
::
writeln();
::
123444123444
because the expression evaluated to determine whether to serialize
always evaluates to true.
.. _Atomic_Statement:
Atomic Statements
-----------------
*Open issue*.
This section describes a feature that is a work-in-progress. We seek
feedback and collaboration in this area from the broader community.
The *atomic statement* is used to specify that a statement should appear
to execute atomically from other tasks’ point of view. In particular, no
task will see memory in a state that would reflect that the atomic
statement had begun executing but had not yet completed.
*Open issue*.
This definition of the atomic statement provides a notion of *strong
atomicity* since the action will appear atomic to any task at any
point in its execution. For performance reasons, it could be more
practical to support *weak atomicity* in which the statement’s
atomicity is only guaranteed with respect to other atomic statements.
We may also pursue using atomic type qualifiers as a means of marking
data that should be accessed atomically inside or outside an atomic
section.
The syntax for the atomic statement is given by:
::
atomic-statement:
`atomic' statement
..
*Example*.
The following code illustrates the use of an atomic statement to
perform an insertion into a doubly-linked list:
::
class Node {
var data: int;
var next: Node;
var prev: Node;
}
var head = new Node(1);
head.insertAfter(new Node(4));
head.insertAfter(new Node(2));
var obj = new Node(3);
head.next.insertAfter(obj);
::
proc Node.insertAfter(newNode: Node) {
atomic {
newNode.prev = this;
newNode.next = this.next;
if this.next then this.next.prev = newNode;
this.next = newNode;
}
}
::
writeln(head.data, head.next.data, head.next.next.data, head.next.next.next.data);
proc Node.remove() {
if this.prev then this.prev = this.next;
if this.next then this.next = this.prev;
return this;
}
while (head) {
next = head.next;
delete head;
head = next;
}
::
atomic.chpl:13: warning: atomic keyword is ignored (not implemented)
1234
The use of the atomic statement in this routine prevents other tasks
from viewing the list in a partially-updated state in which the
pointers might not be self-consistent.
Data Parallelism
================
[Data_Parallelism]
Chapel provides two explicit data-parallel constructs (the
forall-statement and the forall-expression) and several idioms that
support data parallelism implicitly (whole-array assignment, function
and operator promotion, reductions, and scans).
This chapter details data parallelism as follows:
- §\ `27.1 <#Forall>`__ describes the forall statement.
- §\ `27.2 <#Forall_Expressions>`__ describes forall expressions
- §\ `27.3 <#Forall_Intents>`__ specifies how variables from outer
scopes are handled within forall statements and expressions.
- §\ `27.5 <#Promotion>`__ describes promotion.
- §\ `27.6 <#Reductions_and_Scans>`__ describes reductions and scans.
- §\ `27.7 <#data_parallel_knobs>`__ describes the configuration
constants for controlling default data parallelism.
Data-parallel constructs may result in accesses to the same variable
from different tasks, possibly due to aliasing using ``ref`` argument
intents or forall intents, among others. Such accesses are subject to
the Memory Consistency Model
(§`[Memory_Consistency_Model] <#Memory_Consistency_Model>`__).
.. _Forall:
The Forall Statement
--------------------
The forall statement is a concurrent variant of the for statement
described in §\ `11.9 <#The_For_Loop>`__.
.. _forall_syntax:
Syntax
~~~~~~
The syntax of the forall statement is given by
::
forall-statement:
`forall' index-var-declaration `in' iteratable-expression task-intent-clause[OPT] `do' statement
`forall' index-var-declaration `in' iteratable-expression task-intent-clause[OPT] block-statement
`forall' iteratable-expression task-intent-clause[OPT] `do' statement
`forall' iteratable-expression task-intent-clause[OPT] block-statement
[ index-var-declaration `in' iteratable-expression task-intent-clause[OPT] ] statement
[ iteratable-expression task-intent-clause[OPT] ] statement
As with the for statement, the indices may be omitted if they are
unnecessary and the ``do`` keyword may be omitted before a block
statement.
The square bracketed form will resort to serial iteration when
``iteratable-expression`` does not support parallel iteration. The
``forall`` form will result in an error when parallel iteration is not
available.
The handling of the outer variables within the forall statement and the
role of ``task-intent-clause`` are defined in
§\ `27.3 <#Forall_Intents>`__.
.. _forall_semantics:
Execution and Serializability
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The forall statement evaluates the loop body once for each element
yielded by the ``iteratable-expression``. Each instance of the forall
loop’s body may be executed concurrently with the others, but this is
not guaranteed. In particular, the loop must be serializable. Details
regarding concurrency and iterator implementation are described
in \ `23.4 <#Parallel_Iterators>`__.
This differs from the semantics of the ``coforall`` loop, discussed
in §\ `26.6 <#Coforall>`__, where each iteration is guaranteed to run
using a distinct task. The ``coforall`` loop thus has potentially higher
overhead than a forall loop with the same number of iterations, but in
cases where concurrency is required for correctness, it is essential.
In practice, the number of tasks that will be used to evaluate a
``forall`` loop is determined by the object or iterator that is
*leading* the execution of the loop, as is the mapping of iterations to
tasks.
| This concept will be formalized in future drafts of the Chapel
specification. For now, the primer on parallel iterators in the online
documentation provides a brief introduction:
| https://chapel-lang.org/docs/primers/parIters.html
| Please also refer to *User-Defined Parallel Zippered Iterators in
Chapel*, published in the PGAS 2011 workshop.
Control continues with the statement following the forall loop only
after every iteration has been completely evaluated. At this point, all
data accesses within the body of the forall loop will be guaranteed to
be completed.
A ``return`` statement may not be lexically enclosed in a forall
statement. A ``yield`` statement may only be lexically enclosed in a
forall statement that is within a parallel iterator
§\ `23.4 <#Parallel_Iterators>`__. A ``break`` statement may not be used
to exit a forall statement. A ``continue`` statement skips the rest of
the current iteration of the forall loop.
*Example (forallStmt.chpl)*.
In the code
::
config const N = 5;
var a: [1..N] int;
var b = [i in 1..N] i;
::
forall i in 1..N do
a(i) = b(i);
the user has stated that the element-wise assignments can execute
concurrently. This loop may be executed serially with a single task,
or by using a distinct task for every iteration, or by using a number
of tasks where each task executes a number of iterations. This loop
can also be written as
::
[i in 1..N] a(i) = b(i);
::
writeln(a);
::
1 2 3 4 5
.. _forall_zipper:
Zipper Iteration
~~~~~~~~~~~~~~~~
Zipper iteration has the same semantics as described
in §\ `11.9.1 <#Zipper_Iteration>`__
and §\ `23.4 <#Parallel_Iterators>`__ for parallel iteration.
.. _Forall_Expressions:
The Forall Expression
---------------------
The forall expression is a concurrent variant of the for expression
described in §\ `10.22 <#For_Expressions>`__.
.. _forall_expr_syntax:
Syntax
~~~~~~
The syntax of a forall expression is given by
::
forall-expression:
`forall' index-var-declaration `in' iteratable-expression task-intent-clause[OPT] `do' expression
`forall' iteratable-expression task-intent-clause[OPT] `do' expression
[ index-var-declaration `in' iteratable-expression task-intent-clause[OPT] ] expression
[ iteratable-expression task-intent-clause[OPT] ] expression
As with the for expression, the indices may be omitted if they are
unnecessary. The ``do`` keyword is always required in the keyword-based
notation.
As with the forall statement, the square bracketed form will resort to
serial iteration when ``iteratable-expression`` does not support
parallel iteration. The ``forall`` form will result in an error when
parallel iteration is not available.
The handling of the outer variables within the forall expression and the
role of ``task-intent-clause`` are defined in
§\ `27.3 <#Forall_Intents>`__.
.. _Forall_Expression_Execution:
Execution
~~~~~~~~~
A forall expression is an iterator that executes a forall loop
(§`27.1 <#Forall>`__), evaluates the body expression on each iteration
of the loop, and yields each resulting value.
When a forall expression is used to initialize a variable, such as
::
var X = forall iterableExpression() do computeValue();
the variable will be inferred to have an array type. The array’s domain
is defined by the ``iterable-expression`` following the same rules as
for promotion, both in the regular case §\ `27.5 <#Promotion>`__ and in
the zipper case §\ `27.5.2 <#Zipper_Promotion>`__.
*Example (forallExpr.chpl)*.
The code
::
writeln(+ reduce [i in 1..10] i**2);
::
385
applies a reduction to a forall-expression that evaluates the square
of the indices in the range ``1..10``.
The forall expression follows the semantics of the forall statement as
described in \ `27.1.2 <#forall_semantics>`__.
Zipper Iteration
~~~~~~~~~~~~~~~~
Forall expression also support zippered iteration semantics as described
in §\ `11.9.1 <#Zipper_Iteration>`__
and §\ `23.4 <#Parallel_Iterators>`__ for parallel iteration.
.. _Filtering_Predicates_Forall:
Filtering Predicates in Forall Expressions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A filtering predicate is an if expression that is immediately enclosed
by a forall expression and does not have an else clause. Such an if
expression filters the iterations of the forall expression. The
iterations for which the condition does not hold are not reflected in
the result of the forall expression.
When a forall expression with a filtering predicate is captured into a
variable, the resulting array has a 1-based one-dimensional domain.
*Example (forallFilter.chpl)*.
The following expression returns every other element starting with
the first:
::
var s: [1..10] int = [i in 1..10] i;
var result =
::
[i in 1..s.numElements] if i % 2 == 1 then s(i)
::
;
writeln(result);
::
1 3 5 7 9
.. _Forall_Intents:
Forall Intents
--------------
If a variable is referenced within the lexical scope of a forall
statement or expression and is declared outside that forall construct,
it is subject to *forall intents*, analogously to task intents
(§`26.7 <#Task_Intents>`__) for task-parallel constructs. That is, the
variable is considered to be passed as an actual argument to each task
function created by the object or iterator leading the execution of the
loop. If no tasks are created, it is considered to be an actual argument
to the leader or standalone iterator itself. All references to the
variable within the forall construct implicitly refer to a *shadow
variable*, i.e. the corresponding formal argument of the task function
or the leader/standalone iterator.
When the forall construct is inside a method on a record and accesses a
field of ``this``, the field is treated as a regular variable. That is,
it is subject to forall intents and all references to this field within
the forall construct implicitly refer to the corresponding shadow
variable.
Each formal argument of a task function or iterator has the default
intent by default. For variables of primitive, enum, and class types,
this has the effect of capturing the value of the variable at task
creation time. Within the lexical scope of the forall construct, the
variable name references the captured value instead of the original
value.
| A formal can be given another intent explicitly by listing it with
that intent in the optional ``task-intent-clause``. For example, for
variables of most types, the ``ref`` intent allows the body of the
forall loop to modify the corresponding original variable or to read
its updated value after concurrent modifications. The ``in`` intent is
an alternative way to obtain task-private variables
(§`27.4 <#Task_Private_Variables>`__). A ``reduce`` intent can be used
to reduce values across iterations of a forall or coforall loop.
Reduce intents are described in the *Reduce Intents* technical note in
the online documentation:
| https://chapel-lang.org/docs/technotes/reduceIntents.html
*Rationale*.
A forall statement or expression may create tasks in its
implementation. Forall intents affect those tasks in the same way
that task intents §\ `26.7 <#Task_Intents>`__ affect the behavior of
a task construct such as a ``coforall`` loop.
.. _Task_Private_Variables:
Task-Private Variables
----------------------
A *task-private variable* declared in a forall loop results in a
separate shadow variable in each task created by the forall loop’s
parallel iterator, as well as a "top-level" shadow variable created at
the top level of the parallel iterator itself. In contrast to regular
forall intents §\ `27.3 <#Forall_Intents>`__, these shadow variables are
unrelated to outer variables of the same name, if any.
A given shadow variable is created at the start and destroyed at the end
of its task. Within the lexical scope of the body of the forall
statement or expression, the variable name refers to the shadow variable
created in the task that executed the current yield statement.
The "top-level" shadow variable is created at the start and destroyed at
the end of the parallel iterator. It is referenced in those iterations
of the forall loop that are due to "top-level" yields, i.e. yields that
are outside any of the task constructs that the iterator may have.
The syntax of a task-private variable declaration in a forall
statement’s with-clause is:
::
task-private-var-decl:
task-private-var-kind identifier type-part[OPT] initialization-part[OPT]
task-private-var-kind:
`const'
`var'
`ref'
The declaration of a ``const`` or ``var`` task-private variable must
have at least one of ``type-part`` and ``initialization-part``. A
``ref`` task-private variable must have ``initialization-part`` and
cannot have ``type-part``. A ``ref`` shadow variable is a reference to
the ``initialization-part`` as calculated at the start of the
corresponding task or the iterator. ``ref`` shadow variables are never
destroyed.
*Cray’s Chapel Implementation*.
Currently task-private variables are not available for task
constructs. A regular variable declared at the start of the
begin/cobegin/coforall block can be used instead.
..
*Example (task-private-variable.chpl)*.
In the following example, the ``writeln()`` statement will observe
the first shadow variable 4 times: twice each for the yields "before
coforall" and "after coforall". An additional shadow variable will be
created and observed twice for each of the three ``coforall`` tasks.
::
var cnt: atomic int; // count our shadow variables
record R { var id = cnt.fetchAdd(1); }
iter myIter() { yield ""; } // serial iterator, unused
iter myIter(param tag) where tag == iterKind.standalone {
for 1..2 do
yield "before coforall"; // shadow var 0 ("top-level")
coforall 1..3 do
for 1..2 do
yield "inside coforall"; // shadow vars 1..3
for 1..2 do
yield "after coforall"; // shadow var 0, again
}
forall str in myIter()
with (var tpv: R) // declare a task-private variable
do
writeln("shadow var: ", tpv.id, " yield: ", str);
::
\#!/usr/bin/env sh
testname=$1
outfile=$2
sort $outfile > $outfile.2
mv $outfile.2 $outfile
::
shadow var: 0 yield: after coforall
shadow var: 0 yield: after coforall
shadow var: 0 yield: before coforall
shadow var: 0 yield: before coforall
shadow var: 1 yield: inside coforall
shadow var: 1 yield: inside coforall
shadow var: 2 yield: inside coforall
shadow var: 2 yield: inside coforall
shadow var: 3 yield: inside coforall
shadow var: 3 yield: inside coforall
.. _Promotion:
Promotion
---------
A function that expects one or more scalar arguments but is called with
one or more arrays, domains, ranges, or iterators is promoted if the
element types of the arrays, the index types of the domains and/or
ranges, or the yielded types of the iterators can be resolved to the
type of the argument. The rules of when an overloaded function can be
promoted are discussed in §\ `13.13 <#Function_Resolution>`__.
Functions that can be promoted include procedures, operators, casts, and
methods. Also note that since class and record field access is performed
with getter methods (§\ `17.5.1 <#Getter_Methods>`__), field access can
also be promoted.
If the original function returns a value or a reference, the
corresponding promoted expression is an iterator yielding each computed
value or reference.
When a promoted expression is used to initialize a variable, such as
``var X = A.x;`` in the above example, the variable’s type will be
inferred to be an array. The array’s domain is defined by the expression
that causes promotion:
================ ============================================
input expression resulting array’s domain
================ ============================================
array that array’s domain
domain that domain
range one-dimensional domain built from that range
iterator 1-based one-dimensional domain
================ ============================================
..
*Future*.
We would like to allow the iterator author to specify the shape of
the iterator, i.e. the domain of the array that would capture the
result of the corresponding promoted expression, such as
::
var myArray = myScalarFunction(myIterator());
This will be helpful, for example, when the iterator yields one value
per an array or domain element that it iterates over internally.
*Example (promotion.chpl)*.
Given the array
::
var A: [1..5] int = [i in 1..5] i;
and the function
::
proc square(x: int) return x**2;
then the call ``square(A)`` results in the promotion of the
``square`` function over the values in the array ``A``. The result is
an iterator that returns the values ``1``, ``4``, ``9``, ``16``, and
``25``.
::
for s in square(A) do writeln(s);
::
1
4
9
16
25
..
*Example (field-promotion.chpl)*.
Given an array of points, such as ``A`` defined below:
::
record Point {
var x: real;
var y: real;
}
var A: [1..5] Point = [i in 1..5] new Point(x=i, y=i);
the following statement will create a new array consisting of the
``x`` field value for each value in A:
::
var X = A.x;
and the following call will set the ``y`` field values for each
element in A to 1.0:
::
A.y = 1.0;
::
writeln(X);
writeln(A);
::
1.0 2.0 3.0 4.0 5.0
(x = 1.0, y = 1.0) (x = 2.0, y = 1.0) (x = 3.0, y = 1.0) (x = 4.0, y = 1.0) (x = 5.0, y = 1.0)
.. _Promotion_Default_Arguments:
Default Arguments
~~~~~~~~~~~~~~~~~
When a call is promoted and that call relied upon default
arguments (§\ `13.4.2 <#Default_Values>`__), the default argument
expression can be evaluated many times. For example:
*Example (promotes-default.chpl)*.
::
var counter: atomic int;
proc nextCounterValue():int {
var i = counter.fetchAdd(1);
return i;
}
proc assignCounter(ref x:int, counter=nextCounterValue()) {
x = counter;
}
Here the function assignCounter has a default argument providing the
next value from an atomic counter as the value to set.
::
var A: [1..5] int;
assignCounter(A);
The assignCounter call uses both the default argument for counter as
well as promotion. When these features are combined, the default
argument will be evaluated once per promoted element. As a result,
after this command, A will contain the elements 0 1 2 3 4 in some
order.
::
writeln(A.sorted());
::
0 1 2 3 4
.. _Zipper_Promotion:
Zipper Promotion
~~~~~~~~~~~~~~~~
Promotion also supports zippered iteration semantics as described
in §\ `11.9.1 <#Zipper_Iteration>`__
and §\ `23.4 <#Parallel_Iterators>`__ for parallel iteration.
Consider a function ``f`` with formal arguments ``s1``, ``s2``, ... that
are promoted and formal arguments ``a1``, ``a2``, ... that are not
promoted. The call
::
f(s1, s2, ..., a1, a2, ...)
is equivalent to
::
[(e1, e2, ...) in zip(s1, s2, ...)] f(e1, e2, ..., a1, a2, ...)
The usual constraints of zipper iteration apply to zipper promotion so
the promoted actuals must have the same shape.
A zipper promotion can be captured in a variable, such as
``var X = f(s1, s2, ..., a1, a2, ...);`` using the above example. If so,
the domain of the resulting array is defined by the first argument that
causes promotion. The rules are the same as in the non-zipper case.
*Example (zipper-promotion.chpl)*.
Given a function defined as
::
proc foo(i: int, j: int) {
return (i,j);
}
and a call to this function written
::
writeln(foo(1..3, 4..6));
then the output is
::
(1, 4) (2, 5) (3, 6)
.. _Whole_Array_Operations:
Whole Array Operations and Evaluation Order
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Whole array operations are a form of promotion as applied to operators
rather than functions.
Whole array assignment is one example. It is is implicitly parallel. The
array assignment statement:
::
LHS = RHS;
is equivalent to
::
forall (e1,e2) in zip(LHS,RHS) do
e1 = e2;
The semantics of whole array assignment and promotion are different from
most array programming languages. Specifically, the compiler does not
insert array temporaries for such operations if any of the right-hand
side array expressions alias the left-hand side expression.
*Example*.
If ``A`` is an array declared over the indices ``1..5``, then the
following codes are not equivalent:
::
A[2..4] = A[1..3] + A[3..5];
and
::
var T = A[1..3] + A[3..5];
A[2..4] = T;
This follows because, in the former code, some of the new values that
are assigned to ``A`` may be read to compute the sum depending on the
number of tasks used to implement the data parallel statement.
.. _Reductions_and_Scans:
Reductions and Scans
--------------------
Chapel provides reduction and scan expressions that apply operators to
aggregate expressions in stylized ways. Reduction expressions collapse
the aggregate’s values down to a summary value. Scan expressions compute
an aggregate of results where each result value stores the result of a
reduction applied to all of the elements in the aggregate up to that
expression. Chapel provides a number of predefined reduction and scan
operators, and also supports a mechanism for the user to define
additional reductions and scans
(Chapter `[User_Defined_Reductions_and_Scans] <#User_Defined_Reductions_and_Scans>`__).
.. _reduce:
Reduction Expressions
~~~~~~~~~~~~~~~~~~~~~
A reduction expression applies a reduction operator to an aggregate
expression, collapsing the aggregate’s dimensions down into a result
value (typically a scalar or summary expression that is independent of
the input aggregate’s size). For example, a sum reduction computes the
sum of all the elements in the input aggregate expression.
The syntax for a reduction expression is given by:
::
reduce-expression:
reduce-scan-operator `reduce' iteratable-expression
class-type `reduce' iteratable-expression
reduce-scan-operator: one of
+ * && || & | ^ `min' `max' `minloc' `maxloc'
Chapel’s predefined reduction operators are defined by
``reduce-scan-operator`` above. In order, they are: sum, product,
logical-and, logical-or, bitwise-and, bitwise-or, bitwise-exclusive-or,
minimum, maximum, minimum-with-location, and maximum-with-location. The
minimum reduction returns the minimum value as defined by the <@
operator. The maximum reduction returns the maximum value as defined by
the >@ operator. The minimum-with-location reduction returns the lowest
index position with the minimum value (as defined by the <@ operator).
The maximum-with-location reduction returns the lowest index position
with the maximum value (as defined by the >@ operator). When a minimum,
maximum, minimum-with-location, or maximum-with-location reduction
encounters a NaN, the result is a NaN.
The expression on the right-hand side of the ``reduce`` keyword can be
of any type that can be iterated over, provided the reduction operator
can be applied to the values yielded by the iteration. For example, the
bitwise-and operator can be applied to arrays of boolean or integral
types to compute the bitwise-and of all the values in the array.
For the minimum-with-location and maximum-with-location reductions, the
argument on the right-hand side of the ``reduce`` keyword must be a
2-tuple. Its first component is the collection of values for which the
minimum/maximum value is to be computed. The second argument component
is a collection of indices with the same size and shape that provides
names for the locations of the values in the first component. The
reduction returns a tuple containing the minimum/maximum value in the
first argument component and the value at the corresponding location in
the second argument component.
*Example (reduce-loc.chpl)*.
The first line below computes the smallest element in an array ``A``
as well as its index, storing the results in ``minA`` and
``minALoc``, respectively. It then computes the largest element in a
forall expression making calls to a function ``foo()``, storing the
value and its number in ``maxVal`` and ``maxValNum``.
::
config const n = 10;
const D = {1..n};
var A: [D] int = [i in D] i % 7;
proc foo(x) return x % 7;
::
var (minA, minALoc) = minloc reduce zip(A, A.domain);
var (maxVal, maxValNum) = maxloc reduce zip([i in 1..n] foo(i), 1..n);
::
writeln((minA, minALoc));
writeln((maxVal, maxValNum));
::
(0, 7)
(6, 6)
User-defined reductions are specified by preceding the keyword
``reduce`` by the class type that implements the reduction interface as
described
in §\ `[User_Defined_Reductions_and_Scans] <#User_Defined_Reductions_and_Scans>`__.
.. _scan:
Scan Expressions
~~~~~~~~~~~~~~~~
A scan expression applies a scan operator to an aggregate expression,
resulting in an aggregate expression of the same size and shape. The
output values represent the result of the operator applied to all
elements up to and including the corresponding element in the input.
The syntax for a scan expression is given by:
::
scan-expression:
reduce-scan-operator `scan' iteratable-expression
class-type `scan' iteratable-expression
The predefined scans are defined by ``reduce-scan-operator``. These are
identical to the predefined reductions and are described
in §\ `27.6.1 <#reduce>`__.
The expression on the right-hand side of the scan can be of any type
that can be iterated over and to which the operator can be applied.
*Example*.
Given an array
::
var A: [1..3] int = 1;
that is initialized such that each element contains one, then the
code
::
writeln(+ scan A);
outputs the results of scanning the array with the sum operator. The
output is
::
1 2 3
User-defined scans are specified by preceding the keyword ``scan`` by
the class type that implements the scan interface as described in
Chapter \ `[User_Defined_Reductions_and_Scans] <#User_Defined_Reductions_and_Scans>`__.
.. _data_parallel_knobs:
Configuration Constants for Default Data Parallelism
----------------------------------------------------
The following configuration constants are provided to control the degree
of data parallelism over ranges, default domains, and default arrays:
============================= ======== =============================================================
**Config Const** **Type** **Default**
============================= ======== =============================================================
``dataParTasksPerLocale`` ``int`` top level ``.maxTaskPar`` (see §`28.1.2 <#Locale_Methods>`__)
``dataParIgnoreRunningTasks`` ``bool`` ``true``
``dataParMinGranularity`` ``int`` ``1``
============================= ======== =============================================================
The configuration constant ``dataParTasksPerLocale`` specifies the
number of tasks to use when executing a forall loop over a range,
default domain, or default array. The actual number of tasks may be
fewer depending on the other two configuration constants. A value of
zero results in using the default value.
The configuration constant ``dataParIgnoreRunningTasks``, when true, has
no effect on the number of tasks to use to execute the forall loop. When
false, the number of tasks per locale is decreased by the number of
tasks that are already running on the locale, with a minimum value of
one.
The configuration constant ``dataParMinGranularity`` specifies the
minimum number of iterations per task created. The number of tasks is
decreased so that the number of iterations per task is never less than
the specified value.
For distributed domains and arrays that have these same configuration
constants (*e.g.*, Block and Cyclic distributions), these same module
level configuration constants are used to specify their default behavior
within each locale.
Locales
=======
[Locales_Chapter]
Chapel provides high-level abstractions that allow programmers to
exploit locality by controlling the affinity of both data and tasks to
abstract units of processing and storage capabilities called *locales*.
The *on-statement* allows for the migration of tasks to *remote*
locales.
Throughout this section, the term *local* will be used to describe the
locale on which a task is running, the data located on this locale, and
any tasks running on this locale. The term *remote* will be used to
describe another locale, the data on another locale, and the tasks
running on another locale.
.. _Locales:
Locales
-------
A *locale* is a portion of the target parallel architecture that has
processing and storage capabilities. Chapel implementations should
typically define locales for a target architecture such that tasks
running within a locale have roughly uniform access to values stored in
the locale’s local memory and longer latencies for accessing the
memories of other locales. As an example, a cluster of multicore nodes
or SMPs would typically define each node to be a locale. In contrast a
pure shared memory machine would be defined as a single locale.
.. _The_Locale_Type:
Locale Types
~~~~~~~~~~~~
The identifier ``locale`` is a class type that abstracts a locale as
described above. Both data and tasks can be associated with a value of
locale type. A Chapel implementation may define subclass(es) of
``locale`` for a richer description of the target architecture.
.. _Locale_Methods:
Locale Methods
~~~~~~~~~~~~~~
The locale type supports the following methods:
::
proc locale.callStackSize: uint(64);
Returns the per-task call stack size used when creating tasks on the
locale in question. A value of 0 indicates that the call stack size is
determined by the system.
::
proc locale.id: int;
Returns a unique integer for each locale, from 0 to the number of
locales less one.
::
proc locale.maxTaskPar: int(32);
Returns an estimate of the maximum parallelism available for tasks on a
given locale.
::
proc locale.name: string;
Returns the name of the locale.
::
proc numPUs(logical: bool = false, accessible: bool = true);
Returns the number of processing unit instances available on a given
locale. Basically these are the things that execute instructions. If
``logical`` is ``false`` then the count reflects physical instances,
often referred to as *cores*. Otherwise it reflects logical instances,
such as hardware threads on multithreaded CPU architectures. If
``accessible`` is ``true`` then the count includes only those processors
the OS has made available to the program. Otherwise it includes all
processors that seem to be present.
::
use Memory;
proc locale.physicalMemory(unit: MemUnits=MemUnits.Bytes, type retType=int(64)): retType;
Returns the amount of physical memory available on a given locale in
terms of the specified memory units (Bytes, KB, MB, or GB) using a value
of the specified return type.
.. _Predefined_Locales_Array:
The Predefined Locales Array
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Chapel provides a predefined environment that stores information about
the locales used during program execution. This *execution environment*
contains definitions for the array of locales on which the program is
executing (``Locales``), a domain for that array (``LocaleSpace``), and
the number of locales (``numLocales``).
::
config const numLocales: int;
const LocaleSpace: domain(1) = [0..numLocales-1];
const Locales: [LocaleSpace] locale;
When a Chapel program starts, a single task executes ``main`` on
``Locales(0)``.
Note that the Locales array is typically defined such that distinct
elements refer to distinct resources on the target parallel
architecture. In particular, the Locales array itself should not be used
in an oversubscribed manner in which a single processor resource is
represented by multiple locale values (except during development).
Oversubscription should instead be handled by creating an aggregate of
locale values and referring to it in place of the Locales array.
*Rationale*.
This design choice encourages clarity in the program’s source text
and enables more opportunities for optimization.
For development purposes, oversubscription is still very useful and
this should be supported by Chapel implementations to allow
development on smaller machines.
..
*Example*.
The code
::
const MyLocales: [0..numLocales*4] locale
= [loc in 0..numLocales*4] Locales(loc%numLocales);
on MyLocales[i] ...
defines a new array ``MyLocales`` that is four times the size of the
``Locales`` array. Each locale is added to the ``MyLocales`` array
four times in a round-robin fashion.
.. _here:
The *here* Locale
~~~~~~~~~~~~~~~~~
A predefined constant locale ``here`` can be used anywhere in a Chapel
program. It refers to the locale that the current task is running on.
*Example*.
The code
::
on Locales(1) {
writeln(here.id);
}
results in the output ``1`` because the ``writeln`` statement is
executed on locale 1.
The identifier ``here`` is not a keyword and can be overridden.
.. _Querying_the_Locale_of_a_Variable:
Querying the Locale of an Expression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The locale associated with an expression (where the expression is
stored) is queried using the following syntax:
::
locale-access-expression:
expression . `locale'
When the expression is a class, the access returns the locale on which
the class object exists rather than the reference to the class. If the
expression is a value, it is considered local. The implementation may
warn about this behavior. If the expression is a locale, it is returned
directly.
*Example*.
Given a class C and a record R, the code
::
on Locales(1) {
var x: int;
var c: C;
var r: R;
on Locales(2) {
on Locales(3) {
c = new C();
r = new R();
}
writeln(x.locale.id);
writeln(c.locale.id);
writeln(r.locale.id);
}
}
results in the output
::
1
3
1
The variable ``x`` is declared and exists on ``Locales(1)``. The
variable ``c`` is a class reference. The reference exists on
``Locales(1)`` but the object itself exists on ``Locales(3)``. The
locale access returns the locale where the object exists. Lastly, the
variable ``r`` is a record and has value semantics. It exists on
``Locales(1)`` even though it is assigned a value on a remote locale.
Global (non-distributed) constants are replicated across all locales.
*Example*.
For example, the following code:
::
const c = 10;
for loc in Locales do on loc do
writeln(c.locale.id);
outputs
::
0
1
2
3
4
when running on 5 locales.
.. _On:
The On Statement
----------------
The on statement controls on which locale a block of code should be
executed or data should be placed. The syntax of the on statement is
given by
::
on-statement:
`on' expression `do' statement
`on' expression block-statement
The locale of the expression is automatically queried as described
in §\ `28.1.5 <#Querying_the_Locale_of_a_Variable>`__. Execution of the
statement occurs on this specified locale and then continues after the
``on-statement``.
Return statements may not be lexically enclosed in on statements. Yield
statements may only be lexically enclosed in on statements in parallel
iterators §\ `23.4 <#Parallel_Iterators>`__.
.. _remote_variable_declarations:
Remote Variable Declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, when new variables and data objects are created, they are
created in the locale where the task is running. Variables can be
defined within an ``on-statement`` to define them on a particular locale
such that the scope of the variables is outside the ``on-statement``.
This is accomplished using a similar syntax but omitting the ``do``
keyword and braces. The syntax is given by:
::
remote-variable-declaration-statement:
`on' expression variable-declaration-statement
Domain Maps
===========
[Domain_Maps]
A domain map specifies the implementation of the domains and arrays that
are *mapped* using it. That is, it defines how domain indices and array
elements are mapped to locales, how they are stored in memory, and how
operations such as accesses, iteration, and slicing are performed. Each
domain and array is mapped using some domain map.
A domain map is either a *layout* or a *distribution*. A layout
describes domains and arrays that exist on a single locale, whereas a
distribution describes domains and arrays that are partitioned across
multiple locales.
A domain map is represented in the program with an instance of a *domain
map class*. Chapel provides a set of standard domain map classes. Users
can create domain map classes as well.
Domain maps are presented as follows:
- domain maps for domain types §\ `29.1 <#Domain_Maps_For_Types>`__,
domain values §\ `29.2 <#Domain_Maps_For_Values>`__, and arrays
§\ `29.3 <#Domain_Maps_For_Arrays>`__
- domain maps are not retained upon domain assignment
§\ `29.4 <#Domain_Maps_Not_Assigned>`__
- | standard layouts and distributions, such as Block and Cyclic, are
documented under *Standard Library* in Cray Chapel online
documentation here:
| https://chapel-lang.org/docs/modules/layoutdist.html
- | specification of user-defined domain maps is forthcoming; please
refer to the *Domain Map Standard Interface* page under *Technical
Notes* in Cray Chapel online documentation here:
| https://chapel-lang.org/docs/technotes/dsi.html
.. _Domain_Maps_For_Types:
Domain Maps for Domain Types
----------------------------
Each domain type has a domain map associated with it. This domain map is
used to map all domain values of this type
(§`29.2 <#Domain_Maps_For_Values>`__).
If a domain type does not have a domain map specified for it explicitly
as described below, a default domain map is provided by the Chapel
implementation. Such a domain map will typically be a layout that maps
the entire domain to the locale on which the domain value is created or
the domain or array variable is declared.
*Cray’s Chapel Implementation*.
The default domain map provided by the Cray Chapel compiler is such a
layout. The storage for the representation of a domain’s index set is
placed on the locale where the domain variable is declared. The
storage for the elements of arrays declared over domains with the
default map is placed on the locale where the array variable is
declared. Arrays declared over rectangular domains with this default
map are laid out in memory in row-major order.
A domain map can be specified explicitly by providing a *dmap value* in
a ``dmapped`` clause:
::
mapped-domain-type:
domain-type `dmapped' dmap-value
dmap-value:
expression
A dmap value consists of an instance of a domain map class wrapped in an
instance of the predefined record ``dmap``. The domain map class is
chosen and instantiated by the user. ``dmap`` behaves like a generic
record with a single generic field, which holds the domain map instance.
*Example*.
The code
::
use BlockDist;
var MyBlockDist: dmap(Block(rank=2));
| declares a variable capable of storing dmap values for a
two-dimensional Block distribution. The Block distribution is
described in more detail in the standard library documentation.
See:
| https://chapel-lang.org/docs/modules/dists/BlockDist.html
..
*Example*.
The code
::
use BlockDist;
var MyBlockDist: dmap(Block(rank=2)) = new dmap(new Block({1..5,1..6}));
creates a dmap value wrapping a two-dimensional Block distribution
with a bounding box of ``\{1..5, 1..6\}`` over all of the locales.
*Example*.
The code
::
use BlockDist;
var MyBlockDist = new dmap(new Block({1..5,1..6}));
type MyBlockedDom = domain(2) dmapped MyBlockDist;
defines a two-dimensional rectangular domain type that is mapped
using a Block distribution.
The following syntactic sugar is provided within the ``dmapped`` clause.
If a ``dmapped`` clause starts with the name of a domain map class, it
is considered to be an initialization expression as if preceded by
``new``. The resulting domain map instance is wrapped in a newly-created
instance of ``dmap`` implicitly.
*Example*.
The code
::
use BlockDist;
type BlockDom = domain(2) dmapped Block({1..5,1..6});
is equivalent to
::
use BlockDist;
type BlockDom = domain(2) dmapped new dmap(new Block({1..5,1..6}));
.. _Domain_Maps_For_Values:
Domain Maps for Domain Values
-----------------------------
A domain value is always mapped using the domain map of that value’s
type. The type inferred for a domain literal
(§`21.2.1.2 <#Rectangular_Domain_Values>`__) has a default domain map.
*Example*.
In the following code
::
use BlockDist;
var MyDomLiteral = {1..2,1..3};
var MyBlockedDom: domain(2) dmapped Block({1..5,1..6}) = MyDomLiteral;
``MyDomLiteral`` is given the inferred type of the domain literal and
so will be mapped using a default map. MyBlockedDom is given a type
explicitly, in accordance to which it will be mapped using a Block
distribution.
A domain value’s map can be changed explicitly with a ``dmapped``
clause, in the same way as a domain type’s map.
::
mapped-domain-expression:
domain-expression `dmapped' dmap-value
..
*Example*.
In the following code
::
use BlockDist;
var MyBlockedDomLiteral1 = {1..2,1..3} dmapped new dmap(new Block({1..5,1..6}));
var MyBlockedDomLiteral2 = {1..2,1..3} dmapped Block({1..5,1..6});
both ``MyBlockedDomLiteral1`` and ``MyBlockedDomLiteral2`` will be
mapped using a Block distribution.
.. _Domain_Maps_For_Arrays:
Domain Maps for Arrays
----------------------
Each array is mapped using the domain map of the domain over which the
array was declared.
*Example*.
In the code
::
use BlockDist;
var Dom: domain(2) dmapped Block({1..5,1..6}) = {1..5,1..6};
var MyArray: [Dom] real;
the domain map used for ``MyArray`` is the Block distribution from
the type of ``Dom``.
.. _Domain_Maps_Not_Assigned:
Domain Maps Are Not Retained upon Domain Assignment
---------------------------------------------------
Domain assignment (§`21.8.1 <#Domain_Assignment>`__) transfers only the
index set of the right-hand side expression. The implementation of the
left-hand side domain expression, including its domain map, is
determined by its type and so does not change upon a domain assignment.
*Example*.
In the code
::
use BlockDist;
var Dom1: domain(2) dmapped Block({1..5,1..6}) = {1..5,1..6};
var Dom2: domain(2) = Dom1;
``Dom2`` is mapped using the default distribution, despite ``Dom1``
having a Block distribution.
..
*Example*.
In the code
::
use BlockDist;
var Dom1: domain(2) dmapped Block({1..5,1..6}) = {1..5,1..6};
var Dom2 = Dom1;
``Dom2`` is mapped using the same distribution as ``Dom1``. This is
because the declaration of ``Dom2`` lacks an explicit type specifier
and so its type is defined to be the type of its initialization
expression, ``Dom1``. So in this situation the effect is that the
domain map does transfer upon an initializing assignment.
User-Defined Reductions and Scans
=================================
[User_Defined_Reductions_and_Scans]
User-defined reductions and scans are supported via class definitions
where the class implements a structural interface. The definition of
this structural interface is forthcoming. The following paper sketched
out such an interface:
S. J. Deitz, D. Callahan, B. L. Chamberlain, and L. Snyder.
**Global-view abstractions for user-defined reductions and scans**.
In *Proceedings of the Eleventh ACM SIGPLAN Symposium on Principles
and Practice of Parallel Programming*, 2006.
Memory Consistency Model
========================
[Memory_Consistency_Model]
In this section, we describe Chapel’s memory consistency model. The
model is based on *sequential consistency for data-race-free* programs
as adopted by C11, C++11, Java, UPC, and Fortran 2008.
Sequential consistency (SC) means that all Chapel tasks agree on the
interleaving of memory operations and this interleaving results in an
order is consistent with the order of operations in the program source
code. *Conflicting memory operations*, i.e., operations to the same
variable, or memory location, and one of which is a write, form a data
race if they are from different Chapel tasks and can be executed
concurrently. Accesses to the same variable from different tasks can
result from the tasks referencing the same variable directly – or
indirectly via aliases. Aliases arise, for example, when using ``ref``
variables, argument intents, return intents, task intents and forall
intents.
Any Chapel program with a data race is not a valid program, and an
implementation cannot be relied upon to produce consistent behavior.
Valid Chapel programs will use synchronization constructs such as
*sync*, *single*, or *atomic* variables or higher-level constructs based
on these to enforce ordering for conflicting memory operations.
The following design principles were used in developing Chapel’s memory
consistency model:
#. Sequential programs have program order semantics. Programs that are
completely sequential cannot have data races and should appear to
execute as though each statement was executed one at a time and in
the expected order.
#. Chapel’s fork-join constructs introduce additional order
dependencies. Operations within a task cannot behave as though they
started before the task started. Similarly, all operations in a task
must appear to be completed to a parent task when the parent task
joins with that task.
#. Multi-locale programs have the same memory consistency model as
single-locale programs. The Chapel language seeks to allow a single
description of an algorithm to work with different data
distributions. A result of this property is that an expression of a
program must be correct whether it is working on local or distributed
data.
#. Chapel’s memory model should be as relaxed as possible while still
consistent with these design principles. In particular, making all
operations sequentially consistent is not likely to enable good
performance. At the same time, sequential consistency should be
available to programmers when requested.
See *A Primer on Memory Consistency and Cache Coherence* by Sorin,
*et al.* for more background information on memory consistency models.
This chapter will proceed in a manner inspired by the :math:`XC` memory
model described there.
.. _SC_for_DRF:
Sequential Consistency for Data-Race-Free Programs
--------------------------------------------------
Sequential consistency for data-race-free programs is described in terms
of two orders: *program order* and *memory order*. The *program order*
:math:`<_p` is a partial order describing serial or fork-join
parallelism dependencies between variable reads and writes. The *memory
order* :math:`<_m` is a total order that describes the semantics of
synchronizing memory operations (via ``atomic``, ``sync`` or ``single``
variables) with sequential consistency. Non-SC atomic operations
(described in Section \ `31.2 <#non_sc_atomics>`__) do not create this
total order.
Note that ``sync/single`` variables have memory consistency behavior
equivalent to a sequence of SC operations on ``atomic`` variables. Thus
for the remainder of the chapter, we will primarily focus on operations
on ``atomic`` variables.
We will use the following notation:
- :math:`L(a)` indicates a *load* from a variable at address :math:`a`.
:math:`a` could refer to local or remote memory.
- :math:`S(a)` indicates a *store* to a variable at address :math:`a`.
:math:`a` could refer to local or remote memory.
- :math:`A_{sc}(a)` indicates an *atomic operation* on a variable at
address :math:`a` with sequential consistency. The variable at
address :math:`a` could refer to local or remote memory. Atomic
operations must be completed as a single operation (i.e. atomically),
and so it is not possible to observe an intermediate state from an
atomic operation under any circumstances.
- :math:`A_r(a,o)` indicates an *atomic operation* on a variable at
address :math:`a` with ordering constraint :math:`o`, where :math:`o`
can be one of *relaxed*, *acquire*, or *release* (see
Section \ `31.2 <#non_sc_atomics>`__). As with :math:`A_{sc}(a)`,
relaxed atomic operations must be completed as a single operation.
- :math:`L(a)`, :math:`S(a)`, :math:`A_{sc}(a)`, and :math:`A_r(a,o)`
are also called *memory operations*
- :math:`X <_p Y` indicates that :math:`X` precedes :math:`Y` in
program order
- :math:`X <_m Y` indicates that :math:`X` precedes :math:`Y` in memory
order
- ``t = begin\{X\}`` starts a new task named :math:`t` to execute
:math:`X`
- ``waitFor($t_1$..$t_n$)`` waits for tasks :math:`t_1..t_n` to
complete
- ``on(L)`` migrates the running task to locale :math:`L`. Note that
while the ``on`` statement may change the locale on which the current
task is running, it has no impact on the memory consistency
requirements.
For the purposes of describing this memory model, it is assumed that
Chapel programs will be translated into sequences of *memory
operations*, ``begin`` statements, and ``waitFor`` statements. The
translation of a Chapel program into a sequence of *memory operations*
must preserve sequential program semantics. That is, if we have a
snippet of a Chapel program without task operations, such as ``X; Y;``,
the statements :math:`X` and :math:`Y` will be converted into a sequence
of *load*, *store*, and *atomic operations* in a manner that preserves
the behavior of a this serial portion of the program. That is,
:math:`X=x_1,x_2,...` and :math:`Y=y_1,y_2,...` where :math:`x_i` and
:math:`y_j` are each a sequence of *load*, *store*, or *atomic
operations* and we have :math:`x_i <_p y_j`.
Likewise, for the purposes of this memory model, Chapel’s parallelism
keywords are viewed as a sequence of operations including the primitives
of starting a task (``begin``) and waiting for some number of tasks
(``waitFor($t_1$..$t_n$)``). In particular:
- ``forall`` (including promotion) creates some number of tasks
:math:`m` to execute the :math:`n` iterations of the loop
(``$t_i$ = begin\{some-loop-bodies\}`` for each task
:math:`i=1`..\ :math:`m`) and waits for them to complete
(``waitFor($t_1$..$t_m$)``). The number of tasks :math:`m` is defined
by the implementation of the parallel iterator (See
Section \ `[Iterators] <#Iterators>`__ for details on iterators).
- ``coforall`` creates one task per loop iteration
(``$t_i$ = begin\{loop-body\}`` for all loop iterations
:math:`i=1..n`) and then waits for them all to complete
(``waitFor($t_1$..$t_n$)``).
- ``cobegin`` creates one task per enclosed statement
(``$t_i$ = begin\{$X_i$\}`` for statements
:math:`X_1`..\ :math:`X_n`) and then waits for them all to complete
(``waitFor($t_1$..$t_n$)``).
- ``begin`` creates a task to execute the enclosed statement
(``t = begin\{X\}``). The ``sync`` statement waits for all tasks
:math:`t_i` created by a ``begin`` statement in the dynamic scope of
the ``sync`` statement that are not within other, nested ``sync``
statements (``waitFor($t_1$..$t_n$)`` for all :math:`n` such tasks).
.. _program_order:
Program Order
~~~~~~~~~~~~~
Task creation and task waiting create a conceptual tree of program
statements. The task bodies, task creation, and task wait operations
create a partial order :math:`<_p` of program statements. For the
purposes of this section, the statements in the body of each Chapel task
will be implemented in terms of *load*, *store*, and *atomic
operations*.
- If we have a program snippet without tasks, such as ``X; Y;``, where
:math:`X` and :math:`Y` are memory operations, then :math:`X <_p Y`.
- The program ``X; begin\{Y\}; Z;`` implies :math:`X` :math:`<_p`
:math:`Y`. However, there is no particular relationship between
:math:`Y` and :math:`Z` in program order.
- The program ``t = begin\{Y\}; waitFor(t); Z;`` implies :math:`Y`
:math:`<_p` :math:`Z`
- :math:`X` :math:`<_p` :math:`Y` and :math:`Y` :math:`<_p` :math:`Z`
imply :math:`X` :math:`<_p` :math:`Z`
.. _memory_order:
Memory Order
~~~~~~~~~~~~
The memory order :math:`<_m` of SC atomic operations in a given task
respects program order as follows:
- If :math:`A_{sc}(a)<_pA_{sc}(b)` then :math:`A_{sc}(a)<_mA_{sc}(b)`
Every SC atomic operation gets its value from the last SC atomic
operation before it to the same address in the total order :math:`<_m`:
- Value of :math:`A_{sc}(a)` = Value of
:math:`max_{<_m} ( A_{sc}'(a)|A_{sc}'(a) <_m A_{sc}(a) )`
For data-race-free programs, every load gets its value from the last
store before it to the same address in the total order :math:`<_m`:
- Value of :math:`L(a)` = Value of :math:`max_{<_m}`
:math:`( S(a)|S(a)` :math:`<_m` :math:`L(a)` or :math:`S(a)`
:math:`<_p` :math:`L(a) )`
For data-race-free programs, loads and stores are ordered with SC
atomics. That is, loads and stores for a given task are in total order
:math:`<_m` respecting the following rules which preserve the order of
loads and stores relative to SC atomic operations:
- If :math:`L(a)<_pA_{sc}(b)` then :math:`L(a)<_mA_{sc}(b)`
- If :math:`S(a)<_pA_{sc}(b)` then :math:`S(a)<_mA_{sc}(b)`
- If :math:`A_{sc}(a)<_pL(b)` then :math:`A_{sc}(a)<_mL(b)`
- If :math:`A_{sc}(a)<_pS(b)` then :math:`A_{sc}(a)<_mS(b)`
For data-race-free programs, loads and stores preserve sequential
program behavior. That is, loads and stores to the same address in a
given task are in the order :math:`<_m` respecting the following rules
which preserve sequential program behavior:
- If :math:`L(a) <_p L'(a)` then :math:`L(a) <_m L'(a)`
- If :math:`L(a) <_p S(a)` then :math:`L(a) <_m S(a)`
- If :math:`S(a) <_p S'(a)` then :math:`S(a) <_m S'(a)`
.. _non_sc_atomics:
Non-Sequentially Consistent Atomic Operations
---------------------------------------------
Sequential consistency for atomic operations can be a performance
bottleneck under some circumstances. Chapel provides non-SC atomic
operations to help alleviate such situations. Such uses of atomic
operations must be done with care and should generally not be used to
synchronize tasks.
Non-SC atomic operations are specified by providing a *memory order*
argument to the atomic operations. See
Section \ `26.4.1 <#Functions_on_Atomic_Variables>`__ for more
information on the memory order types.
.. _relaxed_atomics:
Relaxed Atomic Operations
~~~~~~~~~~~~~~~~~~~~~~~~~
Although Chapel’s relaxed atomic operations (``memory\_order\_relaxed``)
do not complete in a total order by themselves and might contribute to
non-deterministic programs, relaxed atomic operations cannot contribute
to a data race that would prevent sequential consistency.
When relaxed atomics are used only for atomicity and not as part of
synchronizing tasks, their effect can be understood in the memory
consistency model described in `31.1 <#SC_for_DRF>`__ by treating them
as ordinary loads or stores with two exceptions:
- Atomic operations (including relaxed atomic operations) cannot create
data races.
- All atomic operations (including relaxed atomic operations) will
eventually be visible to all other threads. This property is not true
for normal loads and stores.
.. _unordered_operations:
Unordered Memory Operations
---------------------------
*Open issue*.
Syntax for *unordered* operations has not yet been finalized.
..
*Open issue*.
Should Chapel provide a memory fence that only completes unordered
operations started by the current task?
*Open issue*.
Should unordered operations on a particular memory address always
wait for the address to be computed?
..
*Open issue*.
Does starting a task or joining with a task necessarily wait for
unordered operations to complete?
Rather than issuing normal loads and stores to read or write local or
remote memory, a Chapel program can use *unordered* loads and stores
when preserving sequential program behavior is not important. The
following notation for unordered memory operations will be used in this
section:
- :math:`UL(a)` indicates an *unordered* *load* from a variable at
address :math:`a`. :math:`a` could point to local or remote memory.
- :math:`US(a)` indicates an *unordered* *store* to a variable at
address :math:`a`. Again, :math:`a` could point to local or remote
memory.
The *unordered* loads and stores :math:`UL(a)` and :math:`US(a)` respect
fences but not program order. As in
Section \ `31.1.2 <#memory_order>`__, unordered loads and stores are
ordered with SC atomics. That is, unordered loads and stores for a given
task are in total order :math:`<_m` respecting the following rules which
preserve the order of unordered loads and stores relative to SC atomic
operations:
- If :math:`UL(a)<_pA_{sc}(b)` then :math:`UL(a)<_mA_{sc}(b)`
- If :math:`US(a)<_pA_{sc}(b)` then :math:`US(a)<_mA_{sc}(b)`
- If :math:`A_{sc}(a)<_pUL(b)` then :math:`A_{sc}(a)<_mUL(b)`
- If :math:`A_{sc}(a)<_pUS(b)` then :math:`A_{sc}(a)<_mUS(b)`
Unordered loads and stores do not preserve sequential program behavior.
Unordered Memory Operations Examples
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unordered operations should be thought of as happening in a way that
overlaps with the program task. Unordered operations started in
different program statements can happen in any order unless an SC atomic
operation orders them.
Since unordered operations started by a single task can happen in any
order, totally sequential programs can have a data race when using
unordered operations. This follows from our original definition of data
race.
::
var x: int = 0;
unordered_store(x, 10);
unordered_store(x, 20);
writeln(x);
The value of *x* at the end of this program could be 0, 10, or 20. As a
result, programs using unordered loads and stores are not sequentially
consistent unless the program can guarantee that unordered operations
can never operate on the same memory at the same time when one of them
is a store. In particular, the following are safe:
- Unordered stores to disjoint regions of memory.
- Unordered loads from potentially overlapping regions of memory when
no store could overlap with the loads.
- Unordered loads and stores to the same memory location when these are
always separated by an SC atomic operation.
Unordered loads and stores are available as a performance optimization.
For example, a program computing a permutation on an array might want to
move data between two arrays without requiring any ordering:
::
const n = 10;
// P is a permutation on 1..n, in this case reversing its input
var P = for i in 1..n by -1 do i;
// A is an array to permute
var A = for i in 1..n do i;
// Compute, in B, the permutation applied to A
var B:[1..n] int;
for i in 1..n {
unordered_store(B[P[i]], A[i]);
}
.. _MCM_examples:
Examples
--------
*Example*.
In this example, a synchronization variable is used to (a) ensure
that all writes to an array of unsynchronized variables are complete,
(b) signal that fact to a second task, and (c) pass along the number
of values that are valid for reading.
The program
::
var A: [1..100] real;
var done(*\texttt{\$}*): sync int; // initially empty
cobegin {
{ // Reader task
const numToRead = done(*\texttt{\$}*); // block until writes are complete
for i in 1..numToRead do
writeln("A[", i, "] = ", A[i]);
}
{ // Writer task
const numToWrite = 14; // an arbitrary number
for i in 1..numToWrite do
A[i] = i/10.0;
done(*\texttt{\$}*) = numToWrite; // fence writes to A and signal done
}
}
produces the output
::
A[1] = 0.1
A[2] = 0.2
A[3] = 0.3
A[4] = 0.4
A[5] = 0.5
A[6] = 0.6
A[7] = 0.7
A[8] = 0.8
A[9] = 0.9
A[10] = 1.0
A[11] = 1.1
A[12] = 1.2
A[13] = 1.3
A[14] = 1.4
..
*Example (syncSpinWait.chpl)*.
One consequence of Chapel’s memory consistency model is that a task
cannot spin-wait on a normal variable waiting for another task to
write to that variable. The behavior of the following code is
undefined:
::
if false { // }
::
var x: int;
cobegin with (ref x) {
while x != 1 do ; // INCORRECT spin wait
x = 1;
}
::
// {
}
In contrast, spinning on a synchronization variable has well-defined
behavior:
::
var x(*\texttt{\$}*): sync int;
cobegin {
while x(*\texttt{\$}*).readXX() != 1 do ; // spin wait
x(*\texttt{\$}*).writeXF(1);
}
In this code, the first statement in the cobegin statement executes a
loop until the variable is set to one. The second statement in the
cobegin statement sets the variable to one. Neither of these
statements block.
*Example (atomicSpinWait.chpl)*.
Atomic variables provide an alternative means to spin-wait. For
example:
::
var x: atomic int;
cobegin {
while x.read() != 1 do ; // spin wait - monopolizes processor
x.write(1);
}
..
*Example (atomicWaitFor.chpl)*.
The main drawback of the above example is that it prevents the thread
executing the spin wait from doing other useful work. Atomic
variables include a waitFor method that will block the calling thread
until a read of the atomic value matches a particular value. In
contrast to the spin wait loop above, waitFor will allow other tasks
to be scheduled. For example:
::
var x: atomic int;
cobegin {
x.waitFor(1);
x.write(1);
}
*Future*.
Upon completion, Chapel’s atomic
statement (§\ `26.10 <#Atomic_Statement>`__) will serve as an
additional means of correctly synchronizing between tasks.
Interoperability
================
[Interoperability]
Chapel’s interoperability features support cooperation between Chapel
and other languages. They provide the ability to create software systems
that incorporate both Chapel and non-Chapel components. Thus, they
support the reuse of existing software components while leveraging the
unique features of the Chapel language.
Interoperability can be broken down in terms of the exchange of types,
variables and procedures, and whether these are imported or exported. An
overview of procedure importing and exporting is provided
in §\ `32.1 <#Interop_Overview>`__. Details on sharing types, variables
and procedures are supplied in §\ `32.2 <#Shared_Language_Elements>`__.
*Future*.
At present, the backend language for Chapel is C, which makes it
relatively easy to call C libraries from Chapel and vice versa. To
support a variety of platforms without requiring recompilation, it
may be desirable to move to an intermediate-language model.
In that case, each supported platform must minimally support that
virtual machine. However, in addition to increased portability, a
virtual machine model may expose elements of the underlying machine’s
programming model (hardware task queues, automated garbage
collection, etc.) that are not easily rendered in C. In addition, the
virtual machine model can support run-time task migration.
The remainder of this chapter documents Chapel support of
interoperability through the existing C-language backend.
.. _Interop_Overview:
Interoperability Overview
-------------------------
The following two subsections provide an overview of calling
externally-defined (C) routines in Chapel, and setting up Chapel
routines so they can be called from external (C) code.
.. _Calling_External_Functions:
Calling External Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~
To use an external function in a Chapel program, it is necessary to
inform the Chapel compiler of that routine’s signature through an
external function declaration. This permits Chapel to bind calls to that
function signature during function resolution. The user must also supply
a definition for the referenced function by naming a C source file, an
object file or an object library on the ``chpl`` command line.
An external procedure declaration has the following syntax:
::
external-procedure-declaration-statement:
`extern' external-name[OPT] `proc' function-name argument-list return-intent[OPT] return-type[OPT]
Chapel code will call the external function using the parameter types
supplied in the ``extern`` declaration. Therefore, in general, the type
of each argument in the supplied ``argument-list`` must be the Chapel
equivalent of the corresponding external type.
The return value of the function can be used by Chapel only if its type
is declared using the optional ``return-type`` specifier. If it is
omitted, Chapel assumes that no value is returned, or equivalently that
the function returns ``void``.
It is possible to use the ``external-name`` syntax to create an
``extern`` function that presents a different name to Chapel code than
the name of the function actually used when linking. The
``external-name`` expression must evaluate to a ``param`` ``string``.
For example, the code below declares a function callable in Chapel as
``c_atoi`` but that will actually link with the C ``atoi`` function.
::
extern "atoi" proc c_atoi(arg:c_string):c_int;
At present, external iterators are not supported.
*Future*.
The overloading of function names is also not supported directly in
the compiler. However, one can use the ``external-name`` syntax to
supply a name to be used by the linker. In this way, function
overloading can be implemented “by hand”. This syntax also supports
calling external C++ routines: The ``external-name`` to use is the
mangled function name generated by the external compilation
environment [4]_.
..
*Future*.
Dynamic dispatch (polymorphism) is also unsupported in this version.
But this is not ruled out in future versions. Since Chapel already
supports type-based procedure declaration and resolution, it is a
small step to translate a type-relative extern method declaration
into a virtual method table entry. The mangled name of the correct
external function must be supplied for each polymorphic type
available. However, most likely the generation of ``.chpl`` header
files from C and C++ libraries can be fully automated.
There are three ways to supply to the Chapel compiler the definition of
an external function: as a C source file (``.c`` or ``.h``), as an
object file and as an object library. It is platform-dependent whether
static libraries (archives), dynamic libraries or both are supported.
See the ``chpl`` man page for more information on how these file types
are handled.
.. _Calling_Chapel_Functions:
Calling Chapel Functions
~~~~~~~~~~~~~~~~~~~~~~~~
To call a Chapel procedure from external code, it is necessary to expose
the corresponding function symbol to the linker. This is done by adding
the ``export`` linkage specifier to the function definition. The
``export`` specifier ensures that the corresponding procedure will be
resolved, even if it is not called within the Chapel program or library
being compiled.
An exported procedure declaration has the following syntax:
::
exported-procedure-declaration-statement:
`export' external-name[OPT] `proc' function-name argument-list return-intent[OPT] return-type[OPT]
function-body
external-name:
expression
The rest of the procedure declaration is the same as for a non-exported
function. An exported procedure can be called from within Chapel as
well. Currently, iterators cannot be exported.
As with the ``extern-name`` for ``extern`` ``proc``, if this syntax
element is provided, then it must be a ``param`` ``string`` and will be
used to determine the name of the function to use when linking. For
example, the code below declares a function callable in C as
``chapel_addone`` but it is callable from Chapel code as ``addone``:
::
export "chapel_addone" proc addone(arg:c_int):c_int {
return arg+1;
}
..
*Future*.
Currently, exported functions cannot have generic, ``param`` or type
arguments. This is because such functions actually represent a family
of functions, specific versions of which are instantiated as need
during function resolution.
Instantiating all possible versions of a template function is not
practical in general. However, if explicit instantiation were
supported in Chapel, an explicit instantiation with the export
linkage specifier would clearly indicate that the matching template
function was to be instantiated with the given ``param`` values and
argument types.
.. _Shared_Language_Elements:
Shared Language Elements
------------------------
This section provides details on how to share Chapel types, variables
and procedures with external code. It is written assuming that the
intermediate language is C.
Shared Types
~~~~~~~~~~~~
This subsection discusses how specific types are shared between Chapel
and external code.
.. _Referring_to_Standard_C_Types:
Referring to Standard C Types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In Chapel code, all standard C types must be expressed in terms of their
Chapel equivalents. This is true, whether the entity is exported,
imported or private. Standard C types and their corresponding Chapel
types are shown in the following table.
=========== =========== ============ ============ ======== ===========
C Type Chapel Type C Type Chapel Type C Type Chapel Type
=========== =========== ============ ============ ======== ===========
int8_t int(8) uint8_t uint(8) \_real32 real(32)
int16_t int(16) uint16_t uint(16) \_real64 real(64)
int32_t int(32) uint32_t uint(32) \_imag32 imag(32)
int64_t int(64) uint64_t uint(64) \_imag64 imag(64)
chpl_bool bool const char\* c_string
\_complex64 complex(64) \_complex128 complex(128)
=========== =========== ============ ============ ======== ===========
Standard C types are built-in. Their Chapel equivalents do not have to
be declared using the ``extern`` keyword.
In C, the “colloquial” integer type names ``char``, ``signed char``,
``unsigned char``, (``signed``) ``short`` (``int``), ``unsigned short``
(``int``), (``signed``) ``int``, ``unsigned int``, (``signed``) ``long``
(``int``), ``unsigned long`` (``int``), (``signed``) ``long long``
(``int``) and ``unsigned long long`` (``int``) may have an
implementation-defined width. [5]_. When referring to C types in a
Chapel program, the burden of making sure the type sizes agree is on the
user. A Chapel implementation must ensure that all of the C equivalents
in the above table are defined and have the correct representation with
respect to the corresponding Chapel type.
.. _Referring_to_External_C_Types:
Referring to External C Types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An externally-defined type can be referenced using a external type
declaration with the following syntax.
external-type-alias-declaration-statement: ‘extern’ ‘type’
type-alias-declaration-list ;
In each ``type-alias-declaration``, if the ``type-expression`` part is
supplied, then Chapel uses the supplied type specifier internally.
Otherwise, it treats the named type as an opaque type. The definition
for an external type must be supplied by a C header file named on the
``chpl`` command line.
Fixed-size C array types can be described within Chapel using its
homogeneous tuple type. For example, the C typedef
typedef double vec[3];
can be described in Chapel using
extern type vec = 3*real(64);
.. _Referring_to_External_C_Structs:
Referring to External C Structs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
External C struct types can be referred to within Chapel by prefixing a
Chapel ``record`` definition with the ``extern`` keyword.
::
external-record-declaration-statement:
`extern' external-name[OPT] simple-record-declaration-statement
For example, consider an external C structure defined in ``foo.h``
called ``fltdbl``.
::
typedef struct _fltdbl {
float x;
double y;
} fltdbl;
This type could be referred to within a Chapel program using
::
extern record fltdbl {
var x: real(32);
var y: real(64);
}
and defined by supplying ``foo.h`` on the ``chpl`` command line.
Within the Chapel declaration, some or all of the fields from the C
structure may be omitted. The order of these fields need not match the
order they were specified within the C code. Any fields that are not
specified (or that cannot be specified because there is no equivalent
Chapel type) cannot be referenced within the Chapel code. Some effort is
made to preserve the values of the undefined fields when copying these
structs but Chapel cannot guarantee the contents or memory story of
fields of which it has no knowledge.
If the optional ``external-name`` is supplied, then it is used verbatim
as the exported struct symbol.
A C header file containing the struct’s definition in C must be
specified on the chpl compiler command line. Note that only typdef’d C
structures are supported by default. That is, in the C header file, the
``struct`` must be supplied with a type name through a ``typedef``
declaration. If this is not true, you can use the ``external-name`` part
to apply the ``struct`` specifier. As an example of this, given a C
declaration of:
::
struct Vec3 {
double x, y, z;
};
in Chapel you would refer to this ``struct`` via
::
extern "struct Vec3" record Vec3 {
var x, y, z: real(64);
}
.. _Opaque_Types:
Opaque Types
^^^^^^^^^^^^
It is possible refer to external pointer-based C types that cannot be
described in Chapel by using the "opaque" keyword. As the name implies,
these types are opaque as far as Chapel is concerned and cannot be used
for operations other than argument passing and assignment.
For example, Chapel could be used to call an external C function that
returns a pointer to a structure (that can’t or won’t be described as a
pointer to an external record) as follows:
::
extern proc returnStructPtr(): opaque;
var structPtr: opaque = returnStructPtr();
However, because the type of ``structPtr`` is opaque, it can be used
only in assignments and the arguments of functions expecting the same
underlying type.
::
var copyOfStructPtr = structPtr;
extern proc operateOnStructPtr(ptr: opaque);
operateOnStructPtr(structPtr);
Like a ``void*`` in C, Chapel’s ``opaque`` carries no information
regarding the underlying type. It therefore subverts type safety, and
should be used with caution.
.. _Shared_Data:
Shared Data
~~~~~~~~~~~
This subsection discusses how to access external variables and
constants.
A C variable or constant can be referred to within Chapel by prefixing
its declaration with the extern keyword. For example:
::
extern var bar: foo;
would tell the Chapel compiler about an external C variable named
``bar`` of type ``foo``. Similarly,
::
extern const baz: int(32);
would refer to an external 32-bit integer constant named ``baz`` in the
C code. In practice, external consts can be used to provide Chapel
definitions for #defines and enum symbols in addition to traditional C
constants.
*Cray’s Chapel Implementation*.
Note that since params must be known to Chapel at compile-time and
the Chapel compiler does not necessarily parse C code, external
params are not supported.
.. _Shared_Procedures:
Shared Procedures
~~~~~~~~~~~~~~~~~
This subsection provides additional detail and examples for calling
external procedures from Chapel and for exporting Chapel functions for
external use.
.. _Calling_External_C_Functions:
Calling External C Functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To call an external C function, a prototype of the routine must appear
in the Chapel code. This is accomplished by providing the Chapel
signature of the function preceded by the ``extern`` keyword. For
example, for a C function foo() that takes no arguments and returns
nothing, the prototype would be:
::
extern proc foo();
To refer to the return value of a C function, its type must be supplied
through a ``return-type`` clause in the prototype. [6]_
If the above function returns a C ``double``, it would be declared as:
::
extern proc foo(): real;
Similarly, for external functions that expect arguments, the types of
those arguments types may be declared in Chapel using explicit argument
type specifiers.
The types of function arguments may be omitted from the external
procedure declaration, in which case they are inferred based on the
Chapel callsite. For example, the Chapel code
::
extern proc foo(x: int, y): real;
var a, b: int;
foo(a, b);
would imply that the external function foo takes two 64-bit integer
values and returns a 64-bit real. External function declarations with
omitted type arguments can also be used call external C macros.
External function arguments can be declared using the
``default-expression`` syntax. In this case, the default argument will
be supplied by the Chapel compiler if the corresponding actual argument
is omitted at the callsite. For example:
::
extern proc foo(x: int, y = 1.2): real;
foo(0);
Would cause external function foo() to be invoked with the arguments 0
and 1.2.
C varargs functions can be declared using Chapel’s
``variable-argument-expression`` syntax (``...``). For example, the C
``printf`` function can be declared in Chapel as
::
extern proc printf(fmt: c_string, vals...?numvals): int;
External C functions or macros that accept type arguments can also be
prototyped in Chapel by declaring the argument as a type. For example:
::
extern foo(type t);
Calling such a routine with a Chapel type will cause the type identifier
(e.g., ’int’) to be passed to the routine. [7]_
.. _Calling_Chapel_Procedures_Externally:
Calling Chapel Procedures Externally
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To call a Chapel procedure from external code, the procedure name must
be exported using the ``export`` keyword. An exported procedure taking
no arguments and returning a 64-bit integer can be declared as:
::
export proc foo(): int { ... }
If the optional ``external-name`` is supplied, that is the name used in
linking with external code. For example, if we declare
::
export "myModule_foo" proc foo(): int { ... }
then the name ``foo`` is used to refer to the procedure within chapel
code, whereas a call to the same function from C code would appear as
``myModule_foo();``. If the external name is omitted, then its internal
name is also used externally.
When a procedure is exported, all of the types and functions on which it
depends are also exported. Iterators cannot be explicitly exported.
.. _Interop_Argument_Passing:
Argument Passing
~~~~~~~~~~~~~~~~
The manner in which arguments are passed to an external function can be
controlled using argument intents. The following table shows the
correspondence between Chapel intents and C argument type declarations.
These correspondences pertain to both imported and exported function
signatures.
======= =======
Chapel C
======= =======
T const T
in T T
inout T T\*
out T T\*
ref T T\*
param
type char\*
======= =======
Currently, ``param`` arguments are not allowed in an extern function
declaration, and ``type`` args are passed as a string containing the
name of the actual type being passed. Note that the level of indirection
is changed when passing arguments to a C function using ``inout``,
``out``, or ``ref`` intent. The C code implementing that function must
dereference the argument to extract its value.
.. [1]
For the IEEE 754 format, :math:`mant(32)=24` and :math:`mant(64)=53`.
.. [2]
When converting to a smaller real type, a loss of precision is
*expected*. Therefore, there is no reason to produce a run-time
diagnostic.
.. [3]
This is also known as row-major ordering.
.. [4]
In UNIX-like programming environments, ``nm`` and ``grep`` can be
used to find the mangled name of a given function within an object
file or object library.
.. [5]
However, most implementations have settled on using 8, 16, 32, and 64
bits (respectively) to represent ``char``, ``short``, ``int`` and
``long``, and ``long long`` types
.. [6]
The return type cannot be inferred, since an ``extern`` procedure
declaration has no body.
.. [7]
In practice, this will typically only be useful if the external
function is a macro or built-in that can handle type identifiers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment