Applicativeの双対

\gdef\Set{\mathrm{\mathbf{Set}}} \gdef\Hask{\mathrm{\mathbf{Hask}}} \gdef\Vect{\mathrm{\mathbf{Vect}}} \gdef\Mon{\mathrm{\mathbf{Mon}}} \gdef\id{\mathrm{id}} \gdef\map{\mathrm{map}} \gdef\op#1{{#1}^{\mathrm{\scriptsize op}}} \gdef\mathcode#1{\raisebox{-0.25em}{\colorbox{eeeeee}{\tt {#1}}}} \gdef\ihom{\operatorname*{hom}} \gdef\hombr#1#2{\left\lbrack {#1},{#2} \right\rbrack} \gdef\blank{\mathord{-}} \gdef\blank2{\mathord{=}}

この記事は以前に公開したGistのまとめ直し1です。

ComonadがあるならCoapplicativeは無いのか?」

Comonadという型クラスを知れば「ComonadがあるならCoapplicativeは無いのか?」 という素朴な疑問が出てくることは自然な流れのようです。

Coapplicative」すなわち「Applicativeの双対」という名前から考えられる型クラスはなんだろうか? それには便利な利用法があるだろうか?という検討をしたHaskellerは何人もいたようです。私が見つけたものを挙げると、 (実質的には重複したものを除いて、)以下の3箇所でそれぞれ異なるCoapplicativeの考案がされていました。

