4.5 KiB
パッチメソッドの解決
Nat
は0以上のInt
、つまりInt
のサブタイプである。
本来Nat
はPythonのクラス階層には存在しない。Ergはこのパッチのメソッドをどうやって解決するのだろうか?
1.times do:
log "hello, world"
.times
はNatImpl
のパッチメソッドである。
1
はInt
のインスタンスであるので、まずInt
のMRO(Method Resolution Order)を辿って探索する。
ErgはInt
のMROにInt
, Object
を持っている。これはPython由来である(Pythonにおいてint.__mro__ == [int, object]
)。
.times
メソッドはそのどちらにも存在しない。ここからは、そのサブタイプの探索に入る。
~
整数は明らかにその上位型に実数や複素数、ひいては数全体を持つはずだが、Pythonと互換性をもつレイヤーではその事実は現れない。
だが実際にErgでは1 in Complex
や1 in Num
はTrue
となる。
Complex
に至っては、Int
と継承関係にないクラスであるのに、型として互換性があると判断されている。一体どうなっているのか。
~
あるオブジェクトに対して、その属する型は無数に存在する。 だが実際に考えなくてはならないのはメソッドを持つ型、すなわち名前を持つ型のみである。
Ergコンパイラは、全ての提供メソッドとその実装を持つパッチ・型のハッシュマップを持っている。 このテーブルは型が新たに定義されるたびに更新される。
provided_method_table = {
...
"foo": [Foo],
...
".times": [Nat, Foo],
...
}
.times
メソッドを持つ型はNat
, Foo
である。これらの中から、{1}
型に適合するものを探す。
適合判定は二種類ある。篩型判定とレコード型判定である。篩型判定から行われる。
篩型判定
候補の型が1
の型{1}
と互換性があるか確認する。篩型の中で{1}
と互換性があるのは、{0, 1}
, 0..9
などである。
0..1 or 3..4
, -1..2 and 0..3
などの有限要素の代数演算型は、基底型として宣言すると篩型に正規化される(つまり、{0, 1, 3, 4}
, {0, 1, 2}
にする)。
今回の場合、Nat
は0.._ == {I: Int | I >= 0}
であるので、{1}
はNat
と互換性がある。
レコード型判定
候補の型が1のクラスであるInt
と互換性を持つか確認する。
その他、Int
のパッチである、またその要求属性をInt
がすべて持つ場合も互換性がある。
~
というわけで、Nat
が適合した。ただFoo
も適合してしまった場合は、Nat
とFoo
の包含関係によって判定される。
すなわち、サブタイプのメソッドが選択される。
両者に包含関係がない場合は、コンパイルエラーとなる(これはプログラマーの意図に反したメソッドが実行されないための安全策である)。
エラーを解消させるためには、パッチを明示的に指定する必要がある。
o.method(x) -> P.method(o, x)
全称パッチのメソッド解決
以下のようなパッチを定義する。
FnType T: Type = Patch T -> T
FnType.type = T
FnType
パッチのもとで以下のようなコードが可能である。これはどのように解決されるのだろうか。
assert (Int -> Int).type == Int
まず、provided_method_table
にはFnType(T)
が以下の形式で登録される。
provided_method_table = {
...
"type": [FnType(T)],
...
}
FnType(T)
のパッチする型が適合するかチェックされる。この場合、FnType(T)
のパッチ型はType -> Type
である。
これはInt -> Int
に適合する。適合したら、単相化を行って置換する(T -> T
とInt -> Int
のdiffを取る。{T => Int}
)。
assert FnType(Int).type == Int