Chapel

Chapelは、並列処理に重点を置いた高水準のプログラミング言語です。並列処理を簡単に、安全で、効率的に行うことができるように設計されています。Chapelは、C++、Java、Pythonなどの他のプログラミング言語の影響を受けています。
このチュートリアルでは、Chapelの基礎を学びます。Chapelのインストール方法、基本的な構文、データ型、制御構造、関数、クラスなどについて説明します。また、並列処理の記述方法についても説明します。
このチュートリアルは、Chapelの初心者向けに書かれています。Chapelの経験が豊富な方でも、このチュートリアルから何か新しいことを学ぶことができるかもしれません。
このチュートリアルが、Chapelの基礎を学び、並列処理を記述するための強力なツールを習得するのに役立つことを願っています。
Chapel は、並列処理に重点を置いた高水準のプログラミング言語です。2009年にクレイ・スーパーコンピュータ社によって最初に開発され、それ来、オープンソースプロジェクトとして開発が続けられています。Chapel は、C++、Java、Python などの他のプログラミング言語の影響を受けています。
Chapel の目標は、並列処理を簡単に、安全で、効率的に行うことができる言語を作ることです。そのために、Chapel には並列処理をサポートする多くの機能が用意されています。例えば、並列配列、並列関数、並列タスクなどがあります。Chapel には、並列処理を記述するための多くの高レベルの構文も用意されています。例えば、forall 文、par 文、task 文などがあります。
Chapel は、強力で用途の広いプログラミング言語です。並列処理を簡単に、安全で、効率的に行うことができる言語を探している人には最適です。
Chapelの基本を学ぶのであれば、処理系のインストールは必要なく、Attempt This Onlineなどのオンラインの実行環境をブラウザーから利用することが出来ます。
また、処理系をインストールする場合、DockerのコンテナやHomebrewのパッケージを利用することが出来ます。
そうではなく、ソースコードからビルドする場合は、GitHubでソースコードが公開されています。
Chapel は、コンパイル型言語なので、ソースコードを用意し、コンパイルして出来た実行ファイルを実行します。
Chapel のソースコードの拡張子は、.chpl を使うのが一般的です。
上記の、hello.chpl をエディタで編集し保存し以下のように実行します。
上記の1行からなるソースコードは、なにかの調べ物をするときには便利です。 しかし、このまま機能を加えていくと、どこからプログラムが始まるかなどが、コードが大規模になるとわかりにくくなります。
次のコードは、それらを改修した製品水準のものです。
内容は、山盛りのコメント通りですので、コンパイルして実行してみます。
このようにChapelで作ったプログラムは、コマンドラインのハンドリングが処理系によって予めお膳立てされているので、コマンドラインインターフェイスは自ずと統一されます。 他方、Chapel以外で書かれたプログラムに特有のコマンドラインインターフェイスの作法があった場合、移植に工夫が必要になります。
Chapelでは、変数に値を代入することができます。変数名は英数字とアンダースコア(_)を使用できますが、数字で始めることはできません。 また、大文字と小文字は区別されます。 変数を宣言したときに初期値を与えるには、等号(=)を使用します。以下の例では、変数xを数値10で初期化しています。
変数に代入された値は、後で変更することができます。以下の例では、変数xに数値20を再度代入しています。
以下は、2つ以上の変数を一度に宣言する例です。
このように、コンマで区切って変数を列挙することで、それぞれの変数に対して別々の値を一度に代入することができます。上記の例では、aに1、bに2、cに3が代入されます。
Chapelでは、変数の他に定数の宣言もサポートされています。 定数は初期化された後に値を変更できない変数と考えることができます。 Chapelでは、「実行時定数」と「パラメータ」の2つの種類の定数がサポートされており、それぞれ異なるキーワードで導入されます。
実行時定数は、キーワード const を使って宣言し、実行時に値が確定する定数です。 実行時定数は、キーワード config で修飾すると、実行時のコマンドラインから値を変更することが出来ます。
この例では、gravity という実行時定数を宣言しています(floatと推定されます)。 この実行時定数は、重力加速度を表す実行時定数であり、値は 9.8 であり、後から値を変更することはできません。
パラメータは、キーワード param を使って宣言し、コンパイル時に値が確定している定数です。コンパイル時定数とも呼ばれます。
この例では、debug というパラメータを bool 型と宣言しています。 このパラメータは名前から、デバッグ用のコードの有効無効を切り替えるのに使うと考えられます。 パラメータが、他の言語の条件コンパイルに相当する機能に使えます。
Chapelにおける識別子は、小文字または大文字の英字またはアンダースコア( _ )で始まり、任意の小文字、大文字、数字、アンダースコア( _ )、ドル記号( $ )のシーケンスで続く文字列です。
Charplでは、ドル記号( $ )を識別子の2文字目以降であれば使うことが出来ます。
[TODO:分類と説明の充実]
以下の識別子は、将来の使用のために予約されているキーワードです。
lambda pragma primitive
Chapelには、様々なデータ型があります。その中でプリミティブ型は以下の通りです。
Chapelのプリミティブ型のほとんどは、デフォルトのビット幅で値を指定するための対応するリテラル形式をサポートしています。 他のビット幅の値は、型変換(キャストまたは型強制)を使って取得します。
bool型は、falseとtrueを2つのリテラル値としてサポートします:
Chapelには整数型があります。以下に、Chapelの整数型に関するコード例と解説を示します。
Chapelでは、整数型を使用して数値演算や配列のインデックス指定、制御フローの条件式などを行うことができます。整数型のサイズはビット数で指定することもでき、デフォルトでは int(64) が使用されますが、必要に応じて異なるビット数の整数型を定義することもできます。
int型のリテラル値は、通常、10進数の列として記述されますが、0b/0B、0o/0O、0x/0Xの接頭辞により、2進数、8進数、16進数の値として表現することも可能です:
Chapelでは、負の整数リテラルは存在せず、正の整数リテラルに単項否定演算子(-)を適用するだけであることに注目すべきです。
uint型は独自のリテラル形式を持ちません。しかし、int(64)に収まらないがuint(64)に収まる整数リテラルは、符号なし整数値とみなされます。
したがって、以下の宣言では、最初の変数はint型、2番目の変数はuint型と推定されます。
より小さな整数をuintとして表現するためには、型変換(キャストまたは型強制)を使用する必要があります。
実数型は、10進数または指数型によるリテラル値をサポートしています:
10進数形式では、整数リテラルと区別するために.を使用する必要があります。10.は、Chapelでは有効な浮動小数点リテラル値ではないことに注意してください。 これは、整数値に対してメソッド呼び出しを行う際の構文の曖昧さのためです。このため、代わりに10.0が使用されなければならない。 また、.707は0.707の短縮表記として有効です(特に推奨はしません)。
指数形式では、ベースとなる10進数値を整数値にしたり、大文字のEを使用して指数を指定することができることに注意してください。したがって、次の4つの代入の値はすべて等価である:
Chapelでは、虚数型を表現するためにキーワードimagが使用されます。
複素数型は、リテラル値のネイティブフォーマットをサポートしていません。その代わり、複素数値は通常、実数値と虚数値の加算や減算によって表現される。例えば、以下のようになります:
文字列のリテラル値は、一重引用符または二重引用符を使って表現することができます。例えば、次のような文字列は、合法的な文字列値です:
これによる影響としては、文字列リテラル内で他のタイプの引用符を追加作業なしで使用できることです。
プログラミングを行っていると、ある型の値を別の型に変換する必要が生じます。
などが示されています。特に、キャスト以外にも .type プロパティや writef() といった飛び道具がいきなり出てきましたが、Chapelのプログラミングに頻繁に登場するので、この機会におぼえましょう。
refは、C++の参照と似たような動作をします。 Chapelでは、refは初期化された変数以外の変数とエイリアスを作ることはできません。
同じスコープの変数の参照を作っても、別名が出来るだけであまり楽しいことはありませんが、プロシージャの引数に参照を使うと、文字通り参照渡しが可能となり、呼び出し元の変数に値を返すことが出来ます。
Chapelでは、キーワード enum を使って列挙型(enumeration type)を定義することができます。列挙型は特定の値のセットを表し、それぞれの値に名前を付けることができます。
構造型( Structured Types )には、
があります。

