Debugging Haskell中Rand StdGen Monad的跟踪和调试
作为Haskell的新手,我一直在学习UPenn-Haskell课程,解决家庭作业问题。为了便于调试,我使用以下方法启用跟踪: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
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'