注意: このリストを作るに際して、時系列あるいは各アイデアの初出などは確認しておりません。

  1. Co-Applicative programming style (Haskell for all, by Gabriella Gonzalez

    • ここではcontravariantパッケージにあるDivisibleがco-applicativeに相当するのだ、と紹介されています。

      class Contravariant f => Divisible f where
          divide :: (a -> (b,c)) -> f b -> f c -> f a
          conquer :: f a
  2. Reddit post

    • Coapplicativeに相当するものは何か?という議論がr/haskellでもありました。OP(投稿者)は

      -- Poster (u/tailcalled) proposed
      class (Functor f) => Coapplicative f where
       copure :: f a -> a
       cozip :: f (Either a b) -> Either (f a) (f b)

      と定義して考えています。コメントでは、また別の定義として

      -- Comment by u/camcann
      class (Contravariant f) => Inapplicative f where
          nil :: f Void
          contrazip :: (f a, f b) -> f (Either a b)

      も提案されました。このInapplicativeはcontravariantのDecidableと同等です。

  3. Is there a concept of something like co-applicative functors sitting between comonads and functors? - StackOverflow

    ベストアンサーは、CoApplicativeCoMonoidalを以下のように定義し、それらがComonadと関係しないことを説明しています。つまり、質問者が想像したような、「ComonadFunctorの間にある型クラス」にはならない、としました。

    2つの異なる定義を考えているのは、ApplicativeMonoidal偶然にも同じ型クラスを定義しているが、 そのアイデアは異なるから、それぞれの双対は一致するとは限らない、としているためです。

    class Functor f => Applicative f where
        pure  :: a -> f a
        (<*>) :: f (a -> b) -> (f a -> f b)
    
    class Functor f => Monoidal f where
        -- equivalent to Applicative
        unit :: () -> f ()
        zip  :: (f a, f b) -> f (a,b)
    
    class Functor f => CoApplicative f where
        copure :: f a -> a
        coap   :: (f a -> f b) -> f (a -> b)
    
    class Functor f => CoMonoidal f where
        -- NOT equivalent to CoApplicative
        counit :: f () -> ()
        cozip :: f (a,b) -> (f a, f b)

    別の回答者は以下の Decisive を挙げています。 残念ながら、参照先として書かれたリンクは切れており、詳細はよくわかりませんでした。

    • http://sneezy.cs.nott.ac.uk/fplunch/weblog/?p=69
    class Functor f => Decisive f where
        nogood :: f Void -> Void
        orwell :: f (Either s t) -> Either (f s) (f t)

    (この型クラスは2. でも挙げられていました。)

違いを整理する

参考にしたStackOverflowの回答者も言っているように、Applicativeにはいろいろな定義の仕方があり、それによって双対がどんな定義になるのかが変わります。また、定義のどこを変更することを「双対」と呼んでいるのかもさまざまです。

Applicativeには、以下の3通りの同値な定義を与えることができます。

  • Functorたちの圏において、Dayを対象のモノイド積とみなしたときのモノイド対象のこと
  • Laxモノイダル関手 (\Hask, \mathcode{(,)}) \to (\Hask, \mathcode{(,)}) のこと
  • Lax閉関手 (\Hask, \mathcode{->}) \to (\Hask, \mathcode{->}) のこと

上記の「Applicativeの双対の候補」たちがApplicativeをどんな抽象化だと考え、何を変更したのかを、これらの定義をもとに検討します。

Applicativeを関手圏のモノイド対象と見たとき

Applicativeを関手圏のモノイド対象とみなすことについては、過去の記事でも取りあげています。 この過去記事で紹介しているように、1. や 2. で”Coapplicative”と呼ばれているcontravariantパッケージのDivisibleクラスとDecidableクラスは、Contravariantたちのなす関手圏において、適切なモノイド積を考えたときのモノイド対象になっていました。

名称 Applicative 変更点
Divisible モノイド対象 どのモノイド圏か
Decidable モノイド対象 どのモノイド圏か

また、手前味噌ですが、私は以前”モノイドの双対”といえばコモノイドだろうという発想から、Functorの圏においてDayをモノイド積としたときのコモノイド対象についても検討しました。Haskellでの定義だけざっと書くと、以下のような型クラスです。

data Day f g x where
    Day :: f a -> g b -> (a -> b -> x) -> Day f g x

class Comonad f => Comonoid f where
--  extract :: f a -> a
    coapply :: f a -> Day f f a

詳細はこちらをどうぞ: Data.Functor.Day.Comonoid

名称 Applicative 変更点
Comonoid モノイド対象 同じモノイド圏のコモノイド対象にする

Applicativeをlaxモノイダル関手と見たとき

Laxモノイダル関手2とは、モノイド圏(C,\otimes_C)から(D,\otimes_D)への関手Fで、モノイド圏としての構造を保つ自然変換unit, multを持つようなものです。3

\begin{align*} & \mathrm{unit} \mathrel{\colon} I_D \to F(I_C) \\ & \mathrm{mult} \mathrel{\colon} F(a) \mathrel{\otimes_D} F(b) \to F(a \otimes_C b) \end{align*}

さらなる条件として、このunit, multがどちらも自然な同型でもある関手を、強モノイダル関手といいます。

Laxモノイダル関手の双対版として、oplaxモノイダル関手というものもあります。これは、unit, multの代わりに、射の向きを逆にしたもの、すなわち

\begin{align*} & \mathrm{opunit} \mathrel{\colon} F(I_C) \to I_D \\ & \mathrm{opmult} \mathrel{\colon} F(a \otimes_C b) \to F(a) \mathrel{\otimes_D} F(b) \end{align*}

を持つという条件を満たす関手のことを指します。例えば、強モノイダル関手はunit, multがそれぞれ同型なのでその逆射があり、これによって強モノイダル関手はoplaxモノイダル関手でもあります。

StackOverflowの回答で挙げられたCoMonoidalクラスは、 上記のlaxモノイダル関手をoplaxモノイダル関手に読み替えて得られます。

名称 Applicative 変更点
CoMonoidal laxモノイダル関手(\Hask, \mathcode{(,)}) \to (\Hask, \mathcode{(,)}) laxをoplaxに

余談ですが、このCoMonoidalは「つまらない」クラスです。任意のFunctor fは以下のようにCoMonoidalになります。

instance Functor f => CoMonoidal f where
    counit :: f () -> ()
    counit = const ()

    cozip :: f (a,b) -> (f a, f b)
    cozip fab = (fst <$> fab, snd <$> fab)

更に、(oplaxモノイダル関手と見るならば満たされるべき)CoMonoidal則によって、 任意のCoMonoidalはこの実装と一致しなければならないことが示せます。

また、詳細のわからなかったDecisive型クラスも、メソッドの型だけを見れば、oplaxモノイダル関手(\Hask, \mathcode{Either}) \to (\Hask,\mathcode{Either})に相当します。

名称 Applicative 変更点
Decisive laxモノイダル関手(\Hask, \mathcode{(,)}) \to (\Hask, \mathcode{(,)}) laxをoplaxに、モノイド積を(,)からEither

謎のCoApplicative

このセクションは自分が勉強したばかりの内容が含まれていて、間違いが含まれるかもしれません!

3.のCoApplicativeは、Applicative(<*>) :: f (a -> b) -> f a -> f bを、 「関数型(->)Functor fを分配する」というように読んだ上で、その逆方向のcoap :: (f a -> f b) -> f (a -> b)を持つ型クラスを考えていると見ることができます。

「関数型(->)Functor fを分配することができる」という性質を一般化したものには、lax閉関手というものがあるようです。そこで、“oplax”閉関手というものがあって、CoApplicativeとなるだろうか?と考えたくなります。しかし、「oplax閉関手」というものが明確に定義されたことはなさそうであり、うまく行きそうな定義を考えるのも簡単ではなさそうでした。

より詳しく説明すると、閉関手(closed functor) 4とは、閉圏(closed category)5の構造を保つ関手です。ちょうど、モノイダル関手がモノイド圏の構造を保つ関手であるようなものです。

ここで、閉圏とは、大まかに言えば内部ホム関手\ihom \colon \op{C} \times C \to Cを持つ圏のことです。特に、モノイド閉圏は閉圏になります。

モノイダル関手と同様に、閉関手にもlax閉関手と強閉関手(strong closed functor)の区別が考えられます。

Applicativeは、lax閉関手(\Hask, \ihom = \mathcode{(->)}) \to (\Hask, \ihom = \mathcode{(->)}) と見なすこともできます。(\Hask, \mathcode{(,)}, \mathcode{(->)})はモノイド閉圏であることに加えて、 モノイド閉圏からモノイド閉圏への関手Fがlaxモノイダル関手であることとlax閉関手であることは同値だからです。

一方で、先述したように、“oplax閉関手”なるものがあるのかどうかすら、私にはわかりませんでした。 結局、3.のCoApplicativeは「“oplax閉関手”かもしれない、謎の何か」としか言いようがありません。

名称 Applicative 変更点
CoApplicative lax閉関手(\Hask, \mathcode{(->)}) \to (\Hask, \mathcode{(->)}) (もしかすると)oplax閉関手(?)

まとめ

名称 Applicative 変更点
Divisible モノイド対象 どのモノイド圏か
Decidable モノイド対象 どのモノイド圏か
Comonoid モノイド対象 同じモノイド圏のコモノイド対象にする
CoMonoidal laxモノイダル関手(\Hask, \mathcode{(,)}) \to (\Hask, \mathcode{(,)}) laxをoplaxに
Decisive laxモノイダル関手(\Hask, \mathcode{(,)}) \to (\Hask, \mathcode{(,)}) laxをoplaxに、モノイド積を(,)からEither
CoApplicative lax閉関手(\Hask, \mathcode{(->)}) \to (\Hask, \mathcode{(->)}) (もしかすると)oplax閉関手(?)

  1. https://gist.github.com/viercc/af7a1ad114c2101a0a2983a37673cf26↩︎

  2. https://ncatlab.org/nlab/show/monoidal+functor↩︎

  3. かなり前に触れました。↩︎

  4. https://ncatlab.org/nlab/show/closed+functor↩︎

  5. https://ncatlab.org/nlab/show/closed+category↩︎