Haskell、Monad、堆栈空间、惰性——如何将代码构造成惰性?

Haskell、Monad、堆栈空间、惰性——如何将代码构造成惰性?,haskell,stack,Haskell,Stack,这是一个虚构的示例,但下面的代码演示了我在学习Haskell时不断遇到的一类问题 import Control.Monad.Error import Data.Char (isDigit) countDigitsForList [] = return [] countDigitsForList (x:xs) = do q <- countDigits x qs <- countDigitsForList xs return (q:qs) countDig

这是一个虚构的示例,但下面的代码演示了我在学习Haskell时不断遇到的一类问题

import Control.Monad.Error
import Data.Char (isDigit)

countDigitsForList [] = return []
countDigitsForList (x:xs) = do
    q  <- countDigits x
    qs <- countDigitsForList xs
    return (q:qs)

countDigits x = do
    if all isDigit x
        then return $ length x
        else throwError $ "Bad number: " ++ x

t1 = countDigitsForList ["1", "23", "456", "7890"] :: Either String [Int]
t2 = countDigitsForList ["1", "23", "4S6", "7890"] :: Either String [Int]
import Control.Monad.Error
导入数据.Char(isDigit)
CountDigitForList[]=返回[]
CountDigitForList(x:xs)=do
Q
如何将此类代码构造成不会出现堆栈空间问题和/或懒惰的代码

您不能让此函数以单子或否的方式惰性地处理列表。下面是对
countDigitsForList
的直接翻译,以使用模式匹配而不是
do
表示法:

countDigitsForList [] = return []
countDigitsForList (x:xs) = case countDigits x of
    Left e  -> Left e
    Right q -> case countDigitsForList xs of
                   Left e   -> Left e
                   Right qs -> Right (q:qs)
这里应该更容易看到,因为列表中任何一点的
Left
都会使整个事件返回该值,为了确定结果的最外层构造函数,必须遍历和处理整个列表;对于处理每个元素也是如此。由于最终结果可能取决于最后一个字符串中的最后一个字符,因此编写的此函数本质上是严格的,非常类似于对数字列表求和

考虑到这一点,要做的事情是确保函数足够严格,以避免构建一个巨大的未赋值表达式。一个很好的起点是讨论
foldr
foldl
foldl'
之间的区别

累加器和尾部递归似乎可以解决这个问题,但我反复读到,在Haskell中这两种方法都是不必要的,因为它们都是惰性计算


当您可以懒洋洋地生成、处理和使用列表时,这两者都是不必要的;这里最简单的例子是
map
。对于一个不可能这样做的函数,严格计算的尾部递归正是您想要的。

camccann认为函数本质上是严格的,这是正确的。但这并不意味着它不能在常量堆栈中运行

countDigitsForList xss = go xss []
    where go (x:xs) acc = case countDigits x of
                             Left e -> Left e
                             Right q -> go xs (q:acc)
          go [] acc = reverse acc
这个累积参数版本是camccann代码的部分cps变换,我敢打赌,您也可以通过对cps变换的单子进行处理来获得相同的结果


编辑以考虑jwodder关于反向的更正。哎呀。正如John L所指出的,隐式或显式差异列表也会起作用…

因为
右q->
行将
q
放在累加器的开头,您需要将最后一行更改为
go[]acc=reverse acc
以使最终结果的顺序正确。@jwodder:IME累积差异列表比在算法结束时反转要好。顺便说一下,
countDigitsForList
可以定义为
countDigitsForList=mapM countDigits