2014-09-04 7 views
6

In meiner Anwendung versuche ich ein Animationssystem zu implementieren. In diesem System, Animationen als zyklische Liste von Frames dargestellt werden:Welche Vorteile habe ich beim Erstellen einer Instanz von Comonad?

data CyclicList a = CL a [a] 

Wir können (ineffizienten), um die Animation fort wie folgt:

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

Nun, ich bin ziemlich sicher, dass dieser Datentyp ein comonad ist:

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 

die Frage, die ich habe, ist: welche Art von Leistungen bekomme ich (falls vorhanden) von der comonad Instanz mit?

+0

Sie können alle Funktionen verwenden, die parametrisiert sind, um auf beliebigen comonads zu arbeiten. Ich kann jetzt nicht auf etwas hinweisen, aber ich bin mir sicher, dass es einige gibt. –

+0

[Was ist die comonad Typklasse in Haskell?] (Http://stackoverflow.com/questions/8428554/what-is-the-comonad-typeclass-in-haskell?rq=1) – sjy

Antwort

2

Der Vorteil der Bereitstellung einer Typklasse oder der Implementierung einer Schnittstelle besteht darin, dass Code, der für die Verwendung dieser Typklasse oder Schnittstelle geschrieben wurde, Ihren Code ohne Änderungen verwenden kann.

Welche Programme können in Bezug auf Comonad geschrieben werden? A Comonad bietet eine Möglichkeit, den Wert an der aktuellen Position (ohne Beobachtung seiner Nachbarn) unter Verwendung von extract und einer Möglichkeit zur Überwachung der Umgebung jedes Standortes mit duplicate oder extend. Ohne zusätzliche Funktionen ist das nicht besonders nützlich. Wenn wir jedoch auch andere Funktionen zusammen mit der Comonad-Instanz benötigen, können wir Programme schreiben, die sowohl von lokalen Daten als auch von anderen Daten abhängen. Wenn wir beispielsweise Funktionen benötigen, mit denen wir den Standort ändern können, z. B. Ihre advance, können wir Programme schreiben, die nur von der lokalen Struktur der Daten und nicht von der Datenstruktur selbst abhängen.

Für ein konkretes Beispiel eines zellulären Automaten Programm geschrieben in Bezug auf Comonad und die folgenden Bidirectional Klasse:

class Bidirectional c where 
    forward :: c a -> Maybe (c a) 
    backward :: c a -> Maybe (c a) 

Das Programm könnte dies, zusammen mit Comonad, zu extract Daten in einer Zelle gespeichert und Erkunden Sie die Zellen forward und backward der aktuellen Zelle. Er kann duplicate verwenden, um die Nachbarschaft jeder Zelle zu erfassen, und fmap, um diese Umgebung zu inspizieren. Diese Kombination von fmap f . duplicate ist extract f.

Hier ist ein solches Programm. rule' ist nur für das Beispiel interessant; Es implementiert zellulare Automatenregeln für die Nachbarschaft mit nur den linken und rechten Werten. rule extrahiert Daten aus der Umgebung, die der Klasse zugewiesen sind, und führt die Regel für jede Umgebung aus. slice zieht noch größere Nachbarschaften aus, damit wir sie leicht anzeigen können. simulate führt die Simulation durch und zeigt diese größeren Nachbarschaften für jede Generation an.

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 

Dieses Programm könnte mit der folgenden BidirectionalComonad, ein Zipper auf einer Liste zu arbeiten, wurden bestimmt.

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 

Aber auch mit einem CyclicListBidirectionalComonad arbeiten.

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' 

können wir wiederverwenden simulate entweder mit Datenstruktur.Die CyclicList hat eine interessantere Ausgabe, weil sie, anstatt in eine Wand zu stoßen, sich zurückzieht, um mit sich selbst zu interagieren.

{-# 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))