6 構造体
Chapelでは、構造体を定義して、構造体に変数や関数を定義できる。変数をフィールドと呼び、関数をメソッドと呼ぶ。
構造体は、クラスとレコードと共用体に分類できる。クラスは参照型の性質を、レコードと共用体は値型の性質を持つ。
6.1 定義
以下に、クラスを定義して、new演算子でインスタンスを生成する例を示す。参照型なので、変数には参照が格納される。
class Num {
var r: real;
var i: imag;
}
var num: Num = new Num();
num.r = 816.07;
num.i = 14.22i;
writeln(num.r);
以下に、レコードを定義する例を示す。クラスと似た機能だが、値型なので、インスタンスの複製が変数に格納される。
record Num {
var r: real;
var i: imag;
}
var num: Num;
num.r = 816.07;
num.i = 14.22i;
writeln(num.r);
共用体は、同じメモリ領域を複数のフィールドで共有する。最後に値を格納したフィールドのみ、意味のある値を持つ。
union Num {
var r: real;
var i: imag;
}
var num: Num;
num.r = 816.07;
num.i = 14.22i;
writeln(num.i);
6.2 総称型
総称型は、型引数や静的定数を引数に取る多相型である。型引数が異なると、異なる型として扱われる。以下に例を示す。
record Stack {
type eltType;
param size: int;
}
var a: Stack(eltType = uint, size = 12);
var b: Stack(eltType = real, size = 24);
writeln("a is ", a.type: string); // a is Stack(uint(64), 12)
writeln("b is ", b.type: string); // b is Stack(real(64), 24)
6.3 メソッド
構造体には、メソッドを定義できる。また、メソッドでインスタンスを参照する場合は、thisを使う。以下に例を示す。
record User {
var name: string;
proc set(name) {
this.name = name;
}
}
var user: User;
user.set("Alicia");
writeln(user.name);
なお、値型の性質を持つレコードでも、thisはインスタンスへの参照であり、意図通りにフィールドの値を変更できる。
既存の構造体にも、メソッドを追加できる。また、フィールドと同名で、参照と代入を隠蔽する、アクセサを定義できる。
record User {
var name: string;
}
proc User.name ref {
writeln(".name");
return this.name;
}
var user: User;
user.name = "Jane";
writeln(user.name);
thisメソッドが定義された構造体は、関数と同様に振る舞う。引数を与えると、暗黙的にthisメソッドが実行される。
class Add {
proc this(a: int, b: int): int return a + b;
}
const add = new Add();
writeln(add(123, 45)); // 168
initメソッドが定義された構造体は、引数を与えてインスタンスを生成すると、暗黙的にinitメソッドが実行される。
initメソッドは、フィールドを初期化できる。定数の場合は、initメソッドで初期化する必要がある。以下に例を示す。
class Add {
const x: real;
const y: real;
proc init(x, y) {
this.x = x;
this.y = y;
}
}
const add = new Add(x = 1, y = 2);
writeln("x, y: ", (add.x, add.y));
initメソッドは、省略しても自動的に定義される。また、deinitメソッドは、インスタンスを解放する際に実行される。
class Sub {
const x: real = 0;
const y: real = 0;
proc deinit() {
writeln("deleted");
}
}
const sub = new Sub(x = 1, y = 2);
writeln("x, y: ", (sub.x, sub.y));
6.4 所有権
Chapelのクラスには、所有権の概念がある。所有権を持つ変数の参照が消滅したインスタンスは、自動的に解放される。
所有権は、変数宣言で指定する。無修飾またはownedで修飾した場合は、その変数がインスタンスの所有権を独占する。
class Hello {
proc deinit() {
writeln("See you");
}
}
var hello: owned Hello = new owned Hello();
var hallo: borrowed Hello = hello.borrow();
borrowedで修飾された変数は、所有権を取得せず、他のownedやsharedの変数が所有するインスタンスを参照できる。
sharedで修飾した場合は、変数の間でインスタンスが共有される。全ての参照が消滅するとインスタンスが解放される。
class Hello {
proc deinit() {
writeln("See you");
}
}
var hello = new shared Hello();
var hallo = new unmanaged Hello();
delete hallo;
unmanagedで修飾した場合は、所有権による管理を受けず、delete文で、明示的にインスタンスを解放する必要がある。
変数に代入すると、所有権を移譲できる。ownedの場合は、所有権が左辺値に移り、sharedの場合は、単に共有される。
6.5 継承
クラスを定義する際に、親クラスを指定すると、その親クラスのフィールドやメソッドを継承できる。以下に例を示す。
class Foo {
proc foo() return "foo!";
}
class Bar: Foo {
override proc foo() return super.foo() + "bar!";
}
const foo = new Foo();
const bar = new Bar();
writeln(foo.foo()); // foo!
writeln(bar.foo()); // foo!bar!
継承したメソッドの内容を再定義する場合は、override宣言を行う。再定義する前のメソッドは、superで参照できる。
6.6 構造的部分型
以下に示すvoice関数は、quackメソッドが定義された、任意の型の値を引数に取る。これをダックタイピングと呼ぶ。
class Duck {
proc quack() return "quack!";
}
class Kamo {
proc quack() return "quack!";
}
proc voice(x) return x.quack();
writeln(voice(new Duck())); // quack!
writeln(voice(new Kamo())); // quack!