Haskell、Monad、堆栈空间、惰性——如何将代码构造成惰性?
这是一个虚构的示例,但下面的代码演示了我在学习Haskell时不断遇到的一类问题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
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
。