クラスは、変数や定数(フィールド)および関数やイテレータ(メソッド)を含むことができる型です。 新しいクラスは、キーワードclassを使用して宣言されます。
キーワードnewは、コンストラクタを呼び出すことによりクラスのインスタンスを作成します。 上記のクラスPointは、コンストラクタを宣言していないため、コンパイラが生成したコンストラクタが使用されます。 コンパイラが生成したコンストラクタは、クラス内の各フィールドに対する引数を持っています。 クラスが初期化されると、そのメソッドを呼び出すことができます。 クラスには、解放方法を決定するさまざまなメモリ管理戦略があります。 あらためて詳しく説明しますが、今のところ、new Point(...)は、new owned Point(...)と書いたのと同じです。 ownedはこれらのメモリ管理戦略の一つです。
クラスのタイプには、メモリ管理戦略が含まれています。現在サポートされている戦略は、owned、shared、unmanaged、borrowedの4つです。

レコードは、変数や定数(フィールド)だけでなく、関数やイテレータ(メソッド)も含めることができる型です。
レコードはクラスと多くの類似点がありますが、いくつか重要な違いがあります。
レコード型は、キーワードrecordを使用して宣言することができます。

Chapelのユニオン( union )は、複数の異なる型の値を同じメモリ領域で保持する仕組みです。
ここまではCと同じですが、Chapelの union では、有効なフィールドは1つだけです。 どのフィールドが有効かは、最後に代入されたフィールドによってかわります。

