Help us understand the problem. What is going on with this article?

Arrowを使ってAndroidで関数型プログラミング〜型コンストラクタ、高カインド型について〜

More than 1 year has passed since last update.

関数型プログラミングの手助けをするKotlin製のライブラリであるArrowを使うためには、基本的な用語を知っている必要があります。
今回は主にArrowに記載の内容を元に、型コンストラクタと高カインド型の概要をまとめます。

Arrowにおける型コンストラクタについて

  • 型コンストラクタとは少なくとも1つのジェネリックパラメータを持つクラス(インタフェース)である(例えば、 ListK<A>Option<A>(※1)のAがジェネリックパラメータに相当)
  • ListK<A>Aに対して、Int型を割り当てた場合、ListKListK<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>のみであるため、ForOptionOption<A>は密接な結びつきを持つ。そのため、後述のKind<ForOption, A>.fix()を呼び出すことで、Kind<ForOption, A>からOptionへ安全にダウンキャストができる。

Kind<F, A>の拡張関数について

Kind<F, A>Fに対して、Arrowで提供される全てのDatatypes(OptionやListK、Eitherなど)に適用できる、fixという関数が実装されています。
これにより、KindクラスからOptionEitherなどのサブクラスにダウンキャストができるようになっています。

例えば、Kind<F, A>FForOptionが適用された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も同様)であるため、ダウンキャストが安全に行えるようになっています。

masaki_shoji
ソフトウェアエンジニア。株式会社G・B・S所属。 仕事では主にAndroid開発を担当する事が多いですが、システム全般に興味があります。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away