这些前提是关于折叠和递归吗?

时间:2015-07-09 12:08:06

标签: haskell recursion lazy-evaluation fold

使用foldr时,递归会在函数内部出现,因此, 当给定的函数不严格评估双方时,和 可以根据第一个返回,foldr必须是一个很好的解决方案, 因为它适用于无限列表

findInt :: Int -> [Int] -> Bool
findInt z [] = False
-- The recursion occours inside de given function
findInt z (x:xs) 
  | z == x = True
  | otherwise = findInt z xs 

相当于:

findInt' :: Int -> [Int] -> Bool
findInt' z = foldr (\x r -> if z == x then True else r) False
-- Where False is the "default value" (when it finds [], ex: findInt z [] = False) 

foldr不合适的情况:

addAll :: Int -> [Int] -> Int
addAll z [] = z
-- The recursion occours outside the given function (+)
addAll z (x:xs) = addAll (z + x) xs 

在这种情况下,因为+是严格的(需要评估双方返回) 如果我们以某种方式应用它,它将非常有用 有一个 redex (可简化的表达式),可以避免thunk 并且(当被迫与先前的评估一起运行,而不是懒惰)在常量中 空间并没有推到堆栈上 (类似于命令式算法中for循环的优点)

addAll' :: Int -> [Int] -> Int
addAll' z [] = z
addAll' z (x:xs) = let z' = z + x
                  in seq z' $ addAll' z' xs

相当于:

addAll'' :: Int -> [Int] -> Int
addAll'' z = foldl' (+) z

在这个小例子中,使用foldr(内部递归)没有意义 因为它不会制作重新索引。 它会是这样的:

addAll''' :: Int -> [Int] -> Int
addAll''' z [] = z
addAll''' z (x:xs) = (+) x $ addAll''' z xs

这个问题的主要目的是首先,知道我的前提是否属于 正确可能更好的地方,其次,帮助使其更清晰 对于其他人谁也在学习 Haskell 内部和内部之间的差异 外部递归,在这些方法中,要记住哪一个 可能更适合特定情况

有用的链接:

Haskell Wiki

Stackoverflow - Implications of foldr vs. foldl (or foldl')

1 个答案:

答案 0 :(得分:2)

除了foldr是列表的自然catamorphism,而foldl和foldl'不是,它们的一些使用指南:

  1. 你是正确的,即使在无限列表上,只要函数在第二个参数中是非严格的,折叠器将始终返回,因为列表的元素可用于函数的第一个参数是立即的(与foldl和foldl'相对,其中列表的元素在列表完全被消耗之前不能用于函数的第一个参数);
  2. 如果你想要确保常量空间,
  3. foldl'将是非无限列表的更好选择,因为它是尾递归的,但它总是会解析整个列表,无论评估参数的严格程度如何传递给它的函数;
  4. 一般来说,foldr相当于递归,而foldl和foldl'类似于循环;
  5. 因为foldr是自然的catamorphism,如果你的函数需要重新创建列表(例如,如果你的函数只是列表构造函数':'),则foldr会更合适;
  6. 关于foldl与foldl',foldl'通常是优选的,因为它不会构建一个巨大的thunk但是,如果传递给它的函数在其第一个参数中是非严格的并且列表不是无限的,则foldl可能会返回而foldl'可能会出错(Haskell wiki中有一个很好的例子)。
  7. 作为旁注,我相信您使用术语“内部递归”来定义foldr和foldl和foldl'的“外部递归”,但我之前在文献中没有看到这些术语。更常见的是,这些函数分别被称为从右侧折叠和从左侧折叠,这些术语虽然可能不完全正确,但它们给出了列表元素传递给函数的顺序的良好概念。