Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

構造体

💡 お知らせ: このドキュメントはAIによって翻訳されています。表現に違和感がある場合は、原文(英語)を参照するか、翻訳にご協力ください。

Flix は、ミュータブル(mutable)で スコープ付き(scoped) な構造体(struct)を サポートしています。構造体は、ユーザーが定義したフィールドの並びです。 フィールドはデフォルトではイミュータブル(immutable)ですが、mut 修飾子を 付けることでミュータブルにできます。Flix のすべてのミュータブルなメモリと同様に、 すべての構造体は何らかのリージョン(region)に属していなければなりません。

構造体は、イミュータブルな拡張可能レコード(extensible record)に対する、 ミュータブルな代替手段です。

構造体のフィールドは非ボックス化(unboxed)されています。すなわち、プリミティブ型が 間接参照(indirection)を引き起こしません。そのため構造体はメモリ効率の良い データ構造であり、より高水準なミュータブルなデータ構造(たとえばミュータブルなリスト、 スタック、キューなど)を実装するために利用できます。

Flix は、構造体を扱うために 3 つの操作をサポートしています。

  • new Struct @ rc { ... } による、リージョン内での構造体インスタンスの生成。
  • struct->field による、構造体のフィールドへのアクセス。
  • struct->field = ... による、ミュータブル なフィールドの更新。

それぞれの操作は、その構造体が属するリージョンにおいてエフェクトを持ちます。

構造体の宣言

構造体は、同じ名前のモジュール——そのコンパニオン——の 内部で宣言します。たとえば:

mod Person {
    pub struct Person[r] {
        name: String,
        mut age: Int32,
        mut height: Int32
    }
}

ここでは、nameageheight という 3 つのフィールドを持つ構造体を 宣言しています。name フィールドはイミュータブルであり、構造体インスタンスが 生成された後に変更することはできません。ageheight のフィールドは ミュータブルなので、生成後に変更できます。Person 構造体は、r という 1 つの型パラメータを持ち、 これは構造体が属するリージョンを指定します。

すべての構造体はリージョンの型パラメータを持たなければならず、それは型パラメータの リストの最後になければなりません。

構造体の生成

Person 構造体のインスタンスは、次のように生成できます。

mod Person {
    pub def mkLuckyLuke(rc: Region[r]): Person[r] \ r =
        new Person @ rc { name = "Lucky Luke", age = 30, height = 185 }
}

mkLuckyLuke 関数は、構造体に関連付けるリージョンケイパビリティ(capability) rc という 1 つの引数を取ります。

次の構文:

new Person @ rc { name = "Lucky Luke", age = 30, height = 185 }

は、構造体 Person の新しいインスタンスをリージョン rc 内に生成することを 指定します。続いて、構造体の各フィールドの値を指定します。構造体のすべての フィールドは、即座にかつ明示的に初期化しなければなりません。

フィールドの読み書き

構造体のフィールドは、フィールドアクセス演算子 -> を使って読み書きできます。 たとえば:

mod Person {
    pub def birthday(p: Person[r]): Unit \ r =
        p->age = p->age + 1;
        if(p->age < 18) {
            p->height = p->height + 10
        } else {
            ()
        }
}

birthday 関数は Person 構造体 p を受け取り、その age フィールドと height フィールドを変更(mutate)します。

たとえば、次の行:

p->age = p->age + 1;

では、p->age で現在の age を取得し、それをインクリメントして、その結果を age フィールドに書き戻しています。

構造体のフィールドアクセス演算子 -> と、関数の矢印   ->   は区別しなければなりません。前者は前後に スペースがなく、後者は両側にスペースを置きます。まとめると:

  • s->f:構造体 s のフィールド f へのフィールドアクセスです。
  • x -> x:仮引数 x から変数式 x への関数です。

フィールドの可視性

Flix では、構造体のフィールドはそのコンパニオンモジュール(companion module)の 内部からのみ可視です。これは、コンパイラによって強制されるカプセル化 (encapsulation)の一形態と考えることができます。

