【问题标题】:understanding Haskell/GHCI behavior for recursion了解 Haskell/GHCI 的递归行为
【发布时间】:2015-11-17 23:00:32
【问题描述】:

我试图通过以下函数来理解我所看到的内容。不确定我的理解是否不正确,或者这是 Haskell 的 GHC 实现特有的行为。

countNumLastChar :: Eq a => [a] -> (a, Int)
countNumLastChar [x]      = (x, 1)
countNumLastChar (x:xs)  = if x == fst y
                            then (fst y, (snd y) + 1)
                            else y
                            where y = countNumLastChar xs

我看到了一些我无法用这段代码解释的东西。

*Main> countNumLastChar "aba"
('a',2)
*Main> countNumLastChar "abql;kejrqlwkjer;lqwkejr;lwjerca"
('a',2)
*Main> countNumLastChar "abql;kejrqlwkjer;lqwkejr;lwjercap"
('p',1)
*Main> countNumLastChar "abql;kejrqlwkjer;lqwkejr;lwjerca;"
(';',4)

例如:使用 GHCI 跟踪下面的运行,我看到当我们到达带有尚未重复的元素的单例列表时,我们不会递归每一步。

*Main> countNumLastChar "aabc"
('c',1)
[maxOccurCharInStr.hs:(3,28)-(5,34)] *Main> :step
Stopped at maxOccurCharInStr.hs:3:31-40
_result :: Bool = _
x :: Char = 'b'
y :: (Char, Int) = _
[maxOccurCharInStr.hs:3:31-40] *Main> :list
2  countNumLastChar [x]      = (x, 1)
3  countNumLastChar (x:xs)  = if x == fst y
4                              then (fst y, (snd y) + 1)
[maxOccurCharInStr.hs:3:31-40] *Main> :step
Stopped at maxOccurCharInStr.hs:3:36-40
_result :: a = _
y :: (a, Int) = _
[maxOccurCharInStr.hs:3:36-40] *Main> :step
Stopped at maxOccurCharInStr.hs:6:39-57
_result :: (Char, Int) = _
xs :: [Char] = 'c' : _
[maxOccurCharInStr.hs:6:39-57] *Main> :list
5                              else y
6                              where y = countNumLastChar xs
7  
[maxOccurCharInStr.hs:6:39-57] *Main> :step
Stopped at maxOccurCharInStr.hs:(2,1)-(6,57)
_result :: (a, Int) = _
[maxOccurCharInStr.hs:(2,1)-(6,57)] *Main> :list
1  countNumLastChar :: Eq a => [a] -> (a, Int)
2  countNumLastChar [x]      = (x, 1)
3  countNumLastChar (x:xs)  = if x == fst y
4                              then (fst y, (snd y) + 1)
5                              else y
6                              where y = countNumLastChar xs
7  
[maxOccurCharInStr.hs:(2,1)-(6,57)] *Main> :step
Stopped at maxOccurCharInStr.hs:2:29-34
_result :: (Char, Int) = _
x :: Char = 'c'
[maxOccurCharInStr.hs:2:29-34] *Main> :list
1  countNumLastChar :: Eq a => [a] -> (a, Int)
2  countNumLastChar [x]      = (x, 1)
3  countNumLastChar (x:xs)  = if x == fst y
[maxOccurCharInStr.hs:2:29-34] *Main> :step
('c',1)
*Main> 

我原以为最后一个 :step 会将我带回到定义中的 else y 案例,但我看到结果立即返回。但是,当最后一个字符出现之前,我们递归回来并执行(fst y, (snd y) + 1) 部分......有人可以告诉我发生了什么吗?是我的理解不正确还是 GHCI 优化了某些东西。如果是优化,它怎么知道必须直接返回结果?对此的任何参考都会有很大帮助。

【问题讨论】:

  • 无法编辑 Q,因此在此继续。 IFF GHCI 正在对此进行优化,它是否被视为尾调用优化?但是如果不进行比较,在 x 和 fst y 之间,GHCI 甚至调用优化是什么让我感到困惑......
  • 优化不应该导致与纯代码不同的答案。在极少数情况下(当您的代码做一些不明智的事情时),它们可能会导致技术上不应该发生的异常。这不是这里的情况。你希望你的程序做什么?
  • @dfeuer:我认为程序正在做它应该做的事情。这是试图计算该列表中最后一个“char”/元素的出现次数......我试图看看这里是否有优化。如果是这样,那么该技术称为什么以及其他相关信息。
  • 我没有明确的答案,但我怀疑这是因为共享、转换为连续传递样式甚至可能是尾调用消除的组合?我试图写一个简短的解释,但我觉得我对真正发生的事情没有足够的了解能够做到这一点。
  • 看起来你没有到达else y 只是因为没有什么需要评估的。 y 已被评估,else 不是表达式。

标签: haskell recursion


【解决方案1】:

您所期望的递归(即else y 的评估)是在 Haskell 的惰性评估中不需要的过程期望。

  • y 在需要评估if 时已在where y = countNumLastChar xs 语句中评估,因此无需再次评估。 (else y 没有出现在跟踪中,因为没有什么新东西要评估)
  • then (fst y, (snd y) + 1) 在函数到达单例情况时还没有被评估,所以你确实看到它在返回递归堆栈的路上被评估了。

如果您要将 else 情况更改为在单例情况之后无法评估的情况,则将在备份递归调用的途中对其进行评估。

【讨论】:

  • Haskell 没有与过程语言相同的堆栈帧。想想 Haskell 的堆栈类似于一个数学方程,其中每个递归调用都是方程中的一个项。在最后一个字母不重复的情况下,一旦遇到单例情况,等式就“解决”了。然后,就像在数学中一样,没有理由重述任何原始“术语”,因为已经找到了解决方案。 (即在 f('abc')=f('bc')=f('c')=('c',1) 中,所有项都等于 ('c',1) 没有任何工作要做)
  • 但是它运行在冯诺依曼机器上,所以它必须使用栈帧!我理解您非常高级的解释,但我对寄存器和内存中实际发生的情况感兴趣。
猜你喜欢
  • 2016-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-03
  • 2021-03-22
  • 1970-01-01
  • 2017-03-17
  • 2017-04-17
相关资源
最近更新 更多