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