在Haskell中Cofree CoMonad有哪些激励性的例子?

时间:2016-08-07 17:59:45

标签: haskell monads free-monad comonad

我一直在玩Cofree,并且无法理解它。

例如,我想在ghci中使用Cofree [] Num并且无法获得任何有趣的示例。

例如,如果我构造一个Cofree类型:

let a = 1 :< [2, 3]

我希望extract a == 1,但我得到了这个错误:

No instance for (Num (Cofree [] a0)) arising from a use of ‘it’
    In the first argument of ‘print’, namely ‘it’
    In a stmt of an interactive GHCi command: print it

还有一种:

extract a :: (Num a, Num (Cofree [] a)) => a

我可以得到一些简单的例子,甚至是微不足道的,例如,如何使用Cofree,例如,函数[],或Maybe,或Either来演示

  • extract
  • extend
  • unwrap
  • duplicate

Cross Cross:https://www.reddit.com/r/haskell/comments/4wlw70/what_are_some_motivating_examples_for_cofree/

编辑:在David Young的评论的指导下,这里有一些更好的例子,显示我的第一次尝试被误导的地方,但是我仍然喜欢一些可以指导Cofree直觉的例子:

> let a = 1 :< []
> extract a
    1
> let b = 1 :< [(2 :< []), (3 :< [])]
> extract b
    1
> unwrap b
    [2 :< [],3 :< []]
> map extract $ unwrap b
    [2,3]

1 个答案:

答案 0 :(得分:53)

让我们回顾一下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 :< []]

你应该有更好的运气。

现在,让我们看看我们得到了什么。首先要保持简单。什么&#39; Cofree f ()?特别是Cofree [] ()是什么?后者与[]的固定点同构:树结构,其中每个节点是一个子树列表,也称为&#34;未标记的玫瑰树&#34;。如,

() :< [  () :< [  () :< []
               ,  () :< []
               ]
      ,  () :< []
      ]

同样,Cofree Maybe ()或多或少是Maybe的固定点:自然数的副本,因为Maybe给我们插入子树的零或一个位置。

zero :: Cofree Maybe ()
zero = () :< Nothing
succ :: Cofree Maybe () -> Cofree Maybe ()
succ n = () :< Just n

一个重要的小问题是Cofree (Const y) (),它是y的副本。 Const y仿函数为子树提供 no 位置。

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 :< []
     ]

这些结构总是&#34;非空&#34;,因为至少有一个顶级节点,即使它没有子节点,并且该节点将始终具有标签。 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的{​​{1}}个实例

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的{​​{1}}实例,我们应该可以使用。

计算节点
Foldable

这意味着

Cofree f

comonads的关键思想是它们捕获具有某些上下文的东西,并且它们允许您在任何地方应用依赖于上下文的映射。

length :: Foldable f => Cofree f a -> Int

更直接地定义fmap length . duplicate :: Cofree f a -> Cofree f Int 可以省去重复的麻烦(尽管这只是分享)。

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

这些&#34;在上下文标签上的操作&#34;被称为&#34; co-Kleisli arrows&#34;,

extract

并且extend extract = id 的工作是将共同Kleisli箭头解释为整个结构的函数。 g :: c a -> b 操作是身份co-Kleisli箭头,它由extend解释为身份函数。当然,还有一个合作的Kleisli作品

extract

并且comonad法律确保extend是关联的并且吸收(=<=) :: Comonad c => (c s -> t) -> (c r -> s) -> (c r -> t) (g =<= h) = g . extend h ,给我们共同的Kleisli类别。而且我们有

=<=

这样extract是一个仿函数(在分类意义上)从co-Kleisli类别到集合和函数。这些法律并不难检查extend (g =<= h) = extend g . extend h ,因为它们遵循节点形状的extend定律。

现在,在cofree comonad中查看结构的一种有用方法是作为一种游戏服务器&#34;。结构

Cofree

表示游戏的状态。通过选择{{1}的子树,游戏中的移动包括&#34;停止&#34;,在这种情况下,您获得Functor,或者#34;继续&#34; }。例如,考虑

a :< fca

此服务器的客户端必须停止或继续提供a:它是fca列表。游戏如下:

Cofree ((->) move) prize

也许movemoveplay :: [move] -> Cofree ((->) move) prize -> prize play [] (prize :< _) = prize play (m : ms) (_ :< f) = play ms (f m) 是解析字符序列的结果。

如果你足够努力,你会发现moveChar的版本。免费monad代表客户策略。仿函数prize相当于命令界面,只有命令&#34;发送[move]&#34;。仿函数Free ((,) move) ()是相应的结构&#34;响应发送((,) move)&#34;。

一些仿函数可以被视为捕获命令界面;这种仿函数的免费monad代表了制作命令的程序;仿函数将有一个&#34;对偶&#34;它代表如何响应命令;双重的cofree comonad是环境的一般概念,其中可以运行生成命令的程序,标签说明如果程序停止并返回值该怎么做,子结构说如何继续运行程序它会发出命令。

例如,

move

描述允许发送或接收字符。它的双重是

((->) move)

作为练习,看看您是否可以实施互动

move