我一直在玩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
吗? 交叉发布: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]
最佳答案
让我们回顾一下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
给我们提供了零位或一个位置来插入子树。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
对于 list ,我们得到了标有玫瑰树的标签。
0 :< [ 1 :< [ 3 :< []
, 4 :< []
]
, 2 :< []
]
这些结构始终是“非空”的,因为即使没有子节点,也至少有一个顶部节点,并且该节点将始终具有标签。
extract
操作为您提供顶部节点的标签。extract :: Cofree f a -> a
extract (a :< _) = a
也就是说,
extract
丢弃了top标签的上下文。现在,
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
操作是身份共Kleisli箭头,由extend
解释为身份函数。当然,有一个共同的克莱斯里组合(=<=) :: Comonad c => (c s -> t) -> (c r -> s) -> (c r -> t)
(g =<= h) = g . extend h
comonad法则确保
=<=
具有关联性并吸收extract
,从而为我们提供了共同Kleisli类别。而且我们有extend (g =<= h) = extend g . extend h
因此
extend
是从co-Kleisli类别到set-and-functions的函子(从类别意义上来说)。这些定律不难检查Cofree
,因为它们遵循节点形状的Functor
定律。现在,在cofree社区中查看结构的一种有用方法是作为一种“游戏服务器”。结构
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)
也许
move
是Char
,而prize
是解析字符序列的结果。如果您足够凝视,您会看到
[move]
是Free ((,) move) ()
的版本。免费的monad代表客户策略。函子((,) move)
相当于一个命令界面,仅命令“send a move
”。函子((->) move)
是相应的结构“响应move
的发送”。一些函子可以看作是捕获命令界面。此类函子的免费monad表示发出命令的程序;函子将具有一个“双重”,代表如何响应命令;对偶的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)
关于haskell - Haskell中的Cofree CoMonad有哪些激励性的例子?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38816993/