データ並列型( data parallel types )には、
があります。
範囲(Range)は整数などの一定の値のシーケンスを表し、通常、下限と上限の境界を定義します。 範囲の値は、.. 演算子を使用して指定することができます。下限は .. の左側に指定し、上限は右側に指定します。
Chapel の Range における align キーワードは、範囲を表す値のシーケンスがそのストライドに対してどのようにアライメントされるかを指定するために使用されます。 1 または -1 以外のストライドを持つ範囲では、アライメントは重要です。たとえば、ストライドが 2 の範囲は 2 の倍数であるインデックスのみをサポートします。範囲の値のシーケンスがストライドに対して整列していない場合、範囲は空になります。

ドメインは、イテレーション空間の指定、配列の定義、スライスなどの集約操作を定義するために使用されるインデックス集合の一級表現です。
ドメインマップ(Domain Maps)は、Chapelプログラムでドメインと配列のマッピング方法を指定するために使用されます。ドメインマップに関連するキーワードとその説明をコードを交えて解説します。
ドメイン代入は、ドメインのインデックスセットを右辺の式から左辺のドメインに転送する操作です。以下のコード例では、ドメイン代入が行われています。
Chapelでは、配列は配列のインデックスセットを指定する角かっこで囲まれた式によって指定されます。 その後に配列の要素の型が続きます。矩形配列は、インデックスが整数または整数のタプルであり、標準の多次元、直交座標のインデックスセットをサポートします。
配列の添字(=ドメイン)には、文字列など整数でない型を用いることも出来ます。
配列と結びついたドメインを動的に変更することで連想配列に近いことが出来ます。
同期型( synchronization types )には、
があります。
Chapelでは、多くの種類の式と演算子がサポートされています。ここでは、最もよく使用される式と演算子のいくつかについて説明します。
数学演算子には、加算、減算、乗算、除算、べき乗、剰余があります。次の例は、これらの演算子の使用方法を示しています。
論理演算子には、論理積、論理和、論理否定があります。次の例は、これらの演算子の使用方法を示しています。
関係演算子には、大なり、大なりまたは等しい、小なり、小なりまたは等しい、等しい、異なるが含まれています。次の例は、これらの演算子の使用方法を示しています。
ビット演算子には、左シフト、右シフト、ビット否定、ビット排他的論理和が含まれています。次の例は、これらの演算子の使用方法を示しています。
複合代入演算子には、加算代入、乗算代入、除算代入、べき乗代入、左シフト代入、右シフト代入が含まれています。次の例は、これらの演算子の使用方法を示しています。
交換演算子には、値の交換を行う```<=>`演算子が含まれています。次の例は、この演算子の使用方法を示しています。
Chapelも、多くのプログラミング言語と同じく、分岐と反復の制御構造を持ちます。
Chapelには、ifとselectの2つの分岐構文があります。
ifは、条件式に基づき分岐し、分岐先を評価します。 ifの値は、分岐先の式の値です。
if は、値を返すのでif文ではなくif式とも言えますが、値を使わない場合はelseを省略できます。 値を使うにelseを省略すると Syntax error となります。
thenが突く場合とブロック文が続く場合があり、then を使うとPythonの様なインデント記法に見えますが、インデントは見栄えだけのものです。
select は、1つの式を評価してそれと一致する条件に対応する文を実行します。
when の式リストには、範囲やパターンマッチングは書けないので、使い勝手はよくありませんので、多くの場合はifやマッピングを使うことになるでしょう。
また、列挙型( enum )とselect文を組み合わせて使うと、コードがより明確になります。この場合は、ifよりも好ましい選択です。

