[Haskell] とびだせ！Hask圏

Functor（関手）の話です。

HaskellのFunctorクラスはHask圏からHask圏への関手しか表現できません。

そこで、Hask圏以外の圏についても使える関手のクラスを書いてみました。

よろしくね　(*´σｰ｀)ｴﾍﾍ


#基本的な用語と、HaskellのFunctorクラス
## 別のところにまとめました

この章に書こうとしていた、「基本的な用語の確認とHaskellのFunctorクラス」についての内容は、[別の記事](https://qiita.com/tezca686/items/faebea84f6b626f35e56)にまとめさせて貰いました。

内容としては、「**HaskellのFunctorクラスはHask圏からHask圏への関手しか表現できません。**」という文章の説明なので、それを読んで「そらそうだろ」って感じなら読み飛ばして次の章を読んで頂いて大丈夫です。

#とびだせ！Hask圏
HaskellのFunctorは、Hask圏からHask圏への関手を表現しています。

しかし、HaskellではHask圏以外の圏について考えることもできますし、実際 [Control.Category](https://hackage.haskell.org/package/base-4.10.0.0/docs/Control-Category.html) には様々な圏を統一的に表現できる型クラス `Cat` が定義されています。

私の今回の目的は、もっと一般的な圏から圏への関手を表現できるFunctorクラスを作り、**Hask圏から飛び出す**ことです。

## もっと一般的なFunctorクラス
以下が今回書いた「もっと一般的なFunctor」のクラスです。
「もっと一般的」なので `GeneralFunctor` という名前のクラスにしました。

（適当にカッコいい名前をつけちゃったので、「数学にはGeneralFunctorっていう名前の別の概念があるよ」みたいなことがあったらコメントで教えて下さい）

```hs:Functor.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FunctionalDependencies #-}
module Functor where

import Control.Category


class (Category c1, Category c2) =>
        GeneralFunctor (name :: *)
                       (c1 :: k1 -> k1 -> *)
                       (c2 :: k2 -> k2 -> *)
                           |name -> c1, name -> c2 where
    type F name (x :: k1) :: k2
    gmap :: name -> c1 a b -> c2 (F name a) (F name b)
```

短いですね。

対象を`*`以外のカインドの型にもできるように、`PolyKinds`拡張が使われています。

このクラスは、`c1`という圏から`c2`という圏への関手を表現しているのですが、移る前の圏と移る先の圏を決定しても、そのあいだの関手は唯一つに定まるとは限りません。
そのため、それぞれの関手に名前をつけて`name`という型を使って、どのインスタンス宣言が呼び出されるのか曖昧さがなく決定できるようにします。

`F`という型族で、「**C の任意の対象 X を、 D の対象 F(X) に対応させる**」という性質を表現しています。

また、`gmap`という関数が「**C の任意の射 f : X -> Y を、Dの射 F(f) : F(X) -> F(Y) に対応させる**」という性質を表現しています。

## 使ってみる

Sample.hs は記事の最後にコードを全部貼り付けるので、使っている拡張機能やimportしているモジュールはそちらで確認して下さい。

### 従来のFunctor
では、さっそく`GeneralFunctor`クラスを使ってみます。

まずは、従来のHaskellのFunctorを`GeneralFunctor`クラスで表現してみます。

```hs:Sample.hs
data EndoFunctor f = EF

instance Functor f => GeneralFunctor (EndoFunctor f) (->) (->) where
    type F (EndoFunctor f) x = f x
    gmap _ = fmap
```

```hs:ghci
ghci> gmap (EF :: EndoFunctor Maybe) (+40) (Just 2)
Just 42
```

良さそうですね。
関手の名前として`EndoFunctor f`という型をつくり、それを第一引数として`gmap`を呼び出すことで`fmap`と同じ働き（射の対応付け）ができていることが確認できます。

### Arrow
つぎはArrowの話です。

Hackegeの[Arrow](https://hackage.haskell.org/package/base-4.10.0.0/docs/Control-Arrow.html)にも書かれている通り、ArrowクラスはCategoryクラスを継承しています。

また、そのインスタンスは`arr`という関数を実装する必要があり、`arr (f >>> g) = arr f >> arr g` を満たす必要があります。

これを言い換えると、

- Arrowクラスのインスタンスは圏である
- Hask圏の射を`arr`でArrowのインスタンスである圏の射に対応付ける
- Hask圏の対象の型はArrowのインスタンスである圏の同じ型に対応づける
- この対応付けは関手則を満たす

というこうとが要求されているとわかります。
この、Hask圏からの関手を`GeneralFunctor`クラスで表現してみましょう。

```hs:Sample.hs
data Arr (a :: * -> * -> *) = Arr
instance Arrow a => GeneralFunctor (Arr a) (->) a where
    type F (Arr a) x = x
    gmap _ = arr
```

```hs:ghci
ghci> let kl = gmap (Arr :: Arr (Kleisli Maybe)) (* 7)

ghci> :t kl
Num a => Kleisli Maybe a a

ghci> runKleisli kl 6
Just 42
```

良さそうですね。

ghciでは例として、Hask圏からKleisli圏への関手を`GeneralFunctor`クラスで表現して、射の対応付けを`gmap` を用いて行っています。

### 対象が一つの圏
つぎは、対象のカインドが`*`ではないような圏についても考えてみましょう。

射と射の合成を適当に定義すれば、**任意のモノイドを「対象が一つの圏」として表現できます**。

圏の公理からわかるように、対象が一つしかない圏は、射の合成が結合則を満たし恒等射が単位元とみなせるからです。

では、Haskellでそのような「対象が一つの圏」を定義してみましょう。

```hs:Sample.hs
{-# LANGUAGE DataKinds #-}
data Unit = U

data As (m :: *) (a :: Unit) (b :: Unit) = As {toMonoid :: m} deriving (Show, Eq)

instance Monoid m => Category (As m) where
    (.) (As m1) (As m2) = As $ m1 <> m2
    id = As mempty

as :: Monoid m => m -> As m U U
as = As
```

こんな感じですね。

対象がただ一つであることを表現したかったので、`Unit`というカインドを定義して、そのカインドを持つ型は`U`だけになるようにしています。

`as`関数は幽霊型となっている`a`と`b`をいちいち指定するのが面倒くさいので作りました。

ところで、**列（HaskellにおいてはList）は自由モノイド**なので、対象が一つで射がモノイドの列であるような圏は、対象が一つであり射がそのモノイドである圏への関手が存在するはずです。

そのような関手を`GeneralFunctor`クラスで表現します。

```hs:Sample.hs
data ListToAs m = LtoA
instance Monoid m => GeneralFunctor (ListToAs m) (As [m]) (As m) where
    type F (ListToAs m) x = U
    gmap _ (As xs) = As $ mconcat xs
```

```hs:ghci
ghci> gmap (LtoA :: ListToAs (Sum Int)) (as [Sum 2, Sum 3, Sum 10])
As {toMonoid = Sum {getSum = 15}}
```

良い感じですね。

# まとめ
従来のFunctor、関数のArrowへの持ち上げ、モノイドの列の畳み込みといった、さまざまなことが`GeneralFunctor`クラスで表現できることがわかりました。

関手という概念の汎用性の高さと、それを実装できるHaskellという言語の表現力の高さには驚かされますね。

ぜひ、いろんな関手をHaskellで表現して遊んでみてください！

# おまけ
実例のところにちょこちょこコードの断片を貼っていた、Sample.hsの全コードをここに貼っておきます。

```hs:Sample.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}

import Prelude hiding (id,(.))
import Data.Monoid
import Control.Arrow
import Control.Category
import Functor


data EndoFunctor f = EF
instance Functor f => GeneralFunctor (EndoFunctor f) (->) (->) where
    type F (EndoFunctor f) x = f x
    gmap _ = fmap


data Arr (a :: * -> * -> *) = Arr
instance Arrow a => GeneralFunctor (Arr a) (->) a where
    type F (Arr a) x = x
    gmap _ = arr


data Unit = U

data UnitCat (u1 :: Unit) (u2 :: Unit) = UnitCat deriving (Show, Eq)

instance Category UnitCat where
    (.) _ _ = UnitCat
    id = UnitCat


data As (m :: *) (a :: Unit) (b :: Unit) = As {toMonoid :: m} deriving (Show, Eq)

as :: Monoid m => m -> As m U U
as = As

instance Monoid m => Category (As m) where
    (.) (As m1) (As m2) = As $ m1 <> m2
    id = As mempty

data ListToAs m = LtoA

instance Monoid m => GeneralFunctor (ListToAs m) (As [m]) (As m) where
    type F (ListToAs m) x = U
    gmap _ (As xs) = As $ mconcat xs
```

# 追記
この記事を読んで下さった makoraru さんから [Data.Category.Functor](https://hackage.haskell.org/package/data-category-0.7/docs/Data-Category-Functor.html) というモジュールのことを教えて頂きました。

ここに定義されている`Functor`は、今回私が定義したのと同じように関手の名前`ftag`を`Functor`クラスのインスタンスとすることで、メソッド`(%)`により射の対応付け、型族`(:%)`で対象の対応付けをしています。

`MultiParamClasses`を使って関手の名前と関手で移る前後の圏をまとめてインスタンス化している私の`GeneralFunctor`より見た目が綺麗ですね。

ただし、型族`(:%)`は Poly Kind に定義されていないので、対象が`*`以外の圏への関手はこのモジュールを使って書くことはできなさそうです。


このモジュールに定義されている様々な`Functor`を自分の`GeneralFunctor`を使って実装できないか試してみようと思います。