Scalaz を使うことになったので、そこそこ使いなれた Cats との違いを、特に型クラスに着目して比べてみる。
はじめに
自分が本格的に Scala を始めたときすでに Cats が普及し始めていて、仕事でも Cats から入ったわりと新参なので、Scalaz 自体はほとんど触ったことがない。ただ『FP in Scala』辺りは繰り返し読んでいるし、いろいろ知見の詰まった歴史のある老舗ライブラリということは知っているので、この際、ある程度使えるようにしておきたい。
そこで、この記事では Cats を起点にして違いをみてみる。Cats の ○○ が Scalaz ではどうなるか?という方向で観察するので、ベテラン Scala プログラマには順序が逆に思えるかもしれない。
方針
Cats サイトの型クラス図の、Functor や Monad を含む core の階層をベースにして、 『FP for Mortals』 に掲載されている Scalaz の型クラス階層と対比してみた。ただし、、、
- Cats 側の
Unordered*とCommutative*は省略した。 - 現状のソースとあっていない部分は、図を修正した。
- Cats は
1.6.0、Scalaz は7.2.27を参考にした。
各型クラスのメモ
函手と○変
-
Invariant: Scalaz では
InvariantFunctor。メソッドもimapがxmapになる。 - Functor: だいたい同じ。
- Covariant: Invariant から継承した imap を除いてだいたい同じ。
Foldable 系
- Foldable: だいたい同じ。
- Traverse: だいたい同じ。
-
Reducible: Scalaz では
Foldable1で、メソッドもreduce→fold1のように名称が変わる。1 -
NonEmptyTraverse: Scalaz では
Traverse1。メソッドもnonEmptyTraverse→traverse1となる。 -
Distributive:
Traverseの 双対のDistributiveはどちらも同様に定義されている。
Apply 〜 Monad
-
Apply:
ap、ap2は同じだが、map2が Scalaz ではapply2になる。また Cats ではInvariantSemigroupalを継承しているが、Scalaz はそうではないという違いがあり、Cats のproduct(Semigroupal から継承)と、Scalaz のproduct(Apply[F], Apply[G] から Apply[(F, G)]への合成) では別物になる。 -
Applicative:
pureは共通だが、Applyと同様に Cats のみがInvariantMonoidalを継承しており、Cats 版ではInvariantMonoidalから継承するpointメソッドが、Scalaz ではApplicativeで初めて宣言される(pure(a)=point(a))。 -
FlatMap: Scalaz では
Bindと呼ばれ、メソッド名もflatMap→bind、flatten→joinと変わる。また地味に大きな違いとして、Cats ではFlatMapで宣言されているtailRecMが、Scalaz では別途BindRecで宣言される。 - Monad: ほとんど同じ
Semigroupal 〜 ContravariantMonoidal
以下は Cats にのみあって、Scalaz にはない。
-
Semigroupal: (F[A], F[B])=>(F[(A, B)]) となるメソッド
productが宣言される。Scalaz のproductとは別物(上述)。 -
InvariantSemigroupal: Cats でのみ
Applyの基底型クラスとなる(上述)。 - ContravariantSemigroupal: (特になし)
-
InvariantMonoidal: Cats でのみ
Applicativeの基底型クラスとなる(上述)。 - ContravariantMonoidal: (特になし)
SemigroupK 〜 Alternative
-
SemigroupK: Scalaz では
Plusと呼ばれる。メソッドもcombineKがplusになる。ただしエイリアスとなる演算子<+>は共通。 -
MonoidK: 型名は Scalaz で
PlusEmptyとなり異なるが、メソッドはemptyで同じ。 -
Alternative: Scalaz では
ApplicativePlusという型名になる。ソースを見比べると全然ちがうように見えるが、結局ap、pure、empty、combineK/plusを継承する点でだいたい同じになる。 -
IsEmpty: Scalaz のみで、
Optionのインスタンスなどが提供されている。 -
MonadPlus: Scalaz のみの型クラス。Cats では
Alternativeで定義されている、separateやuniteなどが、Scalaz ではこちらで定義されている。
Error系
-
ApplicativeError: Cats のみ。Scalaz では
MonadErrorで宣言されているraiseError、handleErrorなどが、Cats では一階層上のこちらで宣言されている。 -
MonadError: 上述のとおり、Scalaz ではこちらで
raiseError、handleErrorなどが宣言されている。Cats ではMonad(したがってFlatMapも)を継承していることを利用したensureやrethrowと言ったメソッドが定義されている。
CoflatMap 〜 Bimonad
-
CoflatMap: FlatMap/Bind と同様、Scalaz では
Cobindというクラス名になり、メソッド名も同様に変わる。 -
Comonad: 型クラス名は共通しているが、
extractメソッドが Scalaz ではcopointとなる。 - Bimonad: Cats のみ。
その他 Scalaz のみの型クラス
-
Divide:
Applyの contravariant な型クラスで、apply2に渡す関数の向きを逆にしたdivideメソッドが提供される。 -
Divisible:
Applicativeの contravariant で、pureをひっくり返したconquerが提供される。 -
Align: 「片側一方または両方」となるようなデータの組への操作を提供するもので、
\&/(Cats でのIor) にも関連する。align*、merge、pad* などのメソッドが提供されていて、『FP for Mortals』では Map を題材にした具体例が紹介されている。
ここで触れなかった型クラス
- 階層を成さない独立した型クラス群: Zip, Unzip, Cozip, Show, Optional など
- 代数系: Semigroup, Monoid, Band など
- 比較など: Equal, Order, Enum など
- モナドトランスフォーマー, Free/FreeT
- Coyoneda, Lan/Ran などレベル高めの圏論型クラス
追々調べる。
おわりに
- Cats より記号が多めな気がするが、記号嫌いじゃない派なので好ましい。
\/はよく見るけど、Ior相当のものが\&/だったり、オブジェクト指向の文脈でもよくみるリスコフの置換原理が、<~<とか型で表現されてるのも興味深い。 - 型クラス以外にも便利そうな小物が多い。例えば
MemoとかEndoとか、ほぼ同じコードを自前で書いたりしていた。こういうのをいろいろ発見して、また Cats プロジェクトやるときにも知見を活かしたい。 - 今回は型クラスのみ調べたが、データ型も Cats に無いのが多くて面白そう。
- 思ったより相違点が多いが、逆に勉強になる。
-
'1'は「少なくとも一つ」という意味での 1 だろうか? ↩
