Are comonads a good fit for modeling the Wumpus world?

403 views Asked by At

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!

1

There are 1 answers

0
daniel gratzer On BEST ANSWER

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 extendreturns 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.