while-do文は、式が真である場合、続く文あるいはブロック文を繰り返します。
whileはfor系に比べて使い所が少ないですが、ファイル読み込みが典型的です。
このコードは、ファイル /etc/passwd の内容を読み込み、標準出力に表示します。

do-while文は、文あるいはブロック文をまず実行し、式が真であれば繰り返します。

forループは範囲(range)、ドメイン(domain)、配列(array)、イテレータ(iterator)、または反復可能な任意のクラスを反復処理します。
キーワードzipを使用して複数の反復対象式をループ内でトラバースする場合、各反復対象が生成する対応する式は、ループのインデックス変数であるタプルとして結合されます。これを連結反復処理と呼びます。 zip()式の最初の反復対象は、ループの反復をリードし、反復空間のサイズと形状を決定します。その後の式はリード反復対象に従います。 これらのフォロワー反復対象は、リード反復対象によって生成される値の数と形状に適合することが期待されます。 たとえば、最初の反復対象がm行n列の2次元配列である場合、後続の反復対象も2次元m×n空間での反復をサポートする必要があります。
パラメータ付きforループでは、インデックス変数が変数ではなくパラメータとして展開されます。

並列処理を制御構造に1つに含めるのは奇異に感じられるかもしれません。 ここでは、for文の構文の延長にある forall cofaorall と foreach について扱います。
forallループは範囲(range)、ドメイン(domain)、配列(array)、イテレータ(iterator)、または反復可能な任意のクラスを反復処理します。 ここまでは、forループと同じですが、forループが単一のタスクによって直列に実行されるのに対し、forallループは任意の数のタスクを使用してループを実行することができます。 その結果、単一のタスクを使用して直列に実行される場合もありますし、反復の数に応じて任意の数のタスクを使用することもできます
coforall文は、関連する同種のタスクを任意の数作成するために使用できます。構文的には、forループ文と似ていますが、forの代わりにcoforallキーワードを使用します。 動作上、coforallループは、ループの各反復ごとに独立したタスクを作成し、それぞれがループ本体のコピーを実行します。助記的には、coforallループはconcurrent forallと考えることができます。つまり、各反復が並行タスクである並列ループです。 cobegin文と同様に、元のタスクは、coforallの反復に対応する子タスクが完了するまで進行しません。そして、cobeginと同様に、元のタスクはその直接の子供だけを待ちます。子孫は待ちません。
foreachループは、ループの反復が順序に依存しないことを示します。 これにより、コンパイラはループをベクトル化したり、実行をGPUにオフロードしたりすることができます。 対照的に、forループは順序に依存し、forallループはイテレータに基づいて並列タスクを作成し、反復を分散することができます。
手続き( procedure )は、キーワード proc を使って定義します。
イテレータ( Iterators )は、キーワード iter を使って定義します。

