在Haskell的Rand StdGen Monad中进行跟踪和调试

时间:2016-01-06 14:26:37

标签: debugging haskell random monads tracing

作为Haskell的新手,我已经完成了UPenn Haskell课程并完成了作业问题。为了便于调试,我打开跟踪:

import Debug.Trace

我遇到的问题是,在启用跟踪时,我并不完全理解我的某个程序的行为。

特别是,我一直致力于Haskell课程的最后一项作业,其中要求学生模拟风险游戏(美国流行的桌面游戏),其中2名玩家掷骰子来玩游戏。

http://www.seas.upenn.edu/~cis194/fall14/spring13/hw/12-monads.pdf

在练习2中,赋值调用函数battle,该函数采用Battlefield数据类型并返回monadic Battlefield

           battle :: Battlefield -> Rand StdGen Battlefield

模拟单一战斗的结果,其中攻击玩家和防御玩家掷骰子以确定战斗的结果。

然后在练习3中,我们模拟了一次完全的入侵,其中进行了一轮又一轮的战斗,直到其中一名玩家进入战斗状态。军队太耗尽而无法继续。

           invade :: Battlefield -> Rand StdGen Battlefield

我写了以下内容:

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

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

这是输出:

*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}

我不明白为什么最后一轮追踪没有打印出来。递归调用函数battleUntilOver,直到battleOver函数的结果结束递归。因此,我希望打印出traceM函数

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

battleOver函数返回True之前,游戏结束。我不明白为什么没有。

另外,我注意到在battleUntilOver函数中,如果我替换

if battleOver bfield then return bfield 

if battleOver bfield then randBfield

然后程序无法正常工作,输出错误的结果。

*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}

我在GHCI中看到的最后一行被复制,其中跟踪输出散布在invade函数的评估输出中。

这里的程序似乎已经结束了,因为在monad中,battleOver的计算结果为True,

[attackers = 1, defenders = 9]

结束游戏,表明防御者赢了最后一轮,但评估的输出似乎是

[attackers = 3, defenders = 7]

攻击者获胜的条件(并且游戏应该继续)。

为什么我必须包装纯值bfield而不是返回原始monadic值randBfield

1 个答案:

答案 0 :(得分:1)

问题1

  

我不明白为什么最后一轮追踪没有打印出来。

你不能信任&#34; traceM。这是实施:

traceM :: (Monad m) => String -> m ()
traceM string = trace string $ return ()

正如文件所说:

  

请注意,trace的应用程序不是monad中的操作,因为traceIO位于IO monad中。

这是另一个令人困惑的exampletraceM

import Debug.Trace

f n action = if n > 0
                 then action >> f (n - 1) action
                 else return ()

main = f 4 $ do 
         traceM "traceM"
         traceIO "traceIO"

使用-O0编译时的结果:

traceM
traceIO
traceIO
traceIO
traceIO

使用-O编译时的结果:

traceM
traceIO
traceM
traceIO
traceM
traceIO
traceM
traceIO

尽管如此,我并不完全知道为什么你的代码会运行所有traceM语句,除了最后一个。您必须调查Rand StdGen monad的严格性属性。例如,请参阅此email,其中显示runIdentity $ traceM "test" >> return ()根本不打印任何内容,因为Identity monad在{{1}的第一个参数中并不严格}。由于您致电>>=,而evalRandIO调用了evalRand,这可能与您的问题有关。

(有人请更新答案)

问题2

  

为什么我必须包装纯值runIdentity而不是返回原始monadic值bfield

如果您更改randBfield的签名以取battleUntilOver,可能会更清楚:

Battlefield

您是否同意这与原始程序的作用相同?

现在,你想知道为什么做以下事情并没有给你相同的结果:

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

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

当战斗结束时,你要求最后一场战斗的复赛(使用笔 - 终极战场)!最终的最后一战将使用不同的随机值,如@leftroundabout所说,所以它应该偶尔给你一个不同的结果。其他时候,结果将是相同的,所有这些都取决于随机数生成器的初始种子。