2014-09-04 15 views
6

Nella mia applicazione, sto cercando di implementare un sistema di animazione. In questo sistema, le animazioni sono rappresentati come una lista di frame ciclico:Quali benefici ottengo dalla creazione di un'istanza di Comonad

data CyclicList a = CL a [a] 

Possiamo (inefficiente) far avanzare l'animazione come segue:

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

Ora, sono abbastanza sicuro che questo tipo di dati è un 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 

la domanda che ho è: che tipo di benefici posso ottenere (se presente) di utilizzare l'istanza comonad?

+0

Sarai in grado di utilizzare qualsiasi funzione parametrizzata per lavorare su consonanti arbitrarie. Non posso indicarlo adesso, ma sono sicuro che ce ne sono alcuni che esistono. –

+0

[Qual è la classe di caratteri di Comonad in Haskell?] (Http://stackoverflow.com/questions/8428554/what-is-the-comonad-typeclass-in-haskell?rq=1) – sjy

risposta

2

Il vantaggio di fornire una classe di tipi o l'implementazione di un'interfaccia è che il codice, scritto per utilizzare quella classe di caratteri o l'interfaccia, può utilizzare il codice senza alcuna modifica.

Quali programmi possono essere scritti in termini di Comonad? A Comonad fornisce un modo per ispezionare il valore nella posizione corrente (senza osservare i suoi vicini) utilizzando extract e un modo per osservare il quartiere di ogni posizione con duplicate o extend. Senza funzioni aggiuntive, questo non è terribilmente utile. Tuttavia, se richiediamo anche altre funzioni insieme all'istanza Comonad, possiamo scrivere programmi che dipendono sia da dati locali che da altri dati. Ad esempio, se richiediamo funzioni che ci consentono di cambiare posizione, come ad esempio il tuo advance, possiamo scrivere programmi che dipendono solo dalla struttura locale dei dati, non dalla struttura dati stessa.

Per un esempio concreto, si consideri un programma di automi cellulari scritto in termini di Comonad e le seguenti Bidirectional classe:

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

Il programma potrebbe utilizzare questo, insieme con Comonad, ai dati extract memorizzati in una cella e esplorare le celle forward e backward della cella corrente. Può utilizzare duplicate per acquisire il vicinato di ogni cella e fmap per ispezionare quel quartiere. Questa combinazione di fmap f . duplicate è extract f.

Ecco un programma del genere. rule' è solo interessante per l'esempio; implementa le regole automatiche cellulari sul vicinato con i soli valori sinistro e destro. rule estrae i dati dal quartiere, data la classe, ed esegue la regola su ciascun quartiere. slice estrae anche quartieri più grandi in modo che possiamo visualizzarli facilmente. simulate esegue la simulazione, visualizzando questi quartieri più grandi per ogni generazione.

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 

Questo programma potrebbe essere stato destinato a lavorare con il seguente BidirectionalComonad, un Zipper in un elenco.

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 

Ma funziona anche con un CyclicListBidirectionalComonad.

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' 

Possiamo riutilizzare simulate con la struttura dei dati.Il CyclicList ha un output più interessante, perché, invece di sbattere contro un muro, si riavvolge per interagire con se stesso.

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