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 Bidirectional
Comonad
, 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 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'
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))
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. –
[Qual è la classe di caratteri di Comonad in Haskell?] (Http://stackoverflow.com/questions/8428554/what-is-the-comonad-typeclass-in-haskell?rq=1) – sjy