理解递归的Haskell/GHCI行为

理解递归的Haskell/GHCI行为,haskell,recursion,Haskell,Recursion,我试图通过下面的函数来理解我所看到的。不确定我的理解是否错误,或者这是否是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)

我试图通过下面的函数来理解我所看到的。不确定我的理解是否错误,或者这是否是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> 

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

您所期望的递归(即,对
else y
的求值)是一种过程性的期望,这在Haskell的惰性求值中是不需要的

  • 当需要计算
    if
    时,
    y
    已经在
    语句中进行了计算,其中y=countNumLastChar xs
    语句不需要再次计算。(
    else y
    没有显示在跟踪中,因为没有新的可评估内容)
  • then(fst y,(snd y)+1)
    在函数到达单例情况时未进行求值,因此您确实可以在返回递归堆栈的过程中看到它被求值

如果要将else案例更改为在单例案例之后才能计算的内容,则将在备份递归调用的过程中对其进行计算。

无法编辑Q,因此此处继续。如果GHCI正在对此进行优化,是否将其视为尾部调用优化?但如果不进行比较,在x和
fst y之间
GHCI是如何调用优化的,这让我感到困惑……优化永远不会导致与纯代码不同的答案。在极少数情况下(当您的代码做了一些不明智的事情时),它们会导致技术上不应该发生的异常。这里不是这样。你期望你的程序做什么?@dfeuer:我认为程序正在做它期望做的事情。这是试图计算该列表中最后一个“char”/元素的出现次数。。。我想看看这里是否有优化。如果是这样的话,这项技术叫什么以及其他相关信息。我没有一个明确的答案,但我怀疑这是因为分享、转换为连续传球方式,甚至可能是尾部呼叫消除的组合?我试着写一个快速的解释,但我觉得我没有很好地掌握真正能够做到这一点的方法。看起来你没有得到其他y,仅仅因为没有什么需要评估<代码>y
已经计算过,而
else
不是表达式。Haskell没有与过程语言相同的堆栈帧。想象Haskell的堆栈类似于一个数学方程,其中每个递归调用都是方程中的一个项。在最后一个字母不重复的情况下,一旦命中单例,方程就“求解”。然后,就像在数学中一样,没有理由重述任何原始的“术语”,因为已经找到了解决方案。(即,在f('abc')=f('bc')=f('c')=('c',1)中,所有项都等于('c',1),无需再做任何工作),但它在冯·诺依曼机器上运行,因此必须使用堆栈帧!我理解您的高级解释,但我对寄存器和内存中实际发生的情况感兴趣。