こんにちは。GBS第2システム開発部の小路です。
関数型プログラミングの手助けをするKotlin製のライブラリにArrowというものがありますが、それを使うためには、基本的な用語を知っている必要があります。
今回は主にArrowに記載の内容を元に、型コンストラクタと高カインド型の概要をまとめます。
Arrowにおける型コンストラクタについて
- 型コンストラクタとは少なくとも1つのジェネリックパラメータを持つクラス(インタフェース)である(例えば、 ListK<A>、Option<A>(※1)のAがジェネリックパラメータに相当)
- ListK<A>のAに対して、Int型を割り当てた場合、ListKはListK<Int>となる。このとき、ListKにはジェネリックパラメータではなく、Intと言う具体的な型が指定されているため、型コンストラクタとは見なされなくなり、通常の「型」と見なされるようになる。
- 複数のパラメータを持つ型コンストラクタ(例:Either<L, R>)の一つのパラメータに対して型を適用した場合、その結果まだもう一方のジェネリックパラメータが残っているので、これは型コンストラクタと見なされる。(例えば、Either<L, R>のLに対して、Throwableを適用すると、Either<Throwable, R>となるが、これはRというジェネリックパラメータを持っているので、型コンストラクタと見なされる。)
- Kotlinでは型コンストラクタはファーストクラスとして扱われていないので、ArrowではKind<F, A>というインタフェースを利用して、それを表現している。
※1… ListK, OptionのいずれもArrowが提供するAPI。ListKはKotlin標準のListクラスをラップして、モナド則を満たす関数を提供する。
Arrowにおける高カインド型とは
- カインドをざっくり言い表すと、「型コンストラクタ」の「型」のことである。
- 型パラメータを取らない型(Int、Stringなど)は*で表現され(零項の型コンストラクタと言う事もある)、Option<A>などの一つのパラメータを取る型コンストラクタ(1階の型コンストラクタと言う)は* -> *と表現される。
- n階の型コンストラクタは、型パラメータに具体型を適用することで普通型となる。上述の例の通り、 ListK<A>は1階の型コンストラクタだが、このAに対して、Int型を割り当てた場合、ListK<Int>となり、普通型となる。
- Arrowでは1階の型コンストラクタはKind<F, A>というインタフェースで表現され、Fはコンテンツをラップするコンテナの型を表し、Aはコンテンツの型を表す。(2階の型コンストラクタは(* -> *) -> *で表現され、ArrowではKind<Kind<F, A>, B>で表す。3階以降の表現方法も同様。)
- Kind<F, A>のFを表現するための代理クラスがあり、その代理クラス名はコンテナのクラス名に対して接頭辞「For」を付与したものである。(例:ForListK、ForOption)
具体的に、OptionクラスとForOptionクラスの関係性は以下のようになります。
class ForOption private constructor() { companion object {} } typealias OptionOf<A> = arrow.Kind<ForOption, A> @higherkind sealed class Option<out A> : OptionOf<A> // => sealed class Option<out A>: Kind<ForOption, A>
上記の通り、OptionクラスはKind<ForOption, A>を実装するクラスとして定義されており、以下を満たすことが読み取れます。
- Option<out A>はコンテンツの型として、A(ジェネリックパラメータ)をもつ。
- Option<out A>はコンテナの型(コンテンツをラップする役割を持つ型)としてForOptionをもつ。(※2)
- Option<out A>はパラメータAを持ち、カインドとして* -> *(「1階の型コンストラクタ」)で表現できる。
- Option<out A>のAに対して、具体的な型- 例えばInt -を適用すると、Option<Int>となり、型コンストラクタではなく具体的な型(カインドは「*」)となる。
※2… Kind<ForOption, A>を実装するのはOption<A>のみであるため、ForOptionとOption<A>は密接な結びつきを持つ。そのため、後述のKind<ForOption, A>.fix()を呼び出すことで、Kind<ForOption, A>からOptionへ安全にダウンキャストができる。
Kind<F, A>の拡張関数について
Kind<F, A>のFに対して、Arrowで提供される全てのDatatypes(OptionやListK、Eitherなど)に適用できる、fixという関数が実装されています。
これにより、KindクラスからOptionやEitherなどのサブクラスにダウンキャストができるようになっています。
例えば、Kind<F, A>のFにForOptionが適用されたfix関数は以下の通りです。
inline fun <A> OptionOf<A>.fix(): Option<A> = this as Option<A> // => inline fun <A> Kind<ForOption, A>.fix(): Option<A> = this as Option<A>
Arrowライブラリの実装上、Kind<ForOption, A>を実装するのはOption<A>のみ(他のDatatype(ListK, Either, etcも同様)であるため、ダウンキャストが安全に行えるようになっています。