幽霊型

badge

幽霊型は、コンパイラに注釈を与えるためだけに存在するマーカートレイトである。 幽霊型の使い方として、リストの構成をみる。

Nil = Class()
List T, 0 = Inherit Nil
List T, N: Nat = Class {head = T; rest = List(T, N-1)}

このコードはエラーとなる。

3 | List T, 0 = Inherit Nil
                        ^^^
TypeConstructionError: since Nil does not have a parameter T, it is not possible to construct List(T, 0) with Nil
hint: use 'Phantom' trait to consume T

このエラーはつまり、List(_, 0).new Nil.new()とされたときにTの型推論ができないという文句である。Ergでは型引数を未使用のままにすることができないのである。 このような場合は何でもよいのでT型を右辺で消費する必要がある。サイズが0の型、例えば長さ0のタプルならば実行時のオーバーヘッドもなく都合がよい。

Nil T = Class((T; 0))
List T, 0 = Inherit Nil T
List T, N: Nat = Class {head = T; rest = List(T, N-1)}

このコードはコンパイルを通る。だが少しトリッキーで意図が分かりづらい上に、型引数が型のとき以外では使えない。

このようなときにちょうどよいのが幽霊型である。幽霊型はサイズ0の型を一般化した型である。

Nil T = Class(Impl := Phantom T)
List T, 0 = Inherit Nil T
List T, N: Nat = Class {head = T; rest = List(T, N-1)}

nil = Nil(Int).new()
assert nil.__size__ == 0

PhantomT型を保持する。しかし実際にはPhantom T型のサイズは0であり、T型のオブジェクトを保持してはいない。

また、Phantomは型以外にも任意の型引数を消費することができる。以下の例ではStateというStrのサブタイプオブジェクトである型引数をPhantomが保持している。 この場合も、stateはオブジェクトの実体に現れないハリボテの型変数である。

VM! State: {"stopped", "running"}! = Class(..., Impl := Phantom! State)
VM!("stopped").
    start ref! self("stopped" ~> "running") =
        self.do_something!()
        self::set_phantom!("running")

stateupdate_phantom!メソッドかset_phantom!メソッドを介して更新する。 これはPhantom!(Phantomの可変版)の標準パッチが提供するメソッドで、使い方は可変型のupdate!, set!と同じである。