【问题标题】:Why does printing affects order when right folding in Haskell?为什么在 Haskell 中正确折叠时打印会影响顺序?
【发布时间】:2015-11-04 12:43:23
【问题描述】:

我确信这与惰性评估有关,但我仍然无法解释为什么它会以这种方式运行。为什么在verboseAdd2 中评估右侧会反转调试跟踪输出?

这是代码:

import Debug.Trace

data BinaryTree = Empty | Node Int BinaryTree BinaryTree

instance Show BinaryTree where
  show Empty = "_"
  show (Node x Empty right) = unwords [           show x, show right]
  show (Node x left  right) = unwords [show left, show x, show right]

add :: Int -> BinaryTree -> BinaryTree
add v Empty = Node v Empty Empty
add v a@(Node x left right)
  | v == x = a
  | v <  x = Node x (add v left) right
  | v >  x = Node x left (add v right)

verboseAdd :: Int -> BinaryTree -> BinaryTree
verboseAdd v a = trace ("[1] Adding v=" ++ show v) (add v a)

verboseAdd2 :: Int -> BinaryTree -> BinaryTree
verboseAdd2 v a = trace ("[2] Adding v=" ++ show v ++ " to " ++ show a) (add v a)

verbosePlus :: (Num a, Show a) => a -> a -> a
verbosePlus left right = trace (show left ++ " + " ++ show right)
                               (left + right)

main :: IO()
main = do
  let seq = [1,2,3,4,5]
  in do print $ foldr verbosePlus 0 seq
        putStrLn ""
        print $ foldr verboseAdd Empty seq
        putStrLn ""
        print $ foldr verboseAdd2 Empty seq

这是输出:

5 + 0
4 + 5
3 + 9
2 + 12
1 + 14
15

[1] Adding v=1
[1] Adding v=2
[1] Adding v=3
[1] Adding v=4
[1] Adding v=5
1 _ 2 _ 3 _ 4 _ 5 _

[2] Adding v=5 to _
[2] Adding v=4 to 5 _
[2] Adding v=3 to 4 _ 5 _
[2] Adding v=2 to 3 _ 4 _ 5 _
[2] Adding v=1 to 2 _ 3 _ 4 _ 5 _
1 _ 2 _ 3 _ 4 _ 5 _

【问题讨论】:

  • 将您的代码放在问题的主体中 - 它不会很长,如果所有内容都在那里,问题会更加清晰。
  • 是的,因为首先你只需要强制v 进行追踪,但第二个也强制a (记住a 这是你表达的其余部分;)) - 虽然它将评估这个a所有其他痕迹发生;)
  • 注意第二个更接近正在发生的事情 - 你真的从最后一个开始,但是foldr(或者更好的你返回的结构)是懒惰这一切都会以一个大笨蛋告终,只有在你开始做最后一场演出时才会得到评估("1 _ 2 _ 3 _ 4 _ 5 _"
  • @Carsten 抱歉,我不小心覆盖了您的编辑 - 我们在完全相同的时间戳进行了编辑。随意回滚到您的版本;我已经在 Meta 上发布了关于此的内容,请参阅 meta.stackoverflow.com/questions/309441/…
  • @Carsten 做到了。毕竟,您不能真正期望在评估树“2 _ 3 _ 4 _ 5 _”之前打印消息“[2] 将 v=1 添加到 2 _ 3 _ 4 _ 5 _”。因此,将首先评估“2 _ 3 _ 4 _ 5 _”,但这会触发它自己的跟踪。递归地,我们有向后的痕迹。

标签: haskell lazy-evaluation trace evaluation


【解决方案1】:

让我们扩展foldr in

foldr verboseAdd2 Empty [1, 2, 3, 4, 5]

使用它的定义(将verboseAdd2缩写为va):

1 `va` (2 `va` (3 `va` (4 `va` (5 `va` Empty))))

或者,在这种情况下,如果我们使用前缀表示法可能会更简单:

va 1 $ va 2 $ va 3 $ va 4 $ va 5 Empty

根据va 的定义,这简化为

let y1 = va 2 $ va 3 $ va 4 $ va 5 Empty
in trace ("Adding v=1 to " ++ show y1) (add 1 y1)

通过展开更多出现的va,我们得到

let y4 = trace ("Adding v=5 to " ++ show Empty) (add 5 Empty) in
let y3 = trace ("Adding v=4 to " ++ show y4) (add 4 y4) in
let y2 = trace ("Adding v=3 to " ++ show y3) (add 3 y3) in
let y1 = trace ("Adding v=2 to " ++ show y2) (add 2 y2) in
trace ("Adding v=1 to " ++ show y1) (add 1 y1)

希望你能从中看出,对于show y1,在最外层trace,我们首先需要评估y1,这会导致trace ("Adding v=2 to " ++ show y2")被触发,从而强制y3,等等,直到我们到达y4y4 中的 trace 不需要强制执行任何其他操作,因此它最终可以打印其消息,然后继续评估 add 5 Empty,然后由 tracey3 的定义中使用,等等。

【讨论】:

    【解决方案2】:

    这里有很多cmet,但似乎没有人写过答案...

    答案当然是show 函数。当您说x = add 5 myTree 时,实际上您所做的只是将x 设置为未计算的表达式。 y = add 7 xy 设置为另一个未计算的表达式,依此类推。但是当你尝试show y...好吧,y 依赖于x,所以首先我们必须评估x。而就是为什么trace会按照它的顺序打印出来。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-11
      • 1970-01-01
      • 2019-10-08
      • 2020-02-10
      • 2012-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多