通过创建Comonad实例可以获得哪些好处

时间:2014-09-04 23:34:11

标签: haskell comonad

在我的应用程序中,我正在尝试实现动画系统。在此系统中,动画表示为循环帧列表:

data CyclicList a = CL a [a]

我们可以(低效率)推进动画,如下所示:

advance :: CyclicList a -> CyclicList a
advance (CL x []) = CL x []
advance (CL x (z:zs)) = CL z (zs ++ [x])

现在,我很确定这个数据类型是comonad:

instance Functor CyclicList where
  fmap f (CL x xs) = CL (f x) (map f xs)

cyclicFromList :: [a] -> CyclicList a
cyclicFromList [] = error "Cyclic list must have one element!"
cyclicFromList (x:xs) = CL x xs

cyclicLength :: CyclicList a -> Int
cyclicLength (CL _ xs) = length xs + 1

listCycles :: CyclicList a -> [CyclicList a]
listCycles cl = let
  helper 0 _ = []
  helper n cl' = cl' : (helper (n-1) $ advance cl')
 in helper (cyclicLength cl) cl

instance Comonad CyclicList where
  extract (CL x _) = x
  duplicate = cyclicFromList . listCycles

我的问题是:使用comonad实例可以获得什么样的好处(如果有的话)?

1 个答案:

答案 0 :(得分:2)

提供类型类或实现接口的优点是,使用该类型类或接口编写的代码可以使用您的代码而无需任何修改。

可以用Comonad来编写哪些程序? Comonad提供了一种方法,可以使用extract检查当前位置的值(不观察其邻居),并使用duplicate或{{1来观察每个位置的邻域}}。没有任何附加功能,这不是非常有用。但是,如果我们还需要其他函数以及extend实例,我们可以编写依赖于本地数据和来自其他地方的数据的程序。例如,如果我们需要允许我们更改位置的函数,例如Comonad,我们可以编写仅依赖于数据本地结构的程序,而不是数据结构本身。

举一个具体的例子,考虑一个用advance和以下Comonad类编写的元胞自动机程序:

Bidirectional

该程序可以将此信息与class Bidirectional c where forward :: c a -> Maybe (c a) backward :: c a -> Maybe (c a) 一起用于存储在单元格中的Comonad数据,并探索当前单元格的单元格extractforward。它可以使用backward来捕获每个单元格的邻域,并使用duplicate来检查该邻域。 fmap的这种组合是fmap f . duplicate

这是一个这样的程序。 extract f只对这个例子感兴趣;它通过左右值实现邻域的元胞自动机规则。 rule'在给定班级的情况下从邻域中提取数据,并在每个邻域上运行规则。 rule拉出更大的社区,以便我们可以轻松地显示它们。 slice运行模拟,为每一代显示这些较大的邻域。

simulate

此计划可能旨在与列表中的rule' :: Word8 -> Bool -> Bool -> Bool -> Bool rule' x l m r = testBit x ((if l then 4 else 0) .|. (if m then 2 else 0) .|. (if r then 1 else 0)) rule :: (Comonad w, Bidirectional w) => Word8 -> w Bool -> w Bool rule x = extend go where go w = rule' x (maybe False extract . backward $ w) (extract w) (maybe False extract . forward $ w) slice :: (Comonad w, Bidirectional w) => Int -> Int -> a -> w a -> [a] slice l r a w = sliceL l w (extract w : sliceR r w) where sliceR r w | r > 0 = case (forward w) of Nothing -> take r (repeat a) Just w' -> extract w' : sliceR (r-1) w' sliceR _ _ = [] sliceL l w r | l > 0 = case (backward w) of Nothing -> take l (repeat a) ++ r Just w' -> sliceL (l-1) w' (extract w':r) sliceL _ _ r = r simulate :: (Comonad w, Bidirectional w) => (w Bool -> w Bool) -> Int -> Int -> Int -> w Bool -> IO () simulate f l r x w = mapM_ putStrLn . map (map (\x -> if x then '1' else '0') . slice l r False) . take x . iterate f $ w BidirectionalComonad一起使用。

Zipper

但也适用于data Zipper a = Zipper { heads :: [a], here :: a, tail :: [a] } deriving Functor instance Bidirectional Zipper where forward (Zipper _ _ [] ) = Nothing forward (Zipper l h (r:rs)) = Just $ Zipper (h:l) r rs backward (Zipper [] _ _) = Nothing backward (Zipper (l:ls) h r) = Just $ Zipper ls l (h:r) instance Comonad Zipper where extract = here duplicate (Zipper l h r) = Zipper (goL (h:r) l) (Zipper l h r) (goR (h:l) r) where goL r [] = [] goL r (h:l) = Zipper l h r : goL (h:r) l goR l [] = [] goR l (h:r) = Zipper l h r : goR (h:l) r CyclicList Bidirectional

Comonad

我们可以将data CyclicList a = CL a (Seq a) deriving (Show, Eq, Functor) instance Bidirectional CyclicList where forward (CL x xs) = Just $ case viewl xs of EmptyL -> CL x xs x' :< xs' -> CL x' (xs' |> x) backward (CL x xs) = Just $ case viewr xs of EmptyR -> CL x xs xs' :> x' -> CL x' (x <| xs') instance Comonad CyclicList where extract (CL x _) = x duplicate (CL x xs) = CL (CL x xs) (go (singleton x) xs) where go old new = case viewl new of EmptyL -> empty x' :< xs' -> CL x' (xs' >< old) <| go (old |> x') xs' 重用于任一数据结构。 simulate有一个更有意思的输出,因为它不会撞到墙上,而是会绕回来与自身交互。

CyclicList