Functor, Contravariant, Bifuntor, Profunctor

Contravariant

Functor(すなわちcovariant)とContravariantの違い:

  • Functor f => f aa は(関数が対象なら)返値の型を意味している
  • Contravariant s => s aa は引数の型を意味している。

このようにContravariantはFuntorと逆(双対?)なものになっている。

上で使った「aから目的の型を構成する型」の意味を以下の例で説明する:

Contravariantを使って先のShowType IntShowType Doubleに変換するには以下のようにする(なおContravariantcabal install contravariantでインストールすること):

つまり、ある型の入力から内部で使用する(従って外からは指定する必要がない)データ構造を生成する「初期化関数」があるとして、それを別の型の入力に適用させる枠組みに使えると。

意義?

先のプログラムは以下でも書ける。

関数合成をより一般化したものと考えればよいのだろうか。

BifunctorProfunctor

型構成子が2引数になった場合に対応。

Profunctorの第2引数はcontravariantではないので注意。

用語: bivariant, invariant

  • bivariant : covariantかつcontravariant
  • invariant : covariantでもcontravariantでもない

型引数の出現位置による判別

covariantかcontravariantかは、型引数の出現位置による。
例えば、関数(->) rはFunctorかContravariantか?

ということで ((->) r) はFunctorであることがわかる。

また先のShowTypeの場合では引数の位置に型引数が出ていた。このように「位置」でその型がcovariantなのかcontravariantなのかは自動的に決定できる。

そこで、定義域に現れる場合は負、値域に現れる場合は正とし、高階関数の場合は再帰的に掛けあわせることで得られる値がその型がcovariantなのかcontravariantなのかを常に表すということだった。

もっと難しい例

理解するのに苦労したコード:

g, h, fが何を意味するのか混乱したけど、 fの型をa -> bとすると、

  • {L3の定義より}g(a -> IO ()) -> IO () でなければならない
  • fmapの右辺で作るものは:: Callback bでなければならない
  • {L3の定義より}そのためにCallbackの引数のラムダ式の型は(b -> IO ()) -> IO ())でなければならない
  • その引数なのでh :: b -> IO ()
  • なので関数合成h . fの型はa -> IO ()

となって型検査終了。中で生成されるa型の値を関数fb型の値に変えてから関数h :: b -> IO ()に送り込むということで、どうということはない話だった。(これに似たものはモナド変換、Continuationあたりで見たような。。。)

--

--

Shuji Narazaki
text-is-saved

Studying SAT solvers and symbolic computation (type and logic). Being into 円城塔, Greg Egan, Stephen Colbert, 酒見賢一, say a Sci. Fi. person.