可変型

badge

Warning: この項の情報は古く、一部に間違いを含みます。

Ergではデフォルトですべての型が不変型、すなわち内部状態を更新できないようになっています。 しかし可変な型ももちろん定義できます。可変型は!を付けて宣言します。

Person! = Class({name = Str; age = Nat!})
Person!.
    greet! ref! self = print! "Hello, my name is \{self::name}. I am \{self::age}."
    inc_age! ref! self = self::name.update! old -> old + 1

正確には、可変型・または可変型を含む複合型を基底型とする型は型名の最後に!を付けなくてはなりません。!を付けない型も同一の名前空間に存在してよく、別の型として扱われます。 上の例では、.age属性は可変で、.name属性は不変となっています。一つでも可変な属性がある場合、全体として可変型になります。

可変型はインスタンスを書き換えるプロシージャルメソッドを定義できますが、プロシージャルメソッドを持つからと言って可変型になるとは限りません。例えば配列型[T; N]には要素をランダムに選ぶsample!メソッドが実装されていますが、これはもちろん配列に破壊的変更を加えたりはしません。

可変型オブジェクトの破壊的操作は、主に.update!メソッドを介して行います。.update!メソッドは高階プロシージャで、selfに関数fを適用して更新します。

i = !1
i.update! old -> old + 1
assert i == 2

.set!メソッドは単に古い内容を捨てて新しい値に差し替えます。.set! x = .update! _ -> xです。

i = !1
i.set! 2
assert i == 2

.freeze_mapメソッドは値を不変化して操作を行います。

a = [1, 2, 3].into [Nat; !3]
x = a.freeze_map a: [Nat; 3] -> a.iter().map(i -> i + 1).filter(i -> i % 2 == 0).collect(Array)

多相不変型において型の型引数Tは暗黙に不変型であると仮定されます。

# ImmutType < Type
K T: ImmutType = Class ...
K! T: Type = Class ...

標準ライブラリでは、可変型(...)!型は不変型(...)型を基底としている場合が多いです。しかしT!型とT型に言語上特別な関連はなく、そのように構成しなくても構いません1

T = (...)のとき単にT! = (...)!となる型(...)を単純構造型と呼びます。単純構造型は(意味論上)内部構造を持たない型ともいえます。 配列、タプル、セット、辞書、レコード型は単純構造型ではありませんが、Int型やStr型は単純構造型です。

以上の説明から、可変型とは自身が可変であるものだけでなく、内部に持つ型が可変であるものも含まれるということになります。 {x: Int!}[Int!; 3]などの型は、内部のオブジェクトが可変であり、インスタンス自身が可変なわけではない内部可変型です。

Cell! T

Intや配列などの不変型に対しては、既に可変型が定義されています。しかし、このような可変型はどのようにして定義されたのでしょうか?例えば、{x = Int; y = Int}型に対しては{x = Int!; y = Int!}型などが対応する可変型です。 しかしInt!型はどうやってInt型から作られたのでしょうか?あるいはInt!型はどのようにしてInt型と関係付けられているのでしょうか?

それらに対する答えがCell!型です。Cell! T型はT型オブジェクトを格納する箱のような型です。

IntOrStr = Inr or Str
IntOrStr! = Cell! IntOrStr
x = IntOrStr!.new 1
assert x is! 1 # `Int or Str` cannot compare with `Int` directly, so use `is!` (this compares object IDs) instead of `==`.
x.set! "a"
assert x is! "a"

Cell! T型の重要な性質として、T型の部分型になるというものがあります。これより、Cell! T型のオブジェクトはT型のメソッドを全て使うことができます。

# definition of `Int!`
Int! = Cell! Int
...

i = !1
assert i == 1 # `i` is casted to `Int`

1 T!型とT型に言語上の特別な関係がないのは意図的な設計です。関連があったとすると、例えば名前空間にT/T!型が存在するときに別のモジュールからT!/T型を導入できなくなるなどの不都合が生じます。また、不変型に対し可変型は一意に定まりません。T = (U, V)という定義があった際、(U!, V)(U, V!)という可変サブタイプがT!としてあり得えます。