问题 调试不必要的严格性?


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

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


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


1588
2017-07-07 03:43


起源

什么是你的 fail 做? - Philip JF
它看起来不像你尝试做的那样。在实际拥有地图之前,您无法根据地图做出控制决策(因为地图的内容将取决于控制决策)。 - augustss
@PhilipJF它 RWST的 fail。 - mergeconflict
@augustss :( ...它看起来像是我的Haskell,就像我一样简单地使用 (!)  - 如果我要分层怎么办? MaybeT 在我的上面 RWS? - mergeconflict
使用tie-the-knot是非常脆弱的,并限制你可以做的事情。 - augustss


答案:


你的第一个例子是地图中的严格。以下查找 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>>


10
2017-07-07 06:26



嗯......不要把这当作对你答案的侮辱,而只是表达我对Haskell的开发工具偶尔的盲目,愤怒的挫败......但是 认真???  printf风格的调试仍然是我们可以依靠的东西?什么都没有 ghci 那有助于调试这类问题吗? - mergeconflict
这是我调试严格性问题的方式,这是我学习Haskell的唯一方法。一些可能有用的,相对较新的工具集增加了GHCi的断点和GHC的真空库。断点允许您单步执行程序并查看评估堆栈。使用Vacuum可以查看堆的一部分。我没有足够的经验为您提供使用它们的实用建议。 - Heatsink
好吧,现在我已经走了 博客 ;) - mergeconflict