I watched this live coding of Sokoban in Haskell and thought I'd take a crack at writing it using State and lenses, since I've never tried that before. My problem is that the control flow seems to be complicated by isWall and friends now being monadic values that I need to unwrap, so I can't use the otherwise obvious approach of guards etc. Any suggestions? I did not focus very much on not making partial functions or handling IO properly, what interests me is how to manage the data and control flow cleanly. Note that I should have also not set default values for _wWorker and _wMax.
The worst culprits are move, toChar and setMaxCoords.
EDIT: Because of the way values are in State, I have a series of functions:
isWall :: MonadState World m => Coord -> m Bool
isWall c = use $ wWalls.contains c
isCrate :: MonadState World m => Coord -> m Bool
isCrate c = use $ wCrates.contains c
isStorage :: MonadState World m => Coord -> m Bool
isStorage c = use $ wStorage.contains c
isWorker :: MonadState World m => Coord -> m Bool
isWorker c = uses wWorker (==c)
These, I feel, are all fine, but they end up being less than useful in practice, because of the fact that the Bool is wrapped. The unwrapping process becomes very tedious in several functions:
move :: MonadState World m => Direction -> m ()
move dir = do
newCoord <- uses wWorker (moveCoord dir)
let newCoord' = moveCoord dir newCoord
wall <- isWall newCoord
wall' <- isWall newCoord'
crate <- isCrate newCoord
case () of
() | wall -> return ()
| crate -> if wall' then return () else
do
wWorker ^= newCoord
wCrates %= delete newCoord
wCrates %= insert newCoord'
| otherwise -> wWorker ^= newCoord
toChar :: MonadState World m => Coord -> m Char
toChar c = do
wall <- isWall c
crate <- isCrate c
storage <- isStorage c
worker <- isWorker c
return $ case () of
() | wall -> '#'
| worker -> '@'
| crate -> 'o'
| storage -> '.'
| otherwise -> ' '
The lenses themself also come with a certain cost
setMaxCoords :: StateT World IO ()
setMaxCoords = do
xMaxWall <- uses wWalls $ fold (max . fst) 0
yMaxWall <- uses wWalls $ fold (max . snd) 0
xMaxCrate <- uses wCrates $ fold (max . fst) 0
yMaxCrate <- uses wCrates $ fold (max . snd) 0
xMaxStorage <- uses wStorage $ fold (max . fst) 0
yMaxStorage <- uses wStorage $ fold (max . snd) 0
(xWorker, yWorker) <- use wWorker
wMax ^= (
maximum [xMaxWall, xMaxCrate, xMaxStorage, xWorker],
maximum [yMaxWall, yMaxCrate, yMaxStorage, yWorker]
)
I feel like I must be missing some obvious abstractions, because all these operations seem trivial and necessary, but the wrapping adds a lot of boilerplate.
The full code is here: http://hpaste.org/73124
^=should be substituted with.=. – Sarah Aug 15 '12 at 16:55