我有一个问题,我不知道如何推理。我正要问是否有人能帮我解决这个具体的问题,但我突然意识到,我可以问一个更一般的问题,并希望因此得到更好的总体理解。有希望地因此:
当你的程序过于懒惰时,这通常是显而易见的,因为你最终会遇到明显的问题,比如空间泄漏。我有相反的问题:我的程序太严格了。我正在努力打结,发现我尝试做的某些事情会以某种方式战胜我需要的懒惰。所以我的一般问题是,如何调试不需要的严格性
为了完整起见,下面是我的具体情况:我在RWS
中,编写器组件填充映射,读取器组件观察该映射的最终状态。在我完成填充之前,我不能对这个地图做任何严格的操作。在地图中查找值似乎没有问题,比如:
do
m <- ask
val <- m ! key
doSomething val -- etc.
但是(!)
使用error
失败,而我更喜欢使用monad的fail
失败。所以我想做以下事情:
do
m <- ask
maybe
(fail "oh noes")
(doSomething)
(lookup key m)
这导致我的程序变成<<loop>>
,我不理解。在我看来,这应该比使用(!)
更严格,但显然我错了。。。
您的第一个示例在映射中是严格的。下面查找print "1"
,然后运行它,程序实际打印1。当然,这需要评估m
。
main = do let m = Map.fromList [(1, print "1")]
val <- m ! 1
return val
你可能是想写一些只读地图的东西。以下内容并不严格,因为val
不用于大小写表达式。
main = do let m = Map.fromList [(1, print "1")]
let val = m ! 1
return val
您的第二个示例非常严格,因为它检查lookup
的结果是否成功,以便决定如何完成do块的执行。这需要阅读地图。它相当于:
do m <- ask
case lookup key m of
Nothing -> fail "oh noes"
Just x -> doSomething x
调试严格性问题
求值总是由大小写表达式或某些内置运算符(如整数的+
)强制执行的。如果您怀疑您的程序失败是因为某个值在可用之前被强制使用,那么您将需要找出哪个值被强制使用以及它被强制使用的位置。
哪个值是强制的
在这种错误中,程序试图根据自己的求值结果来求值表达式。可以使用trace
来跟踪正在计算的表达式。在这个问题中,看起来m
的值是被强制的,所以在评估之前使用trace
打印一条消息:
do m1 <- ask
let m = trace "Using m" m1
...
如果"Using m"是程序的最后一个输出(在<<loop>>
之前),那么您就离bug越来越近了。如果它不在输出中,那么m
就不会被评估,所以问题出在其他地方。如果输出中有东西跟在这一行后面,那么程序将继续执行,稍后会发生错误,所以问题一定在其他地方。
它是在哪里被强迫的
这告诉你,评估在停止之前至少得到了这一点。但它走了多远?问题真的发生得晚了吗?要了解这一点,请尝试在稍后评估的内容上添加trace
。我们知道m
是为了决定maybe
的哪个分支运行而评估的,所以我们可以将trace
放在这些点上。
do m1 <- ask
let m = trace "Using m" m1
maybe (trace "Used m" $ fail "oh noes")
(x -> trace "Used m" $ doSomething x)
(lookup key m)
如果您在输出中看到"Using m"后面跟着"Used m",那么您就知道m
的评估已经完成,程序继续运行。如果您只看到"使用m",则程序在这些点之间停止。在这种特殊情况下,您不应该看到"Used m",因为maybe
强制计算m
并导致<<loop>>
。