たとえば、次のように書くと:

mod Point {
    pub struct Point[r] {
        x: Int32,
        y: Int32
    }
}

def area(p: Point[r]): Int32 \ r = 
    p->x * p->y

Flix コンパイラは 2 つのエラーを出力します。

❌ -- Resolution Error -------------------------------------------------- 

>> Undefined struct field 'x'.

7 |     p->x * p->y
           ^
           undefined field

❌ -- Resolution Error -------------------------------------------------- 

>> Undefined struct field 'y'.

7 |     p->x * p->y
                  ^
                  undefined field

代わりに、area 関数はコンパニオンモジュールの 内部 で定義すべきです。

mod Point {
    pub struct Point[r] {
        x: Int32,
        y: Int32
    }

    pub def area(p: Point[r]): Int32 \ r = 
        p->x * p->y
}

構造体のフィールドへのアクセスをコンパニオンモジュールの外部から提供したい 場合は、明示的なゲッター(getter)とセッター(setter)を導入できます。 たとえば:

mod Point {
    pub def getX(p: Point[r]): Int32 \ r = p->x
    pub def getY(p: Point[r]): Int32 \ r = p->y
}

このように、構造体のフィールドへのアクセスは厳密に制御されます。

イミュータブルなフィールドとミュータブルなフィールド

Flix では、構造体のすべてのフィールドはイミュータブルかミュータブルのいずれかです。 ミュータブルなフィールドには mut 修飾子を付けなければなりません。そうでない場合、 フィールドはデフォルトでイミュータブルとなり、すなわち構造体インスタンスが生成された 後はそのフィールドの値を変更できません。

たとえば、ユーザーを表す構造体を次のように定義できます。

mod User {
    pub struct User[r] {
        id: Int32,
        mut name: String,
        mut email: String
    }
}

ここで識別子 id はイミュータブルであり変更できませんが、nameemail の フィールドは構造体インスタンスの生存期間を通じて変更できます。

イミュータブルなフィールドを変更しようとすると:

mod User {
    pub def changeId(u: User[r]): Unit \ r =
        u->id = 0
}

Flix コンパイラはエラーを出力します。

❌ -- Resolution Error -------------------------------------------------- 

>> Modification of immutable field 'id' on User'.

9 |         u->id = 0
               ^^
               immutable field

Mark the field as 'mut' in the declaration of the struct.

フィールドの immutability(不変性)は 推移的ではない ことに注意してください。

たとえば、次の構造体を定義できます。

mod Book {
    pub struct Book[r] {
        title: String,
        authors: MutList[String, r]
    }
}

ここで authors フィールドはイミュータブルです。

しかし、MutList は変更できるので、次のように書けます。

mod Book {
    pub def addAuthor(a: String, b: Book[r]): Unit \ r =
        MutList.push(a, b->authors)
}

ここでは、構造体のフィールドを変更しているのではありません。その下にある ミュータブルなリストを変更しているのです。

再帰的な構造体と多相的な構造体

再帰的(recursive)かつ多相的(polymorphic)な、二分探索木のための構造体を 定義できます。

mod Tree {
    pub struct Tree[k, v, r] {
        key: k,
        mut value: v,
        mut left: Option[Tree[k, v, r]],
        mut right: Option[Tree[k, v, r]]
    }
}

Tree[k, v, r] がソート済みであると仮定すると、search 関数を次のように 定義できます。

mod Tree {
    // 木 `t` から指定されたキー `k` を探索する関数。
    pub def search(k: k, t: Tree[k, v, r]): Option[v] \ r with Order[k] =
        match (Order.compare(k, t->key)) {
            case Comparison.EqualTo  => Some(t->value)
            case Comparison.LessThan =>
                // 左の部分木を探索します。
                match t->left {
                    case None            => None
                    case Some(leftTree)  => search(k, leftTree)
                }
            case Comparison.GreaterThan =>
                // 右の部分木を探索します。
                match t->right {
                    case None            => None
                    case Some(rightTree) => search(k, rightTree)
                }
        }
}