YonedaとCoYoneda、そしてFunctor

本当は、

Freeモナドを超えた!?operationalモナドを使ってみよう
http://fumieval.hatenablog.com/entry/2013/05/09/223604

に影響されて、Operationalモナドの話をまとめようと思ったのですが、ちょっと時間なさそうだったので、今日はちょっとCoYonedaの話をしましょう。
上記ブログでは、CoYonedaについて「ただのデータ型からFunctorを生み出せる」と紹介されています、これがいったいどういう事か、ちょっと深く追ってみましょう。

初めに

今回は、任意の型をFunctorにする事が目標なので、まず簡単に以下のような型を定義しておきます。

data Hoge a = Foo a | Bar a deriving Show

米田先生とYoneda

Yonedaというのは米田信夫という日本の数学者の名に因んだ「米田の補題」と関係のある型・・・なんだと思います。
「思います。」というのは、単にブログ主が米田の補題を理解してない*1だけの話なのですが・・・

さて、ekmett氏によるcategory-extrasというモジュールの、Control.Functor.Yonedaモジュールに、Yonedaというデータ型が以下のように定義されています。

{-# LANGUAGE ExistentialQuantification, RankNTypes #-}

data Yoneda f x = Yoneda { runYoneda :: forall b. (x -> b) -> f b }

YonedaはFunctorです。では、Yonedaをmapするというのはどういう事でしょうか。
これは、runYonedaの型がどう移り変わるか考えると、「「始域の関数」の始域」を書きかえる事になるのがわかります。

m :: Yoneda f x
k :: x -> y

とした時

m        :: Yoneda f x
fmap k m :: Yoneda f y
                     ^

つまり

runYoneda          m :: (x -> b) -> f b 
runYoneda $ fmap k m :: (y -> b) -> f b
                         ^

そのため、Yoneda型のfmapは次のように定義されています。

instance Functor (Yoneda f) where
  fmap f m = Yoneda $ \k -> runYoneda m (k . f)

以下のようにすると、実際に「「始域の関数」の始域」がfmapする度に変わっていく様子がわかります。

runYoneda                                    $ Yoneda (\k -> Foo (k 5)) :: Num x =>           (x      -> b) -> Hoge b
runYoneda                        . fmap show $ Yoneda (\k -> Foo (k 5)) ::                    (String -> b) -> Hoge b
runYoneda            . fmap read . fmap show $ Yoneda (\k -> Foo (k 5)) :: Read x =>          (x      -> b) -> Hoge b
runYoneda . fmap (*2). fmap read . fmap show $ Yoneda (\k -> Foo (k 5)) :: (Num x, Read x) => (x      -> b) -> Hoge b

Yonedaの双対CoYoneda

さて、今度はYonedaの双対を考えてみます。
基本的には関数の矢印の向きを逆にすれば良いので、runYonedaに対応するrunCoYonedaは次のように考えれば良いですね。

runYoneda runCoYoneda
(x -> b) -> f b f b -> (b -> x)

これを元に、CoYoneda型と、そのFunctorを定義すると次のような感じになります。

data CoYoneda f x = CoYoneda { runCoYoneda :: forall b. f b -> (b -> x) }

instance Functor (CoYoneda f) where
  fmap f (CoYoneda g) = CoYoneda $ \x d -> f $ g x d

CoYonedaのfmapは、「「終域の関数」の終域」を書きかえる事になります。

m :: CoYoneda f x
k :: x -> y

とした時

m        :: CoYoneda f x
fmap k m :: CoYoneda f y
                       ^

つまり

runCoYoneda          m :: f b -> (b -> x)
runCoYoneda $ fmap k m :: f b -> (b -> y)
                                       ^

えー、実際に書いてみるとよく解ると思うんですが、このCoYonedaの定義はスッゲー使いづらいです。
このまんまだとちょっと使い物にはならなさそうなので、形を変えてみましょう。

f b -> (b -> x)という型は、次のように考える事ができます。

(->) (f b) (b -> x)

この、(->)をデータコンストラクタCoYonedaに置き換えてみましょう。

CoYoneda (f b) (b -> x)

つまり、CoYonedaとは、f bという値と、(b -> x)という関数のペアと考える事ができます。*2 *3
ここで、fmapによって(b -> x)の終域を書きかえるのは簡単です。(x -> y)という関数と関数合成すれば(b -> y)という型を得ることができます。

従って、CoYonedaの型と、Functorの定義は次のようになります。

data CoYoneda f x = forall b. CoYoneda (f b) (b -> x)

instance Functor (CoYoneda f) where
  fmap f (CoYoneda v g) = CoYoneda v (f . g)

こうすれば、任意の型をCoYoneda型に持ち上げるliftCoYonedaの定義は簡単です。

liftCoYoneda :: f a -> CoYoneda f a
liftCoYoneda x = CoYoneda x id

元の定義から(f b)と(b -> x)を入れ替えると、liftCoYonedaの定義はもっと簡単になります。

{-# LANGUAGE ExistentialQuantification #-}

data CoYoneda f x = forall b. CoYoneda (b -> x) (f b)

instance Functor (CoYoneda f) where
  fmap f (CoYoneda g v) = CoYoneda (f . g) v

liftCoYoneda :: f a -> CoYoneda f a
liftCoYoneda = CoYoneda id

というかこれなら、liftCoYonedaの定義は不要なくらいですね・・・実際にこのCoYonedaはcategory-extrasに定義されているものと同じです。
この改訂版CoYoneda型はなかなか優秀です。まず値を作るのが簡単なのが良いですね。

CoYoneda id (Foo 5)      :: Num x => CoYoneda Hoge x
CoYoneda id (Bar "Hoge") :: CoYoneda Hoge [Char]

CoYonedaはFunctorなので、当然fmapできます。

let foo5 =  CoYoneda id (Foo 5) :: CoYoneda Hoge Integer

として

fmap show foo5 :: CoYoneda Hoge String

さて、ここからが問題です。
CoYoneda Hoge aから、Hoge aだけ取り出すにはどうすれば良いでしょうか?

えー・・・

結局ですね、パターンマッチしてデータコンストラクタの数だけCoYonedaを剥がす計算を書いてやる必要があるんですね・・・

runFoo :: CoYoneda Hoge a -> Hoge a
runFoo (CoYoneda f (Foo a)) = Foo (f a)
runFoo (CoYoneda f (Bar a)) = Bar (f a)

なんやー(´・ω・`)これなら普通にHogeをFunctorにしたほうがえーやんなー・・・

と、思いません?
僕は思います。





もし、Freeモナドを知らなければね




Free+CoYoneda→Operational

Freeモナドについて詳しい事は、ブログ主の過去の記事とか、@fumievalさんの記事でも参照してください。
任意のFunctorをモナドにする事ができ、それによって簡単に言語内DSLが作れるすぐれものでしたね。

何も無い所からFunctorを作るのがCoYoneda、Functorからモナドを作るのがFreeモナド、では、この二つを組み合わせたらどーなるのっと。

type Program f a = Free (CoYoneda f) a
data MyPg a = Order1 a | Order2 a

type MyProgram a = Program MyPg a

singleton :: f a -> Program f a
singleton = liftF . liftCoYoneda

order1 = singleton $ Order1 () :: MyProgram ()
order2 = singleton $ Order2 () :: MyProgram ()

runMyPg :: MyProgram a -> IO a
runMyPg (Free (CoYoneda f o)) = runOrder o >>= runMyPg . f
  where
    runOrder :: MyPg a -> IO a
    runOrder (Order1 n) = putStrLn "Order1 Called!!" >> return n
    runOrder (Order2 n) = putStrLn "Order2 Called!!" >> return n
runMyPg (Pure a) = return a

----
-- Test --

program :: MyProgram ()
program = do
  order1
  order2
  order2
  order1

main :: IO ()
main = runMyPg program

実行結果:

Order1 Called!!
Order2 Called!!
Order2 Called!!
Order1 Called!!

こうなります。
MyPgをFunctorにする事無く、Freeで言語内DSLに仕立て上げてしまいました。

「Freeよりも簡単にモナドが作れる!」が売り文句のOperationalはこんな仕組みらしいです。
ブログ主もいずれ、詳しい事書きますが、「勿体ぶってんじゃねぇよ!!」という方は、冒頭で紹介した@fumievalさんの記事をもう一度記載しますので、参照してください。

Freeモナドを超えた!?operationalモナドを使ってみよう:
http://fumieval.hatenablog.com/entry/2013/05/09/223604

おまけ

「ところでブログ主、category-extrasをインストールしていないらしいけど」
「・・・(黙秘)」
「またcabalの依存関係が解決できなかったんですか?」
「・・・(泣き顔)」
「ザコですね」
「・・・ウッ・・・ウッウッ・・・(号泣)」

*1:圏論コワイ

*2:圏論的にはf bと(b -> x)間の射が(->)かCoYonedaなのかの違いで、基本的には同型なんじゃないかなーと思います。と思ったけど、この間の関手を上手く定義できないので違いますねー

*3:Twitterで軽く話題に出してみたら同型というより随伴という関係っぽい・・・?圏論わからん!