タイトルは、[Haskell] FunctorクラスはHask圏からHask圏への関手となっていますが、
もっと正確には、[Haskell] FunctorクラスはHask圏からHask圏の部分圏への関手のほうが、より適切かもしれません。
もともとこの記事はHaskellアドベントカレンダー(その3)の、12/14に登録した「とびだせ!Hask圏」という記事の前半部分として書いていたものです。
。
ただ、その記事がクソ長くなってしまいそうだったので、前半を分離して一つの記事にしたのがこの記事になります。
基本的な用語
圏とは
圏の公理を書こうかと思ったんですが、結構面倒くさいので省略します。
忘れちゃダメなポイントは、
- すべての対象に恒等射が存在する。
- 射の合成は結合則を満たす。
という点です。
Hask圏とは
- 対象はHaskellの、カインドが
*
である型 - 射はHaskellの関数
- 射の始域は関数の引数の型
- 射の終域は関数の戻り値の型
- 恒等射は
id
- 射の合成は関数結合
(.)
であるような圏です。
関手とは
圏 C から圏 D への共変関手 F とは、
- C の任意の対象 X を、 D の対象 F(X) に対応させる。
- C の任意の射 f : X -> Y を、Dの射 F(f) : F(X) -> F(Y) に対応させる。
- C の恒等射は、 D の恒等射に対応させる。
- C の任意の射 f : X -> Y と g : Y -> Z について、F(g ∘ f) = F(g) ∘ F(f) を満たす。
以上をみたすようなもののことです。
最後のはちょっと分かりづらいかもしれませんが、射を合成してから関手で移しても、関手で移してから合成してもおんなじってことです。
Haskell の Functorクラス
HaskellにはFunctor
という型クラスがあり、これは関手を表現しています。
では、具体的にどんな圏からどんな圏への関手なのでしょうか?
##例:Maybe
代表的なFunctorである Maybe
を例にとって考えてみましょう。
Maybe
という型コンストラクタのカインドを見てみると
ghci> :kind Maybe
Maybe :: * -> *
というように、カインド*
を持つ任意の型を、カインド*
を持つ別の型に移すものだとわかります。
具体的には、
ghci> :kind Int
Int :: *
ghci> :kind Maybe Int
Maybe Int :: *
という具合です。
どうやらこれは関手の定義のうちの一つ、「C の任意の対象 X を、 D の対象 F(X) に対応させる」という性質を表現しているようです。
つぎに、fmap
という関数について考えてみます。
fmapの型を見てみると
ghci> :type fmap
fmap :: Functor f => (a -> b) -> f a -> f b
これは多相的な表現になっていますが、Maybeに限って言えばfmap :: (a -> b) -> (Maybe a -> Maybe b)
ということです((->)
は右結合なので括弧を補足しました)
この型を見ると、fmap
は任意の型の間の関数 f :: a -> b
を、fmap f :: Maybe a -> Maybe b
に移すものだとわかります。
これは関手の定義のひとつ、「C の任意の射 f : X -> Y を、Dの射 F(f) : F(X) -> F(Y) に対応させる」という性質を表現しているようです。
Maybeの例からわかること
さて、型コンストラクタ Maybe
が関手による対象の対応付け、fmap
が関手による射の対応付けを表現していることがわかりました。
では、この関手はどんな圏からどんな圏への関手を表現しているのでしょう?
その答えは、関手によって移される前後の「対象」と「射」に注目してみるとわかります。
Maybeで移される前 | Maybeで移された後 | |
---|---|---|
対象 | カインド* を持つ型 |
カインド* を持つ型 |
射 | 関数 | 関数 |
この表を見ると、Maybeによって移される前後で両方とも対象が*
で射が関数なので、両方ともHask圏であると言えそうです。
(移された後の対象はすべて Maybe a
という形をとる型に限られるので、「Hask圏の部分圏」と言ったほうが、より適切かもしれません)
ここではMaybeを例に挙げましたが、他のFunctorでも同じことです。
一般にHaskellのFunctorクラスはHask圏からHask圏への関手を表現していると言えます。
#まとめ
タイトルにもなっていますが、
HaskellのFunctorクラスはHask圏からHask圏への関手
ということがわかりました。