5 関数
Chapelの関数には、手続きとイテレータと演算子の3種類が存在し、予約語のprocとiterとoperatorで定義できる。
記事では、単に手続きを指して関数と呼ぶ。関数は、first-classで、テンプレートの機能を持ち、例外処理も可能である。
5.1 定義
以下の関数fooは、int型の引数xとyを取り、return文でint型の値を返す。関数の呼び方は、C言語と同様である。
proc foo(x: int, y: int): int {
return x + y;
}
writeln(foo(1, 2));
引数や返り値の型は、省略できる。この場合の関数は、実質的にテンプレート関数であり、型は、実引数から推論される。
proc foo(x, y) return x + y;
writeln(foo(1, 2i)); // 1.0 + 2.0i
writeln(foo(2i, 3)); // 3.0 + 2.0i
引数が0個の場合は、引数の括弧を省略できる。また、内容がreturn文だけの関数は、処理を囲む括弧{}
を省略できる。
proc foo return 100110;
writeln(foo);
関数に引数を渡す際に、引数の名前を指定できる。また、引数を省略した場合に渡されるデフォルトの値を設定できる。
proc foo(x: int = 0, y: int = 0): int return x + y;
writeln(foo(x = 1, y = 2)); // 3
writeln(foo(y = 2, x = 1)); // 3
writeln(foo(1)); // 1
関数は、可変長の引数を宣言できる。その実体はタプルである。また、タプルを渡す場合は、展開の演算子...を使う。
proc sum(x: int ...): int return + reduce(x);
writeln(sum(1, 2, 3) + sum((...(100, 200)))); // 306
関数は、他の関数の内側にも定義できる。関数の外側から見ると、内側の関数は秘匿される。また、高階関数にもできる。
proc factorial(num: int): int {
proc tc(n, accum: int): int {
if n == 0 then return accum;
return tc(n - 1, n * accum);
}
return tc(num, 1);
}
writeln(factorial(10)); // 3628800
例えば、関数を関数の引数や変数に代入できる。また、関数の名前が必要なければ、lambdaで無名の関数を定義できる。
proc call(f, x: int, y: int): int return f(x, y);
const add = lambda(x: int, y: int) return x + y;;
writeln(call(add: func(int, int, int), 36, 514)); // 550
5.2 修飾子
inlineで宣言された関数は、インライン展開される。exportで宣言された関数は、ライブラリ関数として公開される。
inline proc foo(x: int): int return 2 * x;
export proc bar(x: int): int return 2 * x;
writeln(foo(100)); // 200
writeln(bar(300)); // 600
共有ライブラリで実装された関数を使う場合は、externで関数を宣言する。以下に、CPUの番号を取得する例を示す。
require "sched.h";
extern proc sched_getcpu(): int;
writeln("CPU:", sched_getcpu());
引数や返り値にも、修飾子を指定できる。paramやtypeで宣言された引数は、定数や型となる。返り値も同様にできる。
proc tuplet(param dim: int, type eltType) type return dim * eltType;
const septet: tuplet(7, int) = (114, 514, 1919, 810, 364, 889, 464);
inoutやoutで宣言された引数の値は、関数から戻る際に書き戻される。ただし、outの場合は、実引数が無視される。
proc intents(inout x: int, in y: int, out z: int, ref v: int): void {
x += y;
z += y;
v += y;
}
var a: int = 1;
var b: int = 2;
var c: int = 3;
var d: int = 4;
intents(a, b, c, d);
writeln(a, b, c, d); // 3226
refで宣言された引数は、参照渡しになる。また、返り値がrefの関数は、左辺値を返すので、代入の構文を定義できる。
var tuple = (0, 0, 0, 0, 0, 0, 0, 0, 0);
proc A(i: int) ref: int return tuple(i);
coforall i in tuple.indices do A(i) = i;
writeln(tuple);
5.3 条件分岐
Chapelの関数は、実質的にテンプレート関数である。関数がインスタンスになる条件は、where節で詳細に指定できる。
where節は、コンパイル時に評価される。例えば、以下のwhichType関数は、引数xの型xtに応じて、内容が変化する。
proc whichType(x: ?xt): string where xt == real return "x is real";
proc whichType(x: ?xt): string where xt == imag return "x is imag";
proc whichType(x: ?xt): string return "x is neither real nor imag";
writeln(whichType(114.514));
writeln(whichType(364364i));
writeln(whichType(1919810));
where節の条件分岐は、任意の定数式を扱えるので、コンパイル時計算に利用できる。以下は、階乗を計算する例である。
proc fact(param n: int) param: int where n >= 1 return n * fact(n-1);
proc fact(param n: int) param: int where n == 0 return 1;
if fact(8) != 5040 then compilerError("fact(7) == 5040");
5.4 型引数
任意の値を受け取る引数に、?を前置した型引数を宣言すると、型を取得できる。以下に、配列の型を取得する例を示す。
ただし、型引数を宣言せずに、.typeや.domainや.eltTypeなどのクエリ式を活用し、型の詳細を取得する方法もある。
proc foo(x: [?dom] ?el) return (dom, el: string);
proc bar(x) return (x.domain, x.eltType: string);
writeln(foo([1, 2, 3, 4, 5, 6, 7, 8])); // ({0..7}, int(64))
writeln(bar([1, 2, 3, 4, 5, 6, 7, 8])); // ({0..7}, int(64))
writeln(foo(["one" => 1, "two" => 2])); // ({one, two}, int(64))
writeln(bar(["one" => 1, "two" => 2])); // ({one, two}, int(64))
5.5 例外処理
throw文は、異常の発生を通知する。この異常を例外と呼ぶ。catch文で捕捉されるまで、関数が順繰りに巻き戻される。
例外が発生し得る関数は、throws宣言が必要である。また、defer文で予約した処理は、例外が発生しても実行される。
proc foo(message: string) throws {
defer writeln("See you");
throw new Error(message);
}
catch文は、try文の内側で例外が発生した場合には、その例外を捕捉し、回復処理を行う役割がある。以下に例を示す。
try {
foo("Hello,");
foo("world!");
} catch e {
writeln(e);
}
5.6 演算子
operatorで宣言された関数は、演算子の機能を再定義する。ただし、第2章に掲載した演算子に限る。以下に例を示す。
operator *(text: string, num: int) return + reduce (for 1..num do text);
writeln("Lorem ipsum dolor sit amet, consectetur adipiscing elit" * 10);
5.7 イテレータ
iterで宣言された関数は、イテレータとなる。yield文は、処理を断ち、指定された値を返し、残りの処理を再開する。
theseを実装した構造体も、イテレータとして機能する。for文に渡すと、暗黙的にtheseが呼ばれる。以下に例を示す。
iter iterator(): string {
yield "EMURATED";
yield "EMURATED";
yield "EMURATED";
}
iter int.these() const ref: int {
for i in 1..this do yield i;
}
var repetition: int = 10;
for i in iterator() do writeln(i);
for i in repetition do writeln(i);