调试不需要的严格性



我有一个问题,我不知道如何推理。我正要问是否有人能帮我解决这个具体的问题,但我突然意识到,我可以问一个更一般的问题,并希望因此得到更好的总体理解。有希望地因此:

当你的程序过于懒惰时,这通常是显而易见的,因为你最终会遇到明显的问题,比如空间泄漏。我有相反的问题:我的程序太严格了。我正在努力打结,发现我尝试做的某些事情会以某种方式战胜我需要的懒惰。所以我的一般问题是,如何调试不需要的严格性


为了完整起见,下面是我的具体情况:我在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>>

相关内容

最新更新