作为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
?
答案 0 :(得分:1)
我不明白为什么最后一轮追踪没有打印出来。
你不能信任&#34; traceM
。这是实施:
traceM :: (Monad m) => String -> m ()
traceM string = trace string $ return ()
正如文件所说:
请注意,
trace
的应用程序不是monad中的操作,因为traceIO
位于IO monad中。
这是另一个令人困惑的example与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的严格性属性。例如,请参阅此email,其中显示runIdentity $ traceM "test" >>
return ()
根本不打印任何内容,因为Identity
monad在{{1}的第一个参数中并不严格}。由于您致电>>=
,而evalRandIO
调用了evalRand
,这可能与您的问题有关。
(有人请更新答案)
为什么我必须包装纯值
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所说,所以它应该偶尔给你一个不同的结果。其他时候,结果将是相同的,所有这些都取决于随机数生成器的初始种子。