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!