UnionのGenericsでは、assignされてないタイプパラメータを持つ変数がありうる
以前、Genericsについてのメモで、 IR上、より正確にはexpr上では全てのタイプパラメータは何かしらassignされている、 と書いたが、これは間違いだった。 というかジェネリクスのUnionをサポートすると間違いのケースが出てくる(それ以外では出てこない)。
以下のメモは自分の理解だが、あまり自信はないので間違っている所とかを見つけたら教えて下さい。
例えば以下のような型があるとする。
type AorB<'t> =
| A
| B of 't
この時、式の中でAとすると、この型はAorB<'a>となる。
fsxで試すと以下。
> type AorB<'t> =
- | A
- | B of 't
-
- ;;
type AorB<'t> =
| A
| B of 't
> A ;;
val it: AorB<'a>
ようするにofがないcaseの時だけ特別にこういう型の変数が出てくる。
このAはかなり特殊な型で、例えば以下のようなコードがあった時に、コメントにもあるように、 vの型が場所によって変わる。
let v = A // 1. ここでは、AorB<'a>型
if cond then
v // 2. ここではおそらくAorB<int>型?
else
B 123
somFunc v // 3. ここではAorB<string>型にもなりうる
普通変数の型は解決されたら全て同じ型になるのだが、 このケースだけは参照されるコンテキストで型が決まる。 特にifのケースではelse節の型で決まると思う。
けれど1の所の変数の型は変わってない。それは3の所で違う型を期待する関数にわたす時にはその型になりうる事からも確認出来る。
これは、リストのnilの特別扱いの、より一般的なルールに見える。
nilはList<int>の型でもList<string>の型でも使える、特別なもの。
このAはそれと同じようなルールが適用される特別な何かに見える。
こういう、vのような変数はたぶんGolangでは作れない。
ただ実行時の値としてはどうせ中は空なのだから、なににでもキャスト出来る事が保証される。 だから変数の参照の所で目的の型にキャストするのはいつも出来る。 だからトランスパイラとしてエミュレートする事は出来そう。
自分の理解だと、vという変数の型はAorB<'a>で確定していて、
それを参照する所でコンテキストに応じて型が変わる、
と思っているが、この理解であっているだろうか?
ただAorB<int>とAorB<string>のどちらにもなれる、というよりは、
AorB<int>であると同時にAorB<string>でもある、というのが正しい解釈に思う。
実装としてはvを参照するごとに'aに別々のType Variableを振っていく、とかでいいのか?
追記: 関数もassignされてないタイプパラメータがASTに現れうる
Arantium Maestumのzehnpaardさんに教えてもらったが、
fun x->xなども同じような挙動をしている。
> let a = fun x -> x ;;
val a: x: 'a -> 'a
こうやって作ったaは複数の型で使える。
> a 123 ;;
val it: int = 123
> a "123" ;;
val it: string = "123"
現状のFolangの実装では、ルートの関数定義は特別扱いしていて関数ファクトリをscopeに登録するようになっていて、
これはASTには現れない。
ASTに現れる時には全てType Variableが振られてそれが解決されている。
だからaも何かのType Variableが振られて、一回a 123と使われるともうint以外では使えなくなる。
この理解は間違えていて、ASTにファクトリが登録出来るのが正しそうだ。
けれどこちらのケースは実用上はそんなには問題にならない。 基本的にはinner 関数は親の関数のtype parameterでのgenericな関数にはなっているので、 それで困るのは同じ関数内で複数回別の型に対して使う場合だけだからだ。 実際自分のFolangの振る舞いの方が正解だ、と言われても、比較対象がなければそういうものだと思う(自分もF#で試すまでそう思っていた)。
Unionのof無しのケースはそれとは対照的に、実用上めちゃ困るし、他の解釈は難しい。
ちなみにGolangは関数リテラルのGenericsは無くて、関数リテラルじゃないinner関数もないのでこういう場合は無い。