F[A]→F[B] に至る矢印を比べて、いろいろな型クラスをまとめて理解する、Cats 入門者向け記事。
はじめに
この記事では、Cats の型クラスをテーマにする。
Cats は関数型な Scala プログラミング1のためのライブラリなので、この記事でも関数を扱う。関数型パラダイムで関数2というと数学の関数とだいたい同じだから、定義域の集合から値域の集合へのマッピングのことになる。ただし、数学では写像や射などと言うが、記号でも矢印(→)だし、図の上でも矢印なので、この記事では矢印ということにする。
また扱う型クラスとしては、Functor、Applicative、Monad の周辺をメインとする。前半ではそれらの Scala トレイトが要求する実装必須メソッドのうち、F[A]→F[B] に向かう矢印に還元できるもの3に着目して整理し、さらに後半では、それらの周辺に見られるもう一つのパターンについて付け加える。
Cats は 2.8.0 を参照した。
F[A]→F[B] に向かう矢印のパターン
Functor 〜 Applicative 〜 Monad 周辺の関数のうち、(○→○)→(F[A]→F[B]) の形に還元できる矢印を整理すると、以下のように図示できる。
Functor の (A→B) → (F[A]→F[B]) を基本として、適当に F[_] で囲んだり、A と Bを入れ替えたりすることで、他の矢印が派生する様子が見え易いように描いてみた。一度分かったら、なかなか忘れにくいのではないかと思う。
※ 一応補足すると、F[_]
も型パタメータの一つで、List[_]
や Option[_]
のような型を代表したものになる4。
もう一つのパターン
Cats や Scalaz を使っていれば、特に圏論を勉強していなくても、何となく Monoid と Monad が似ている事に気づく人も多いのではないかと思う。実は Cats では、Monoid/Monad の類似点を共有する型クラス構成が他にもあって、表にして並べると以下のようになる。
ある型クラスに | ↓ を追加すると | この型クラスになる | メモ |
---|---|---|---|
Semigroup[A] | empty: A | Monoid[A] | 1階型. combine+empty. |
Semigroupal[F] | unit: F[Unit] | Monoidal[F] | ここから高階型. product+unit.5 |
SemigroupK[F] | empty: F[A] | MonoidK[F] | combineK+empty. |
Apply[F] | pure: A→F[A] | Applicative[F] | ap + pure |
FlatMap[F] | 同上 | Monad[F] | flatMap + pure |
CoflatMap[F] | extract: F[A]→A | Comonad[F] | Monad の双対 |
Compose[F] | id: F[A, A] | Category[F] | cats.arrow パッケージ、F[_,_] |
ある基本的な演算・操作を持つ型クラスに、「単位元ぽい何か」を付け加えて別の型クラスを作っている点が共通する。こうしたパターンも、一度理解すると覚えやすく忘れにくいと思う。
おわりに
Cats の型クラス群を個別でなくまとまりで捉えて、共通点と相違点、また型クラス相互の関係を観察すると、Scala コードのシグネーチャに埋もれていたパターンが見えてきて、複数の型クラスがまとめて分かるようになる。
補足
ごちゃごちゃするので上の図には含めなかったが、型クラス間に以下のような関係がある。
-
継承
- Invariant ◁- { Functor, Contravariant }
- Functor ◁- { Apply, CoflatMap }
- Apply ◁- { FlatMap, Applicative } ◁- Monad
- CoflatMap ◁- Comonad
-
双対
- Functor - Contravariant
- FlatMap - CoflatMap
- Monad - Comonad
継承に関して付け加えると、一般に継承下位のメソッドを使って上位のメソッドが実装できることを確認すると、さらに理解が進むかもしれない。例えば、Functor#map
は Applicative
の pure
と ap
で実装できるし、Apply#ap
は FlatMap
の map
とflatMap
で実装できる。つまりこういうことが、例えばよく言う「Monad は Applicative より制約が多いが、強力である」という場合の「強力」の意味だったりする。
-
Scala は、ぜんぜん関数型(⊂宣言型)らしくない、命令型パラダイムなスタイルで書くこともできる言語で、そういうコーディングが優勢な分野もあるが、ここでは扱わない。 ↩
-
In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. (wikipedia) ↩
-
矢印から矢印に向かう矢印、高階関数になる。 ↩
-
プロパー型(proper type)を受け取って別の型を作る、1階カインドの型(1st-order-kinded type)。 ↩
-
正確には、Semigroupal ◁- InvariantSemigroupal ◁- InvariantMonoidalの継承関係になる ↩