関数型プログラミングの手助けをする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も同様)であるため、ダウンキャストが安全に行えるようになっています。