调试不必要的严格性?

时间:2012-07-07 03:43:05

标签: haskell lazy-evaluation strictness tying-the-knot

我有一个问题,我不知道如何推理。我只是想问一下是否有人可以帮助我解决具体问题,但我突然意识到我可以提出一个更普遍的问题,希望能得到一个更好的一般性理解。希望。所以这里:

当你的程序太懒,通常很明显,因为你最终会遇到像空间泄漏这样的明显问题。我有相反的问题:我的程序太严格了。我正在尝试tie knots,并发现我试图做的某些事情会以某种方式打败我所需要的懒惰。所以我的一般问题是,如何调试不必要的严格性?


为了完整起见,我的具体情况如下:我在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>>,我不明白。它对我来说似乎 ,因为这应该比使用(!)更严格,但显然我错了......

1 个答案:

答案 0 :(得分:10)

你的第一个例子是地图中的严格。以下查找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-block的执行。这需要阅读地图。它相当于:

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
   ...

如果“使用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)

如果在输出中看到“使用m”后跟“使用过的m”,那么您就知道m的评估已完成并且程序继续运行。如果只看到“使用m”,则程序在这些点之间停止。在这种特殊情况下,您不应该看到“使用过的m”,因为maybe会强制m的评估并导致<<loop>>