I'm trying to find some practical applications of a comonad and I thought I'd try to see if I could represent the classical Wumpus world as a comonad.
I'd like to use this code to allow the Wumpus to move left and right through the world and clean up dirty tiles and avoid pits. It seems that the only comonad function that's useful is extract (to get the current tile) and that moving around and cleaning tiles would not use be able to make use of extend or duplicate.
I'm not sure comonads are a good fit but I've seen a talk (Dominic Orchard: A Notation for Comonads) where comonads were used to model a cursor in a two-dimensional matrix.
If a comonad is a good way of representing the Wumpus world, could you please show where my code is wrong? If it's wrong, could you please suggest a simple application of comonads?
module Wumpus where
-- Incomplete model of a world inhabited by a Wumpus who likes a nice
-- tidy world but does not like falling in pits.
import Control.Comonad
-- The Wumpus world is made up of tiles that can be in one of three
-- states.
data Tile = Clean | Dirty | Pit
deriving (Show, Eq)
-- The Wumpus world is a one dimensional array partitioned into three
-- values: the tiles to the left of the Wumpus, the tile occupied by
-- the Wumpus, and the tiles to the right of the Wumpus.
data World a = World [a] a [a]
deriving (Show, Eq)
-- Applies a function to every tile in the world
instance Functor World where
fmap f (World as b cs) = World (fmap f as) (f b) (fmap f cs)
-- The Wumpus world is a Comonad
instance Comonad World where
-- get the part of the world the Wumpus currently occupies
extract (World _ b _) = b
-- not sure what this means in the Wumpus world. This type checks
-- but does not make sense to me.
extend f w@(World as b cs) = World (map world as) (f w) (map world cs)
where world v = f (World [] v [])
-- returns a world in which the Wumpus has either 1) moved one tile to
-- the left or 2) stayed in the same place if the Wumpus could not move
-- to the left.
moveLeft :: World a -> World a
moveLeft w@(World [] _ _) = w
moveLeft (World as b cs) = World (init as) (last as) (b:cs)
-- returns a world in which the Wumpus has either 1) moved one tile to
-- the right or 2) stayed in the same place if the Wumpus could not move
-- to the right.
moveRight :: World a -> World a
moveRight w@(World _ _ []) = w
moveRight (World as b cs) = World (as ++ [b]) (head cs) (tail cs)
initWorld = World [Dirty, Clean, Dirty] Dirty [Clean, Dirty, Pit]
-- cleans the current tile
cleanTile :: Tile -> Tile
cleanTile Dirty = Clean
cleanTile t = t
Thanks!
I'll move my string of comments to a more coherent answer..
What you have there is actually a "zipper", specifically a zipper for a list
data ListZip a = ListZip {lefts :: [a]
,focus :: a
,rights :: [a]}
We can convert a nonempty list to a ListZip
toZip :: [a] -> Maybe (ListZip a)
toZip (x:xs) = Just $ ListZip [] x xs
toZip _ = Nothing
Like all zippers, ListZip
has a focus, and we can do work on this focused area
modifyFocus :: (a -> a) -> ListZip a -> ListZip a
modifyFocus f z = z{focus = f $ focus z}
And we can move the focus around, what you've called moveLeft
and moveRight
moveLeft, moveRight :: ListZip a -> ListZip a
moveLeft (ListZip (x:xs) y ys) = ListZip xs x (y : ys)
moveRight (ListZip xs x (y:ys)) = ListZip (x : xs) y ys
Now like all zippers, ListZip
is a comonad!
extract
extracts the focus, or the currently occupied square
instance Comonad ListZip where
extract = focus
and extend
returns a new world where we've modified every focused square according to a "rule".
extend f w = ListZip (moving moveLeft) (f w) (moving moveRight)
where moving i = map f $ iterate i w
A rule in this case is a function
ZipList a -> a
If this reminds you vaguely of cellular automata you'd be right! This is very similar to how games like Conway's Game of Life work: simple context dependent rules.
We also get duplicate
defined which will return a Listzip
of ListZip
's where the focus has been moved in ever possible direction on each ListZip
.
Now I have no idea what exactly this would be useful for in the context of Wumpus, are there any uniform transformations you can make to each square on your board? Could you benefit from being able to reify a Wumpus world to a Wumpus world of all possible Wumpus worlds?
That's what comonads are giving you in this case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With