この例では、MyIteratorというイテレータを定義しています。イテレータは、startからendまでの整数を順に生成します。
MyIteratorの定義では、iterキーワードを使用してイテレータを宣言しています。イテレータの本体はwhileループで構成されており、currentの値をイテレータとして返すたびに、yieldステートメントを使用してその値を生成します。そして、currentを1増やし、次の値を生成するための準備をします。
イテレータを使用するために、MyIteratorを呼び出してmyIterという変数に代入しています。その後、forループを使用して、myIterからイテレータが生成する値を順番に取得し、writeln関数を使って出力しています。
基本的なアルゴリズムが共有する手続きとイテレータを比較してみましょう。
:* このアルゴリズムの主役は、primesというリストです。 :* primes は名前の通り素数のリストで、その中に因数がない数が素数で primes 自身に追加します。
:* このアルゴリズムの主役は、primesというリストです。 :* primes は名前の通り素数のリストで、その中に因数がない数が素数で primes 自身に追加します。

演算子メソッド( Operator Methods )は、キーワード operator を使って定義します。
演算子メソッドは、Chapelで演算子の振る舞いを定義するために使用されます。これにより、特定の型に対して演算子をオーバーロードすることができます。
演算子メソッドは、次の構文で定義されます。
上記の構文で、<operatorSymbol>は演算子を表すシンボル(例: +, -, *, /)を指定します。lhsとrhsは同じ型Tの引数であり、演算子の左辺と右辺の値を表します。
モジュールはコードのグループ化であり、各プログラムは少なくとも1つのモジュールから構成され、すべてのシンボルはあるモジュールに関連付けられます。 ファイルのすべてのコードがmoduleキーワードを使用して明示的なモジュールで囲まれていない場合、ファイル自体はファイル名(.chpl拡張子を除く)と同じ名前のモジュールとして扱われます。 コンパイラは、コマンドラインでモジュールの名前を指定するか、-Mフラグに依存することで、異なるファイルにモジュールを含めるように指示することができます。

複数のモジュールを持つプログラムでは、モジュールが他のモジュールの内容にアクセスすることが一般的です。そのための出発点は、Chapelのuse文またはimport文です。これらの文は、実行可能なコードを含む任意のレキシカルスコープに挿入することができます。
デフォルトでは、use文は特定のモジュールのすべての可視シンボルを、そのuse文を含むスコープで利用可能にします。これらのシンボルは、モジュール名の接頭辞なしで直接アクセスすることができます。



Chapelにおける例外処理は、通常のプログラミング言語とは異なる独自の機構を持っています。 Chapelでは、並列処理をサポートするために非同期タスクと呼ばれる並列実行単位があります。 非同期タスク内で例外が発生した場合、その例外をキャッチするためのメカニズムが提供されています。
Chapelでは、ジェネリックプログラミングのサポートがあります。 ジェネリックプログラミングは、異なるデータ型に対して汎用的なアルゴリズムやデータ構造を定義するためのテクニックです。 Chapelでは、型パラメータを使用してジェネリックなコードを記述することができます。

エラトステネスの篩を、若干 Chapel らしく書いてみました。
クイックソートを、若干 Chapel らしく書いてみました。
最大公約数と最小公倍数を、若干 Chapel らしく書いてみました。
二分法を、若干 Chapel らしく書いてみました。
順列を、若干 Chapel らしく書いてみました。
組み合わせを、若干 Chapel らしく書いてみました。
Chapel-1.30 の構文です。