【问题标题】:Debugging unwanted strictness?调试不需要的严格性?
【发布时间】:2012-07-07 11:53:15
【问题描述】:

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

当您的程序过于懒惰时,这通常很明显,因为您最终会遇到诸如空间泄漏之类的明显问题。我有相反的问题:我的程序太严格了。我正在尝试tieknots,并发现我尝试做的某些事情会以某种方式击败我需要的懒惰。所以我的一般问题是,如何调试不需要的严格性?


为了完整起见,这是我的具体情况:我在RWS,编写器组件填充地图,阅读器组件观察该地图的最终状态。在完成填充之前,我不能对这张地图做任何严格的事情。在地图中查找值似乎没有问题,例如:

do
  m <- ask
  val <- m ! key
  doSomething val -- etc.

但是(!) 使用error 失败,我宁愿使用我的monad 的fail 失败。所以我想做如下的事情:

do
  m <- ask
  maybe
    (fail "oh noes")
    (doSomething)
    (lookup key m)

这导致我的程序&lt;&lt;loop&gt;&gt;,我不明白。 似乎在我看来,这应该比使用 (!) 更严格,但显然我错了......

【问题讨论】:

  • 看起来你尝试做的事情是不可能的。在您真正拥有地图之前,您无法根据地图做出控制决策(因为地图的内容将取决于控制决策)。
  • @PhilipJF 是RWSTfail
  • @augustss :( ... 简单地使用(!) 似乎有点不像Haskell - 如果我要在我的RWS 之上分层MaybeT 怎么办?
  • 打结很脆弱,限制了你能做什么。

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


【解决方案1】:

您的第一个示例在地图中是严格的。下面查找print "1",然后运行它,程序实际打印1。当然,这需要评估m

main = do let m = Map.fromList [(1, print "1")]
          val <- m ! 1
          return val

你可能打算写一些只读取地图的东西。以下内容并不严格,因为val 未在 case 表达式中使用。

main = do let m = Map.fromList [(1, print "1")]
          let val = m ! 1
          return val

您的第二个示例是严格的,因为它检查lookup 的结果是否成功,以决定如何完成执行块。这需要阅读地图。相当于:

do m <- ask
   case lookup key m of
     Nothing -> fail "oh noes"
     Just x  -> doSomething x 

调试严格性问题

求值总是由 case 表达式或某些内置运算符(例如 + 用于整数)强制执行。如果您怀疑您的程序失败是因为某个值在可用之前被强制执行,您需要找出强制执行的值以及强制执行的位置。

强制了哪个值?

在这种错误中,程序会尝试根据自己的评估结果来评估表达式。您可以使用trace 来跟踪正在评估哪个表达式。在这个问题中,m 的值似乎是被强制的,所以使用trace 在消息被评估之前打印一条消息:

do m1 <- ask
   let m = trace "Using m" m1
   ...

如果“Using m”是您程序的最后一个输出(在&lt;&lt;loop&gt;&gt; 之前),那么您离错误越来越近了。如果它不在输出中,则 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 并导致&lt;&lt;loop&gt;&gt;

【讨论】:

  • hmm... 不要认为这是对您的回答的侮辱,而只是表达我对 Haskell 开发工具偶尔失明、愤怒的挫败感...但是 说真的?? ? printf 风格的调试仍然是我们依赖的东西吗? ghci里面有没有什么可以帮助调试这类问题的?
  • 这是我调试严格性问题的方法,也是我学习 Haskell 的唯一方法。工具集中的一些可能有用的、相对较新的添加是 GHCi 的断点和 GHC 的真空库。断点使您可以单步执行程序并查看评估堆栈。 Vacuum 允许您查看堆的一部分。我没有足够的经验给你使用它们的实用建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-29
  • 2015-10-02
  • 1970-01-01
  • 1970-01-01
  • 2013-05-25
  • 1970-01-01
相关资源
最近更新 更多