$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というのは言い過ぎかなと思うのですが実際のところどうなのでしょうか?(僕もカインドを理解できてる訳ではないので怪しい)
このあたりカインドに詳しい方がいましたら、ぜひ教えてください。