$Callヤバイ。超ヤバイ。
flowtypeのUtility Typesを紹介する記事にて、
$Callは関数の戻り値型を返す型です。
と紹介させていただいたのですが、この説明は間違いではないけど、$Callのヤバさを何一つ説明できてませんでした。(というか気づいていなかった)
発端
ことの発端は上記issueのコメントを見たところから始まります。
From what I can tell, higher-kinded types are actually supported in Flow, though they are encoded using the simple-kinded function type and the utility type $Call for type-level application. In fact, some of the utility types of Flow are using this encoding, such as the $ObjMap type, where F is higher-kinded. Maybe this isn't official though, because I don't see it explicitly mentioned in the documentation, or I might be missing something.
Higher Kineded Typeがflowtypeでサポートされている!?$Callがそれ!!?
これはもう検証するしかないと触りだしたところ$Callのヤバいとしか言いようのない型だということがわかりました。
$CallというUtility Type
端的に言うと$Callは型を一定のルールに基づき変換するための型です。
例えば、
type InType<T: Array<*>> = $Call<<A>(arr: Array<A>) => A, T>
declare var arr: Array<number>;
declare var v: InType<typeof arr>;
v.toFixed();
v.replace('', ''); // Error
というように$Callを用いてArrayの型パラメーターを抜き出す型を作ることができます。
これは<A>(arr: Array<A>) => Aのルールに基づいて渡されたT型を変換する$Callの呼び出しになります。
また、$Callに渡すルールは(FunctionA)&(FunctionB)の形で定義できるので、TがFunctionAにマッチすればそちらの変換、FunctionBにマッチすればそちらの変換ということもできます。
つまり、僕たちは$Callによって新しい型表現を手に入れることができたんですね!!
余談
余談ですが$CallがHigher Kinded Typeかというとちょっと疑問は残ります。
僕の理解ではHigher Kinded Typeと呼べるのは2階カインドからです。
通常の型が0階カインド、型コンストラクタを持った型が1階カインド、型コンストラクタを受け取れる型が2階カインドで、flowtypeでは型コンストラクを受け取る方法はない認識です。
class _A {} // *
class _F<A> {} // * -> *
class _X<F: _F<*>> {} // * -> *
_Xでは一見それっぽく見えますが、_Fは既に確定しており_F<A>のAが決まれば新しい型を返せるので、_Xは_Fと同様の1階カインドです。
そのため、$CallをHigher Kinded Typeというのは言い過ぎかなと思うのですが実際のところどうなのでしょうか?(僕もカインドを理解できてる訳ではないので怪しい)
このあたりカインドに詳しい方がいましたら、ぜひ教えてください。