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によって翻訳されています。表現に違和感がある場合は、原文(英語)を参照するか、翻訳にご協力ください。

トレイト(Trait)は型クラスとしても知られており、抽象化とモジュール性を支える仕組みです。Flix のトレイトシステムは Haskell や Rust のものと似ていますが、同一ではありません。Flix のトレイトは、関連型、関連エフェクト、高カインド型をサポートしています。

例を使ってトレイトを説明します。

Option[Int32] に対する等価性は、次のように定義できます:

def equals(x: Option[Int32], y: Option[Int32]): Bool = 
    match (x, y) {
        case (None, None)         => true
        case (Some(v1), Some(v2)) => v1 == v2
        case _                    => false
    }

同様に、List[Int32] に対する等価性も次のように定義できます:

def equals(x: List[Int32], y: List[Int32]): Bool = 
    match (x, y) {
        case (Nil, Nil)           => true
        case (v1 :: xs, v2 :: ys) => v1 == v2 and equals(xs, ys)
        case _                    => false
    }

しかし、等価性をサポートするデータ型に対する共通の抽象化が欲しい場合はどうすればよいでしょうか?

ここでトレイトの出番です。Equatable トレイトを次のように定義できます:

trait Equatable[t] {
    pub def equals(x: t, y: t): Bool
}

このトレイトは、単一の equals というトレイトシグネチャを持ちます。このトレイトは型パラメータ t について多相的であるため、Option[t]List[t] の両方に対して Equatable を実装できます:

instance Equatable[Option[t]] with Equatable[t] {
    pub def equals(x: Option[t], y: Option[t]): Bool = 
        match (x, y) {
            case (None, None)         => true
            case (Some(v1), Some(v2)) => Equatable.equals(v1, v2)
            case _                    => false
        }
}

ここで注目してほしいのは、EquatableOption[Int32] に対して実装したのではなく、t 自体が等価比較可能である限り任意の Option[t] に対して実装したという点です。さらに、v1v2== で直接比較する代わりに、それらに対して Equatable.equals を呼び出しています。

List[t] に対しても Equatable を実装できます:

instance Equatable[List[t]] with Equatable[t] {
    pub def equals(x: List[t], y: List[t]): Bool = 
        use Equatable.equals;
        match (x, y) {
            case (Nil, Nil)           => true
            case (v1 :: xs, v2 :: ys) => equals(v1, v2) and equals(xs, ys)
            case _                    => false
        }
}

Int32 に対しても Equatable を実装したと仮定すると、Equatable を使って 2 つの Option[Int32] 値が等しいかどうかを計算できます。それだけでなく、2 つの Option[List[Int32]] 値が等しいかどうかも計算できるのです!これは抽象化の力を示しています。Option[t]List[t] に対するインスタンス(instance)を一度実装すれば、それらのインスタンスをあらゆる場所で再利用できます。

新しく定義した Equatable トレイトを使って、多相的な関数を書くことができます。

例えば、ある要素がリストに含まれているかどうかを計算する関数を定義できます:

def memberOf(x: t, l: List[t]): Bool with Equatable[t] = 
    match l {
        case Nil     => false
        case y :: ys => Equatable.equals(x, y) or memberOf(x, ys)
    }

要素の型が Equatable を実装してさえいれば、任意の型のリストに対して memberOf を使うことができます。

注意: Flix 標準ライブラリでは、Equatable トレイトは Eq という名前です。さらに、== 演算子はトレイトシグネチャ Eq.eq の糖衣構文です。

Sealed トレイト

トレイトを sealed として宣言すると、そのトレイトを誰が実装できるかを制限できます。

例えば:

mod Zoo {
    sealed trait Animal[a] {
        pub def isMammal(x: a): Bool
    }

    instance Animal[Giraffe] {
        pub def isMammal(_: Giraffe): Bool = true
    }

    instance Animal[Penguin] {
        pub def isMammal(_: Penguin): Bool = false
    }

    pub enum Giraffe
    pub enum Penguin
}

ここでは AnimalGiraffe のインスタンスを実装できます。それらが Animal トレイトと同じモジュール内に存在するからです。しかし、Zoo モジュールの外側から Animal を実装することはできません。試しに次のように書くと:

mod Lake {
    pub enum Swan

    instance Zoo.Animal[Swan] {
        pub def isMammal(_: Swan): Bool = false
    }
}

Flix は次のように報告します:

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

>> Trait 'Zoo.Animal' is sealed from the module 'Lake'.

21 |     instance Zoo.Animal[Swan] {
                  ^^^^^^^^^^
                  sealed trait.

不正な形のトレイト

トレイトは C# や Java スタイルのインターフェースではありません。具体的には:

  • すべてのトレイトはちょうど 1 つの型パラメータを持たなければならず、
  • すべてのシグネチャはその型パラメータに言及しなければなりません。

例えば、次のトレイトは正しくありません:

trait Animal[a] {
    pub def isMammal(x: a): Bool      // OK     -- a に言及している。
    pub def numberOfGiraffes(): Int32 // NG     -- a に言及していない。
}

上記のトレイトをコンパイルすると、Flix は次のように報告します:

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

>> Unexpected signature 'numberOfGiraffes' which does not mention the type 
>> variable of the trait.

7 |     pub def numberOfGiraffes(): Int32 
                ^^^^^^^^^^^
                unexpected signature.

問題は、numberOfGiraffes のシグネチャが型パラメータ a に言及していないことです。

複雑なインスタンス

トレイトのインスタンスは、次の条件を満たす型に対して定義しなければなりません:

  • ちょうど 1 つの型コンストラクタであり、
  • それが 0 個以上の相異なる型変数に適用されていること。

例えば、先ほどの Equatable トレイトがあるとします:

trait Equatable[t] {
    pub def equals(x: t, y: t): Bool
}

次のような型に対してはインスタンスを実装できます:

  • Option[a]
  • List[a]
  • (a, b)

しかし、次のような型に対してはインスタンスを実装できません

  • Option[Int32]
  • List[String]
  • (a, Bool)
  • Map[Int32, v]

例えば List[Int32] に対してインスタンスを実装しようとすると、Flix は次のように報告します:

❌ -- Instance Error -------------------------------------------------- 

>> Complex instance type 'List[Int32]' in 'Equatable'.

6 | instance Equatable[List[Int32]] {
             ^^^^^^^^^
             complex instance type

An instance type must be a type constructor applied to zero or more 
distinct type variables.

重複するインスタンス

重複する型に対して、同じトレイトのインスタンスを 2 つ実装することはできません。

例えば、List[t] に対して Equatable のインスタンスを 2 つ実装しようとすると:

instance Equatable[List[t]] {
    pub def equals(x: List[t], y: List[t]): Bool = ???
}

instance Equatable[List[t]] {
    pub def equals(x: List[t], y: List[t]): Bool = ???
}

Flix は次のように報告します:

❌ -- Instance Error -------------------------------------------------- 

>> Overlapping instances for 'Equatable'.

1 | instance Equatable[List[t]] {
              ^^^^^^^^^
              the first instance was declared here.

4 | instance Equatable[List[t]] {
             ^^^^^^^^^
             the second instance was declared here.