最近コミットランキングで7位に浮上して、(コミッターじゃない*1のに)何人かのコミッターよりScalazへのコミット数が多いです⇒
こんにちは(#・∀・)
https://github.com/scalaz/scalaz/contributors
圏論勉強会の第4回ででてきたContravariant Functor
http://nineties.github.io/category-seminar/4.html#/58
がScalaz7にあるので
https://github.com/scalaz/scalaz/blob/v7.0.0/core/src/main/scala/scalaz/Contravariant.scala
流れにのってScalazにおいての例をひたすら説明する(?)エントリ。
ところでPlay2にもContravariantはありますよ
ちょっと調べたら、「Scalazアドベントカレンダー」*2でも、「独習Scalaz」でも、ほぼ取り上げられてないのでちょうどいいですね。
書いてる人は全然圏論に詳しくないので、圏論的な理論的な話は一切しません(できません)
まずContravariantは、以下のようなcontramapという抽象メソッドが1つだけあるtraitです。
https://github.com/scalaz/scalaz/blob/v7.0.0/core/src/main/scala/scalaz/Contravariant.scala#L25
def contramap[A, B](r: F[A])(f: B => A): F[B]
その他色々ありますが、基本的にScalazのtypeclassの定義の作法に則っていて、(Scalazに慣れてる人にとっては)何の変哲もないシンプルな感じ*3です。
いきなりちょっと話逸れます。
v7.0.0の時点ではContravariantはどのtypeclassも継承していませんが、これを書いている時点の最新のscalaz-sevenのbranchではInvariantFunctorというtypeclassを継承してます。InvariantFunctorについてはここでは詳しく説明しません。InvariantFunctorのコメントに、あのekmettの rotten bananas という謎なタイトルのblogへのリンクがあるので、それ読めばいいんじゃないでしょうか*4
https://github.com/scalaz/scalaz/blob/cbc11d242/core/src/main/scala/scalaz/InvariantFunctor.scala
https://github.com/scalaz/scalaz/blob/cbc11d242/core/src/main/scala/scalaz/Contravariant.scala#L18
ちなみに、対比させるために参考としてScalazのFunctorの定義を確認しておくと、以下のようなmapという抽象メソッドがあるのがFunctorです
def map[A, B](fa: F[A])(f: A => B): F[B]
Functorのほうはfの型が A => B
になっているのに対して、ContravariantはB => A
になっています。それ以外は全く一緒ですね。
さて、Contravariantのtypeclassのインスタンスの一覧を挙げてみます
- Equal
- MetricSpace
- 数学的な"距離"を表すもの。 ほぼBKTreeのためにある。spireというライブラリがあって、そっちのほうが優れてて、spire使えばいいじゃんということで、BKTreeと一緒にdeprecatedになってしまうらしいが・・・(´;ω;`)
- NullArgument
- v7.0.0がでた後に入ったもの。
Option[A] => B
の函数のラッパー
- v7.0.0がでた後に入ったもの。
- NullResult
- v7.0.0がでた後に入ったもの。
A => Option[B]
の函数のラッパー
- v7.0.0がでた後に入ったもの。
- Order
- Show
- Function1
- いわゆる普通の函数
- Coproduct
- IndexedContsT
- 継続モナド
- IndexedStateT
- IndexedStoreT *5
- Isomorphism
- "同型"を表すためのもの(?)
さて、ここから詳しく見ていきたいところですが、もう列挙するだけで疲れたし、全部見きれるわけないので、適当に例を1つだけ挙げて終わりにします・・・。
一番簡単そう(?)で、それなりに実際にScalaz内で使われてるEqualの例を見てみましょう。例えば以下のような感じ
implicit def listTEqual[F[+_], A](implicit E: Equal[F[List[A]]], F: Monad[F]): Equal[ListT[F, A]] =
E.contramap((_: ListT[F, A]).toList)
https://github.com/scalaz/scalaz/blob/v7.0.0/core/src/main/scala/scalaz/ListT.scala#L89
なにやらだいぶ型がややこしそうに見えますが、ここで最終的にやりたいことは
Equal[ ListT[F, A] ]
という型のオブジェクト*6をimplicitに定義したいわけです。
それで、
Equal[ F[ List[A] ] ]
という型のオブジェクトは手元にあります、あることになってます。*7
ここでは、この Equal[ F[ List[A] ] ] 型の変数である"E"に対して、contramapを呼び出しています。
Equal[ F[ List[A] ] ] は、
F[ List[A] ] 型のオブジェクトを2つ引数に取って、Booleanを返す*8
という役割があるわけですが、それに対してcontramapで変換して新しいEqualのオブジェクトができます。
その結果生成されるオブジェクトの型がEqual[ ListT[F, A] ] であり、
ListT[F, A] 型のオブジェクトを2つ引数に取って、Booleanを返す
という役割をもつものになるわけです。ここでcontramapに渡している型は
ListT[F, A] => F[ List[A] ]
という函数です。もう一度contramapの定義を載せます
def contramap[A, B](r: F[A])(f: B => A): F[B]
もう一回まとめて言い直すと
1. Equal[ F[ List[A] ] ] 型のオブジェクトがある
2. それに対してcontramapを呼び出し、 ListT[F, A] => F[ List[A] ] という函数を渡す
3. すると、Equal[ ListT[F, A] ] 型のオブジェクトが手に入る
となります。最終的に手に入れたいのはEqual[ ListT[F, A] ]なわけですが、渡す函数がF[ List[A] ] => ListT[F, A]
型ではなく ListT[F, A] => F[ List[A] ]
なのがポイントです。
わかりましたか?わかりませんか?
自分自身でも、説明しててこれ以上どう説明すればいいのかわからなくなってきたので*9とりあえずお終いにします・・・(´・ω・`)
結論として
「なんとなく圏論勉強会ででてきた抽象的で難しそうな概念が、Scalazという身近な(?)実用的な(?)ライブラリの中で普通に使われてる!」
ことが、少しでも実感できればいいんじゃないでしょうか!(なげやり・・・
今後、圏論勉強会が進んで、またScalazと圏論の概念で対応するものがでてくると思うので、気が向いたら今回のような「圏論勉強会便乗のエントリ」を書くかもしれません・・・
*1:これ書いた当時はコミッターじゃなかったが、このあとコミッターになった
*2: http://partake.in/events/4b3afdc8-e4ec-4010-b8ec-31b89210dda0 http://partake.in/events/7211abc9-ebb8-4670-b912-3089dc5e0edd
*3:つまりこのcontramap周辺の数行以外は、半自動生成されています
*4:ここ数日サーバー落ちてるみたいですが・・・
*5:Lensに関係あるというくらいしかしらなくて、よく理解できてない・・・
*6:ListモナドのモナドトランスフォーマーのEqualのインスタンス
*7:implicit な引数で取得してくることになってます
*8:つまり2つが同一のものか判定をおこなう
*9:書いてから気づいたが、ListTよりもっとわかりやすい例あるのでは・・・