Cofree
データ型の定義を要約してみましょう。
data Cofree f a = a :< f (Cofree f a)
少なくとも、例の問題を診断するにはこれで十分です。あなたが書いたとき
1 :< [2, 3]
あなたは小さな間違いを犯しました。ここで、f = []
とa
は数値です1 :: a
。それに応じて必要です
[2, 3] :: [Cofree [] a]
それゆえ
2 :: Cofree [] a
のインスタンスでもある場合、これは問題ありません。したがって、定義は満たされる可能性が低い制約を取得し、実際、値を使用すると、制約を満たす試みは失敗します。Cofree [] a
Num
で再試行してください
1 :< [2 :< [], 3 :< []]
運が良くなるはずです。
では、何があるか見てみましょう。シンプルに保つことから始めます。なにCofree f ()
?特に、とはCofree [] ()
何ですか?後者は の固定点に同形です[]
: 各ノードがサブツリーのリストであるツリー構造で、「ラベルのないバラの木」としても知られています。例えば、
() :< [ () :< [ () :< []
, () :< []
]
, () :< []
]
同様に、Cofree Maybe ()
は多かれ少なかれ の固定点Maybe
: 自然数のコピーですMaybe
。サブツリーを差し込む位置がゼロまたは 1 つになるためです。
zero :: Cofree Maybe ()
zero = () :< Nothing
succ :: Cofree Maybe () -> Cofree Maybe ()
succ n = () :< Just n
重要な自明なケースはCofree (Const y) ()
で、これは のコピーですy
。Const y
ファンクターはサブツリーの位置を与えません。
pack :: y -> Cofree (Const y) ()
pack y = () :< Const y
次に、もう一方のパラメーターに取り掛かりましょう。各ノードに付けるラベルの種類がわかります。パラメータの名前をより暗示的に変更する
data Cofree nodeOf label = label :< nodeOf (Cofree nodeOf label)
(Const y)
例にラベルを付けると、ペアが得られます
pair :: x -> y -> Cofree (Const y) x
pair x y = x :< Const y
数値のノードにラベルを付けると、空でないリストが得られます
one :: x -> Cofree Maybe x
one = x :< Nothing
cons :: x -> Cofree Maybe x -> Cofree Maybe x
cons x xs = x :< Just xs
リストの場合は、ラベルの付いたバラの木を取得します。
0 :< [ 1 :< [ 3 :< []
, 4 :< []
]
, 2 :< []
]
これらの構造は、たとえ子がなくても、少なくとも最上位ノードがあり、そのノードには常にラベルがあるため、常に「空ではありません」。このextract
操作により、最上位ノードのラベルが得られます。
extract :: Cofree f a -> a
extract (a :< _) = a
つまり、トップ ラベルのコンテキストextract
を破棄します。
これで、duplicate
操作によってすべてのラベルが独自のコンテキストで装飾されます。
duplicate :: Cofree f a -> Cofree f (Cofree f a)
duplicate a :< fca = (a :< fca) :< fmap duplicate fca -- f's fmap
ツリー全体にアクセスすることでFunctor
インスタンスを取得できますCofree f
fmap :: (a -> b) -> Cofree f a -> Cofree f b
fmap g (a :< fca) = g a :< fmap (fmap g) fca
-- ^^^^ ^^^^
-- f's fmap ||||
-- (Cofree f)'s fmap, used recursively
それを見るのは難しいことではありません
fmap extract . duplicate = id
duplicate
すべてのノードをそのコンテキストで装飾し、装飾を破棄するためfmap extract
です。
fmap
出力のラベルを計算するには、入力のラベルのみを参照する必要があることに注意してください。コンテキスト内の各入力ラベルに応じて出力ラベルを計算したいとします。たとえば、ラベルのないツリーが与えられた場合、各ノードにそのサブツリー全体のサイズでラベルを付けたい場合があります。Foldable
のインスタンスのおかげで、Cofree f
ノードをカウントできるはずです。
length :: Foldable f => Cofree f a -> Int
つまり、
fmap length . duplicate :: Cofree f a -> Cofree f Int
comonads の重要なアイデアは、「何らかのコンテキストを持つもの」をキャプチャし、コンテキスト依存のマップをどこにでも適用できるようにすることです。
extend :: Comonad c => (c a -> b) -> c a -> c b
extend f = fmap f -- context-dependent map everywhere
. -- after
duplicate -- decorating everything with its context
より直接的に定義するextend
ことで、重複の問題を回避できます (ただし、それは共有にすぎません)。
extend :: (Cofree f a -> b) -> Cofree f a -> Cofree f b
extend g ca@(_ :< fca) = g ca :< fmap (extend g) fca
そして、あなたは取るduplicate
ことによって戻ることができます
duplicate = extend id -- the output label is the input label in its context
extract
さらに、コンテキスト内の各ラベルに対して行うこととして選択した場合は、各ラベルを元の場所に戻すだけです。
extend extract = id
これらの「コンテキスト内のラベルに対する操作」は、「co-Kleisli 矢印」と呼ばれます。
g :: c a -> b
の仕事はextend
、co-Kleisli アローを構造全体の関数として解釈することです。extract
演算は恒等共クライスリの矢であり、 によって恒等関数として解釈されextend
ます。もちろんコ・クライスリ構成もある
(=<=) :: Comonad c => (c s -> t) -> (c r -> s) -> (c r -> t)
(g =<= h) = g . extend h
また、コモナドの法則により、=<=
が 結合的で吸収されることが保証されextract
、co-Kleisli カテゴリが得られます。さらに、
extend (g =<= h) = extend g . extend h
これextend
は、co-Kleisli 圏から集合関数への (圏論的な意味での)関手です。これらの法則は、ノード形状Cofree
の法則に従うため、チェックするのは難しくありません。Functor
cofree comonad の構造を理解するための 1 つの便利な方法は、一種の「ゲームサーバー」です。構造
a :< fca
ゲームの状態を表します。ゲーム内の移動は、 を取得する場合は「停止」するa
か、 のサブツリーを選択して「続行」するかのいずれかで構成されますfca
。たとえば、
Cofree ((->) move) prize
このサーバーのクライアントは、停止するか、またはmove
: のリストを指定して続行する必要がありますmove
。ゲームは次のように行われます。
play :: [move] -> Cofree ((->) move) prize -> prize
play [] (prize :< _) = prize
play (m : ms) (_ :< f) = play ms (f m)
おそらく amove
は aChar
であり、 theprize
は文字シーケンスを解析した結果です。
[move]
よく見ると、それが のバージョンであることがわかりますFree ((,) move) ()
。フリーモナドはクライアント戦略を表します。ファンクター((,) move)
は、コマンド「send a move
」のみを備えたコマンドインターフェイスになります。functor((->) move)
は、対応する構造体である "の送信に応答するmove
" です。
一部のファンクタは、コマンド インターフェイスをキャプチャしていると見なすことができます。そのようなファンクタの自由なモナドは、コマンドを作成するプログラムを表します。ファンクターには、コマンドへの応答方法を表す「デュアル」があります。双対の cofree comonad は、コマンドを作成するプログラムを実行できる環境の一般的な概念であり、ラベルは、プログラムが停止して値を返した場合に何をすべきかを示し、下位構造は、次の場合にプログラムの実行を続行する方法を示します。コマンドを発行します。
例えば、
data Comms x = Send Char x | Receive (Char -> x)
文字の送受信が許可されていることを示します。その二重は
data Responder x = Resp {ifSend :: Char -> x, ifReceive :: (Char, x)}
演習として、相互作用を実装できるかどうかを確認します
chatter :: Free Comms x -> Cofree Responder y -> (x, y)