Debugging Haskell中Rand StdGen Monad的跟踪和调试

Debugging Haskell中Rand StdGen Monad的跟踪和调试,debugging,haskell,random,monads,trace,Debugging,Haskell,Random,Monads,Trace,作为Haskell的新手,我一直在学习UPenn-Haskell课程,解决家庭作业问题。为了便于调试,我使用以下方法启用跟踪: import Debug.Trace 我的问题是,当启用跟踪时,我不太了解我的一个程序的行为 特别是,我一直在做Haskell课程的最后一个家庭作业,其中要求学生模拟风险游戏(美国流行的棋类游戏),其中两名玩家掷骰子玩游戏 在练习2中,任务要求编写一个函数battle,该函数采用battle数据类型并返回一元battle battle :: B

作为Haskell的新手,我一直在学习UPenn-Haskell课程,解决家庭作业问题。为了便于调试,我使用以下方法启用跟踪:

import Debug.Trace
我的问题是,当启用跟踪时,我不太了解我的一个程序的行为

特别是,我一直在做Haskell课程的最后一个家庭作业,其中要求学生模拟风险游戏(美国流行的棋类游戏),其中两名玩家掷骰子玩游戏

在练习2中,任务要求编写一个函数
battle
,该函数采用
battle
数据类型并返回一元
battle

           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
我不明白的是为什么最后一轮追踪没有打印出来。递归调用函数
battleUntilOver
,直到
battleOver
函数的结果结束递归。因此,我希望
traceM
函数能够打印出来

bfield::Battlefield {attackers = 1, defenders = 5}::
battleOver
函数返回True并结束游戏之前。我不明白为什么没有

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

if battleOver bfield then return bfield 

然后程序不能正常工作,输出错误的结果

*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中看到的复制,其中跟踪输出穿插在
invain
函数求值的输出中

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

[attackers = 1, defenders = 9]
结束游戏,表明防守队员赢了上一轮,但评估结果似乎是正确的

[attackers = 3, defenders = 7]
一种情况,在这种情况下,攻击者反而赢得了那一轮(并且游戏应该继续)

为什么我必须包装纯值
b字段
,而不是返回原始的一元值
randBfield

问题1 我不明白的是为什么最后一轮追踪没有打印出来

你不能“信任”
traceM
。这就是实现:

traceM :: (Monad m) => String -> m ()
traceM string = trace string $ return ()
正如文件所说:

注意,
trace
的应用在monad中不是一个动作,因为
traceIO
在IO monad中

下面是另一个与
traceM
混淆的问题:

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的严格性属性。例如,请参见此示例,其中显示
runIdentity$traceM“test”>>
return()
根本不打印任何内容,因为
标识在
>=
的第一个参数中不严格。由于您调用了
evalRandIO
,它调用了
evalRandIO
,它调用了
runIdentity
,这可能也与您的问题有关

(请有人更新答案)

问题2 为什么我必须包装纯值
bfield
,而不是返回原始的一元值
randBfield

如果您将
battleUntilOver
的签名更改为乘坐
battle
,可能会更清楚:

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'
入侵::战场->兰德斯台根战场
侵入bfield=do
如果在战场上作战,则返回战场
要不然,我就去贝菲尔德
battleUntilOver::Battleway->Rand StdGen Battleway
战场

bfield“Debug.Trace中的函数通常不能提供真正可靠的输出。它们只是一种快速了解“那里到底发生了什么”的方式。但是所显示的内容以及顺序在很大程度上取决于表达式的计算顺序,而且由于这是Haskell,编译器在重新排序等方面有很大的自由度:由于引用透明性,它知道一些转换根本没有任何区别。但是
跟踪
在引用上是不透明的如果您想要任何类型的可靠日志记录,您应该在monad堆栈中插入一个
WriterT
。@leftaroundabout:谢谢。这有助于回答为什么最后一次迭代的调试和跟踪没有出现。然而,我仍然不确定为什么用
替换
如果在战场上战斗,然后返回bfield
,如果在战场上战斗,那么randBfield
会产生不正确的结果。返回的结果,
[攻击者=3,防御者=7]
,不应该结束递归。啊。当然,这应该是不同的。您正在生成两个不同的随机值。“随机性的关键在于,对一个随机操作的两个后续调用不会给出相同的结果,而只是来自一个定义良好的随机分布的不同样本。”Leftabout。知道了。谢谢托米在下面解释得很好。谢谢你们两位
traceM
traceIO
traceM
traceIO
traceM
traceIO
traceM
traceIO
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'
battleUntilOver :: Battlefield -> Rand StdGen Battlefield
battleUntilOver bfield = do
  bfield' <- battle bfield
  traceM $ "bfield::" ++ show bfield' ++ "::"
  if battleOver bfield' then battle bfield   -- Here is the change
    else battleUntilOver bfield'