% basic set manipulation
\gdef\id{\mathop{\mathrm{id}}}
\gdef\comp{\circ}
\gdef\placeholder{{-}}
\gdef\setsum{\operatorname{\raisebox{-0.2em}{$\Large\Sigma$}}}
\gdef\setprod{\operatorname{\raisebox{-0.2em}{$\Large\Pi$}}}
% category theory
\gdef\Set{\mathrm{\mathbf{Set}}}
\gdef\Cat{\mathrm{\mathbf{Cat}}}
\gdef\Functors#1{\mathrm{\mathbf{Fun}}\left({#1}\right)}
\gdef\Monads#1{\mathrm{\mathbf{Monads}}\left({#1}\right)}
\gdef\Comonads#1{\mathrm{\mathbf{Comonads}}\left({#1}\right)}
\gdef\CatC{\Cat^\sharp}
\gdef\Ob{\mathop{\mathrm{Ob}}}
\gdef\Mor{\mathop{\mathrm{Mor}}}
\gdef\dom{\mathop{\mathrm{dom}}}
\gdef\cod{\mathop{\mathrm{cod}}}
\gdef\map{\mathop{\mathrm{map}}}
\gdef\binprod{\mathbin{\Pi}}
\gdef\bincoprod{\mathbin{\amalg}}
\gdef\Id{\mathrm{Id}}
% custom notations
\gdef\homset#1#2#3{{#1}\! \left\lbrack {#2}, {#3} \right\rbrack}
\gdef\homsetl#1#2{{#1}\! \left\lbrack {#2} \right\rbrack}
\gdef\asfunctor#1{\left. \llbracket {#1} \rrbracket^{c} \right.}
\gdef\Cont{\mathrm{\mathbf{Cont}}}
\gdef\Poly{\mathrm{\mathbf{Poly}}}
\gdef\PolyFunctor#1{\mathrm{\mathbf{PFun}}\left({#1}\right)}
\gdef\contcomp{\lhd}
\gdef\inl{\operatorname{\mathtt{inl}}}
\gdef\inr{\operatorname{\mathtt{inr}}}
\gdef\divleft#1#2{\frac{{#1}, \_}{{#1}, {#2}}}
\gdef\divright#1#2{\frac{\_, {#2}}{{#1}, {#2}}}
\gdef\rootpos{o}
\gdef\nextshape{\darr}
\gdef\posplus{\oplus}
\gdef\retroarr{\nrightarrow}
\gdef\Travel{\operatorname{\mathrm{Travel}}}
\gdef\Flow{\operatorname{\mathrm{Flow}}}
Travel
: Category
から作るComonad
以前、多項式コモナドと小さい圏の間に対応があるという論文の解説を行いました。 この記事では「小さい圏Cが与えられたとき、対応する多項式コモナドWを構成する方法」があることを暗黙に示しています。 この構成方法に名前をつけて明示的に書くことにしましょう。
- (定義)コモナド\Travel_{C}
-
小さい圏Cに対して、C上のtravelコモナドを次の多項式コモナド\Travel_{C} : \Set \to \Setと定義する。
\begin{align*} & \begin{aligned} & \Travel_{C} : \Set \to \Set \\ & \Travel_{C} X = \setsum (c : \Ob(C)). \left(\homsetl{C}{c} \to X \right) \end{aligned} \\ & \begin{aligned} & \operatorname{extract} : \forall \alpha. \Travel_{C}\alpha \to \alpha \\ & \operatorname{extract} (c, f) = f \id_c \\ \end{aligned} \\ & \begin{aligned} & \operatorname{duplicate} : \forall \alpha. \Travel_{C}\alpha \to \Travel_{C}(\Travel_{C}\alpha)\\ & \operatorname{duplicate} (c, f) = \left(c, \lambda p. (\cod p, \lambda q. f (p;q)) \right) \end{aligned} \end{align*}
さて、Haskellには圏を表す型クラスCategory
がありました。Haskell内でCategory
をもとにComonad
を構成するような
型Travel
は、標準のHaskellではありませんが、GHC拡張をふんだんに使って依存型を表すことで実現可能です。
実際に私が書いてみたものがこちらにあります: polynomial-functors/polynomial-comonad
-- https://github.com/viercc/polynomial-functor/blob/17404603235028fe9b10af72a0eaf96a01dae7c2/polynomial-comonad/src/Control/Comonad/Travel.hs
-- | Make 'Polynomial' 'Comonad' from 'Category'.
--
-- @Travel cat@ is a polynomial functor for any type constructor
-- @cat@.
--
-- @
-- Travel cat r ~ ∑a. ((∑b. cat a b) -> r)
-- @
--
-- @Travel cat@ is a @Comonad@ if @cat@ is a @Category@.
type Travel :: (k -> k -> Type) -> Type -> Type
data Travel (cat :: k -> k -> Type) r where
MkTravel ::
forall k (cat :: k -> k -> Type) (r :: Type) (a :: k).
Sing a ->
Sigma k (TyCon (cat a)) -> r) ->
(Travel cat r
Sing
やSigma
といった型はsingletonsパッケージ由来です。
このsingletonsパッケージは、依存型がはじめから使える言語に存在する「依存積型(\setprod-型)」「依存和型(\setsum-型)」
に相当する型をHaskellで書くために必要な「singleton型」というテクニックに関する便利な機能を提供しています。
例えば、集合の依存積\setprod (i \in I). P(i)に相当する型を以下のProdP
のように表すことができます。
type I :: Type
type P :: I -> Type
-- ProdP = Π(i ∈ I). P(i)
type ProdP = forall (i :: I). Sing i -> P i
Sing
を用いずに以下のように定義してしまうと、依存積になりません。
type ForallP = forall (i :: I). P i
例えば、I = Bool
であり、P
はGADTを使って定義される以下のFoo
である場合を考えます。
data Foo (i :: Bool) where
FooAny :: Foo i
FooFalse :: Foo False
FooTrue :: Foo True
Foo
の依存積はΠ(i ∈ I) Foo(i) ~ (Foo False, Foo True)
です。これは
FooAny, FooAny) :: (Foo False, Foo True)
(FooFalse, FooTrue) :: (Foo False, Foo True) (
のように、第一成分はFooAnyとFooFalseから、第二成分はFooAnyとFooTrueから任意に選んだ組の型です。
しかし、ForallP
のようにforall i. Foo i
という型がつく項はFooAny
しかありません。
Haskellには型変数に対するパターンマッチが存在しないので、型変数i
が実際にとる型に依存する項
というものが書けないのです。しかし、Sing (i :: Bool)
という型
の値レベルの引数に対してはパターンマッチが可能であり、以下のように書くことができます。
-- (FooFalse, FooTrue)に対応する依存積の値をSingを使って表現
prodValue :: forall (i :: Bool). Sing i -> Foo i
= case singI of
prodValue singI SFalse -> FooFalse
STrue -> FooTrue
同じように、集合の依存和\setsum (i \in I). P(i)に相当する型は1
type SumP = Sigma' I P
data Sigma' k t where
(:&:) :: Sing k -> t k -> Sigma' k t
として書くことができます。
Flow
: Category
から作るMonad
Comonad
からMonad
を作ることができるCoという型を以前紹介しました。
再掲すると、以下のように定義されるCo
型を用いると、任意のComonad w
に対してCo w
はMonad
になります。
newtype Co w = Co { runCo :: forall r. w (a -> r) -> r }
instance Functor f => Functor (Co f)
instance Comonad w => Applicative (Co w)
instance Comonad w => Monad (Co w)
前述した通り、Comonad w
が多項式関手でもあった場合、w
は適当なCategory cat
を用いて
Travel cat
と同型なコモナドです。したがって、Co w
はCo (Travel cat)
と同型なモナドです。
では、Co (Travel cat)
とはどのようなモナドなのか?ということが気になります。
Co (Travel cat)
を同型な型に変形させていくと、Flow
モナドを以下のように得ることができます。2
-- Control.Monad.Flow
-- https://github.com/viercc/polynomial-functor/blob/17404603235028fe9b10af72a0eaf96a01dae7c2/polynomial-comonad/src/Control/Monad/Flow.hs
-- より改変
newtype Flow (cat :: k -> k -> Type) x = MkFlow {
runFlow :: forall (a :: k). Sing a -> (x, Sigma k (TyCon (cat a)))
}deriving (Functor)
instance Category cat => Applicative (Flow cat) where
pure x = MkFlow $ \sa -> (x, sa :&: id)
<*>) = ap
(
instance Category cat => Monad (Flow cat) where
>>= k = MkFlow $ \sa ->
ma case runFlow ma sa of
:&: f) -> case runFlow (k y) sb of
(y, sb :&: g) -> (z, sc :&: (f >>> g)) (z, sc
以前の記事で、Co w
というモナドはState
モナドの一般化StateLike m e
と見做せる、と述べました。
特にw
が多項式コモナドであり圏cat
を使って表現できる場合に、Co w
と同型なFlow cat
モナドは、
よりはっきりとState
モナドとの対応をみることが可能です。
モナドState s
において、State s α
という値の実体は、s -> (α, s)
型の関数でした。特に言うと、
State s α
型の値は、現在の状態s0 :: s
が与えられると、そこでのモナドの実行結果x :: α
と実行後の状態s1 :: s
を返す関数でした。
一方、Flow cat
モナドでは、圏cat :: k -> k -> Type
の対象を”状態”、射を”状態遷移の経路”と呼ぶことにすると、Flow cat α
型の値は、現在の”状態” a :: k
が与えられると、そこでのモナドの実行結果x :: α
と、
実行後の状態への”状態遷移の経路” f :: Σ(b :: k). (cat a b)
を返す関数であると考えることができます。
モナド | : | 現在の状態 | → | 実行結果 | 実行後の状態 | 状態遷移の経路 |
---|---|---|---|---|---|---|
State s α |
: | s0 :: s |
→ | x :: α |
s1 :: s |
_ |
Flow cat α |
: | a :: k |
→ | x :: α |
b :: k |
f :: cat a b |
すなわち、Flow cat
モナドは、cat
の対象を状態とするState
モナドのようなものであって、
遷移後の状態b
だけでなく「どう遷移するか」を表すcat
の射f :: cat a b
も持っているものです。
ついでに、State
モナドに対してStateT
モナド変換子があるのと同じ調子で、
Flow
モナドにもFlowT
を考えることができます。
newtype FlowT (cat :: k -> k -> Type) m x = MkFlowT {
runFlowT :: forall (a :: k). Sing a -> m (x, Sigma k (TyCon (cat a)))
}deriving (Functor)
instance (Category cat, Monad m) => Applicative (FlowT cat m) where
pure x = MkFlowT $ \sa -> pure (x, sa :&: id)
<*>) = ap
(
instance (Category cat, Monad m) => Monad (FlowT cat m) where
>>= k = MkFlowT $ \sa ->
mx do (y, sb :&: f) <- runFlowT mx sa
:&: g) <- runFlowT (k y) sb
(z, sc pure (z, sc :&: (f >>> g))
instance (Category cat) => MonadTrans (FlowT cat) where
= MkFlowT $ \sa -> mx <&> \x -> (x, sa :&: id) lift mx
このFlowT
の理論的背景は私はよくわかっていませんが、なんとなく「こうじゃないかな〜」と思っていることが
Flow cat
モナドを随伴に分解する方法のひとつにFlow cat ~ U ∘ F -- U :: (k -> Type) -> Type -- F :: Type -> (k -> Type)
という型になるものがあって、
FlowT
はFlowT cat m ~ U ∘ Compose m ∘ F -- F :: Type -> (k -> Type) -- Compose m :: (k -> Type) -> (k -> Type) -- U :: Type -> (k -> Type)
として
Monad
になっているんではないか?
というものです。
singletonsパッケージでは、ここで示した
Sigma'
より一般化された 定義であるSigmaが用いられています。 これは、依存和をとるt
として、上記の定義では型コンストラクタしか渡すことができないのに対し、 型コンストラクタではない色々な型関数を渡すことができるようにするためです。 singletonsパッケージには型コンストラクタを型関数とみなすTyCon
が用意されていて、Sigma k (TyCon t)
という型が上記のSigma' k t
と全く同じように使えます。↩︎これには先行研究がすでに存在し(Danel Ahman and Tarmo Uustalu, “Update Monads: Cointerpreting Directed Containers,” 2013. )、Updateモナドという名称が付けられていました。 これに全く気付かずに私が勝手に
Flow
と命名してしまっている状態は不本意なので、今後改める可能性がかなりあります。↩︎