class Functor m => Monad m where
pure :: a -> m a
join :: m (m a) -> m a
-- or
bind :: (a -> m b) -> m a -> m b
Monad specifies how nested container “collapses”.
class Functor w => Comonad w where
extract :: w a -> a
duplicate :: w a -> w (w a)
-- or
extend :: (w a -> b) -> w a -> w b
Comonad specifies how a container “duplicates”.
Haskell library for State and Store.
type State s a = s -> (a, s)
type Store s a = (s -> a, s)
instance Monad (State s) where
pure :: a -> s -> (s, a)
pure a = \s -> (s, a)
bind :: (a -> (s -> (s, b))) -- a -> m b
-> (s -> (s, a)) -- m a
-> (s -> (s, b)) -- m b
bind f g = \s -> let (s', a) = g s
(s'', b) = (f a) s'
in (s'', b)
instance Comonad (Store s) where
extract :: (s -> a, s) -> a
extract (f, s) = f s
extend :: ((s -> a, s) -> b) -- w a -> b
-> (s -> a, s) -- w a
-> (s -> b, s) -- w b
extend f (g, s) = (\s' -> f (g, s'), s)
Applications:
- State
- Mutable state simulation, parser
- Store
- A focused store (
s
acts like an index, ands->a
is an indexed store)
A doubly-ended infinite list - zipper.
type Zipper a = ([a], a, [a])
goLeft :: Zipper a -> Zipper a
goLeft ((a:as), curr, bs) = (as, a, (curr:bs))
goRight :: Zipper a -> Zipper a
goRight (as, curr, (b:bs)) = ((curr:as), b, bs)
-- Zipper a ~ Store Integer a
-- (Integer -> a, Integer)
instance Comonad Zipper where
extract :: ([a], a, [a]) -> a
extract (as, curr, bs) = curr
extend :: (([a], a, [a]) -> b) -- w a -> b
-> ([a], a, [a]) -- w a
-> ([b], b, [b]) -- w b
extend f (as, c, bs) =
let b = f (as, c, bs)
in (..., b, ...
-- may be too complicated to express, let's turn to duplicate
duplicate :: Zipper a -> Zipper (Zipper a)
duplicate :: Zipper a -> ([Zipper a], Zipper a, [Zipper a])
duplicate z = (iterate1 goLeft z, z, iterate1 goRight z)
where iterate1 :: (a -> a) -> a -> [a]
iterate1 f = drop 1 . iterate f
-- now it's a lot easier
extend :: (Zipper a -> b) -- w a -> b
-> Zipper a -- w a
-> Zipper b -- w b
extend f z = fmap f (duplicate z)
An infinite 2d array.
type Zipper2D a = Zipper (Zipper a)
-- Zipper2D a ~ Store (Integer,Integer) a
Note: comonad implementation for zipper2d isn’t just trivially compose two zipper comonad. It involves in an extra layer of transposition.
For the context of game of life, let’s define two aliases:
type Cell = Bool
type World a = Zipper2D a
rule :: World Cell -> Cell
rule w = case num_of_neighbors w of
n | n < 2 -> False
n | n in [2,3] -> True
n | n > 3 -> False
step :: World Cell -> World Cell
step = extend rule
-- extend :: Comonad w => (w a -> b) -> w a -> w b
That’s it!