幽霊型
幽霊型は、コンパイラに注釈を与えるためだけに存在するマーカートレイトである。 幽霊型の使い方として、リストの構成をみる。
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
PhantomがT型を保持する。しかし実際には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")
stateはupdate_phantom!メソッドかset_phantom!メソッドを介して更新する。
これはPhantom!(Phantomの可変版)の標準パッチが提供するメソッドで、使い方は可変型のupdate!, set!と同じである。