首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >创建Comonad实例有什么好处

创建Comonad实例有什么好处
EN

Stack Overflow用户
提问于 2014-09-05 07:34:11
回答 1查看 318关注 0票数 6

在我的应用程序中,我试图实现一个动画系统。在这个系统中,动画被表示为帧的循环列表:

代码语言:javascript
复制
data CyclicList a = CL a [a]

我们可以(低效地)按如下方式推进动画:

代码语言:javascript
复制
advance :: CyclicList a -> CyclicList a
advance (CL x []) = CL x []
advance (CL x (z:zs)) = CL z (zs ++ [x])

现在,我非常确定这个数据类型是一个comonad:

代码语言:javascript
复制
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实例中获得了什么好处(如果有的话)?

EN

回答 1

Stack Overflow用户

发布于 2014-09-06 03:41:38

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

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

作为一个具体的例子,考虑一个用Comonad和下面的Bidirectional类编写的元胞自动机程序:

代码语言:javascript
复制
class Bidirectional c where
    forward  :: c a -> Maybe (c a)
    backward :: c a -> Maybe (c a)

该程序可以结合Comonad使用它来extract存储在单元格中的数据,并浏览当前单元格的单元格forwardbackward。它可以使用duplicate来捕获每个细胞的邻域,并使用fmap来检查该邻域。fmap f . duplicate的这种组合就是extract f

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

代码语言:javascript
复制
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

此程序可能旨在与以下Bidirectional Comonad一起使用,即列表中的Zipper

代码语言:javascript
复制
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一起使用。

代码语言:javascript
复制
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'

我们可以在任一数据结构中重用simulateCyclicList有一个更有趣的输出,因为它不是撞到墙上,而是绕回来与自己交互。

代码语言:javascript
复制
{-# LANGUAGE DeriveFunctor #-}

import Control.Comonad
import Data.Sequence hiding (take)
import Data.Bits
import Data.Word

main = do
    putStrLn "10 + 1 + 10 Zipper"
    simulate (rule 110) 10 10 30 $ Zipper (take 10 . repeat $ False) True (take 10 . repeat $ False)
    putStrLn "10 + 1 + 10 Cyclic"
    simulate (rule 110) 10 10 30 $ CL True (fromList (take 20 . repeat $ False))
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25676537

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档