可変型
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!
としてあり得えます。↩