mercredi 6 janvier 2016

Tracing and Debugging in Rand StdGen monad in haskell

As something of a Haskell newbie, I've been going through the UPenn Haskell course and working through the homework problems. In order to facilitate debugging, I turn on Tracing with:

import Debug.Trace

The problem that I have is that I don't quite understand the behavior of one of my programs when tracing is enabled.

I am been working on the last homework assignment of the Haskell course, in which students are asked to simulate the game of Risk (a popular boardgame in the US) in which 2 players roll dice to play the game.

http://ift.tt/1TEoitA

In Exercise 2, the assignment calls for writing a function battle which takes a Battlefield data type and returns a monadic Battlefield

           battle :: Battlefield -> Rand StdGen Battlefield

to simulate the result of a single battle in which the attacking player and the defending player roll dice to determine the outcome of the battle.

Then in Exercise 3, we simulate a full invasion, in which round after round of battles are waged until one of the player's army is too depleted to continue.

           invade :: Battlefield -> Rand StdGen Battlefield

I wrote up the following:

invade :: Battlefield -> Rand StdGen Battlefield
invade bfield = do
  if battleOver bfield then return bfield
    else battleUntilOver $ battle afield

battleOver :: Battlefield -> Bool
battleOver bfield
  | attackers bfield < 2        = True
  | defenders bfield == 0       = True
  | otherwise                   = False

battleUntilOver :: Rand StdGen Battlefield -> Rand StdGen Battlefield
battleUntilOver randBfield = do
  bfield <- randBfield
  traceM $ "bfield::" ++ show bfield ++ "::"
  if battleOver bfield then return bfield
    else battleUntilOver $ battle bfield

Here is the output:

*Risk> evalRandIO $ invade Battlefield {attackers=10,defenders=10}
bfield::Battlefield {attackers = 10, defenders = 8}::
bfield::Battlefield {attackers = 8, defenders = 8}::
bfield::Battlefield {attackers = 6, defenders = 8}::
bfield::Battlefield {attackers = 4, defenders = 8}::
bfield::Battlefield {attackers = 4, defenders = 6}::
bfield::Battlefield {attackers = 2, defenders = 6}::
bfield::Battlefield {attackers = 2, defenders = 5}::
Battlefield {attackers = 1, defenders = 5}

What I don't understand is why the last round of tracing does not get printed. The function battleUntilOver is called recursively until the the result of the battleOver function ends the recursion. Thus I would expect the traceM function to print out

bfield::Battlefield {attackers = 1, defenders = 5}::

before the battleOver function returns True and the game ends. I don't understand why it doesn't.

Also, I have noticed that in the battleUntilOver function, if I replace

if battleOver bfield then return bfield 

with

if battleOver bfield then randBfield

then the program does not work properly, outputting a faulty result.

*Risk> evalRandIO $ invade Battlefield {attackers=10,defenders=10}
bfield::Battlefield {attackers = 8, defenders = 10}::
bfield::Battlefield {attackers = 7, defenders = 9}::
bfield::Battlefield {attackers = 5, defenders = 9}::
bfield::Battlefield {attackers = 3, defenders = 9}::
Battlefield {attackers = bfield::Battlefield {attackers = 1, defenders = 9}::
3, defenders = 7}

The last line is reproduced as I see it in GHCI, in which the tracing output is interspersed into the usual output of the evaluation of the invade function.

Here the program seems to be ending because within the monad, battle Over evaluates to True, and

[attackers = 1, defenders = 9]

ends the game, indicating that the defenders won the last round, yet the evaluated output appears to be

[attackers = 3, defenders = 7]

a condition in which the attackers had won that round instead (and in which the game should continue).

Why do I have to wrap the pure value bfield instead of returning the original monadic value?




Aucun commentaire:

Enregistrer un commentaire