使用foldr或foldl的Haskell转换函数

使用foldr或foldl的Haskell转换函数,haskell,Haskell,我正在努力理解foldr和foldl,使用它们的主要好处是什么,foldl应该是尾部递归的,但是什么是foldr,通常是递归的 我有一个简单的函数,它接受int的列表并返回第一个和最后一个元素之间的平方和 sumSquare :: [Int] -> Int sumSquare xs = if length xs == 2 then sum $ map (^2) [head xs..last xs] else error "bad input" 我想做的是使用foldr或foldl实现

我正在努力理解foldr和foldl,使用它们的主要好处是什么,foldl应该是尾部递归的,但是什么是foldr,通常是递归的

我有一个简单的函数,它接受int的列表并返回第一个和最后一个元素之间的平方和

sumSquare :: [Int] -> Int
sumSquare xs = if length xs == 2 then  sum $  map (^2) [head xs..last xs] else error "bad input"
我想做的是使用foldr或foldl实现这个函数,我已经看了几个例子,但遗憾的是,没有任何结果


如果有人能够演示如何使用foldr或foldl将此函数转换为一个函数,并解释此过程,我将非常感谢您提供一些不完善的经验法则:

  • 不要使用
    foldl
  • 如果您正在构造一个可以延迟构造的结果,请使用
    foldr
  • 如果您正在构建的结果不能延迟构建,请使用
    Data.List
    模块中的
    foldl'
  • 尽量不要直接使用这两种方法。可以将折叠看作是相对低级的函数,用于构建易于理解的可重用函数
  • 第四点的例子:哪一点更容易理解?例1:

    example1 = foldr step [] [0..]
        where step n ns = if even n then n:ns else ns
    
    例2:

    filter :: (a -> Bool) -> [a] -> [a]
    filter p = foldr step []
        where step a as = if p a then a:as else as
    
    example2 = filter even [0..]
    
    当然是第二个!你想通过把问题分解成更小的问题来解决问题,然后把它们分解成更小的问题。当分裂底部出现褶皱时,这一过程最容易理解

    第2点和第3点:想想
    sum::Num a=>a
    filter::(a->Bool)->[a]->[a]
    之间的区别。
    sum
    的结果是一个数字,
    filter
    的结果是一个列表。在Haskell中,可以惰性地生成和使用列表,但标准数字类型不能

    如果您正在构建的结果是一个列表,那么您需要使用
    foldr
    ,因为实际发生的情况是,结果将按照列表的要求延迟构建。例如,上面的
    过滤器定义就是如此;
    filter
    生成的列表将根据消费者的需求延迟构建。示例(与上述定义相同的
    步骤
    ):

    这里的懒惰意味着一旦产生足够的结果来计算出
    head
    的结果,对
    foldr
    的评估就会停止

    但是,如果您正在执行
    sum
    ,则很可能希望使用
    foldl'

    import Data.List (foldl')
    
    sum :: Num a => [a] -> a
    sum = foldl' (+) 0
    
    由于没有办法懒散地使用数字,因此您需要利用
    foldl'
    的尾部递归。例如:

    sum [1..3] * 2
        = foldl' (+) 0 [1..3] * 2
        = foldl' (+) 1 [2..3] * 2
        = foldl' (+) 3 [3..3] * 2
        = foldl' (+) 6 [] * 2
        = 6 * 2
        = 12
    
    但另一方面,
    foldl'
    不能像
    foldr
    那样利用懒惰
    foldl'
    必须一次遍历整个列表


    对评论的回答:好吧,让我们再试一次。我要说的第一点是,你试图做的是一个坏主意。你不想一次做“3-4件事”。它产生的代码很难阅读和修改

    但有一种方法可以把它变成一个好例子。让我们这样写函数:

    sumSquares (lo, hi) = sum $ map (^2) [lo..hi]
    
    请注意,我将参数更改为一对。您的原始版本希望列表的长度正好为2,这意味着您不应该使用列表,而应该使用一对(或两个参数)

    那么如何将其转化为一组褶皱呢?第一步:让我们以折叠的方式编写
    sum
    map

    sum = foldr (+) 0
    map f = foldr (\a bs -> f a : bs) []
    
    因此,现在我们可以手动将这些内容内联到定义中:

    sumSquares (lo, hi) = foldr (+) 0 $ foldr (\a bs -> a^2 : bs) [] [lo..hi]
    
    现在,这里有一个非常有用的事实:如果
    foldr
    使用另一个
    foldr
    生成的列表,通常可以将这两个折叠合并为一个。在这种情况下,它们与此融合:

    sumSquares (lo, hi) = foldr (\a bs -> a^2 + bs) 0 [lo..hi]
    
    例如:

    sumSquares (1, 3)
        = foldr (\a bs -> a^2 + bs) 0 [1..3]
        = 1^2 + foldr (\a bs -> a^2 + bs) 0 [2..3]
        = 1^2 + 2^2 + foldr (\a bs -> a^2 + bs) 0 [3..3]
        = 1^2 + 2^2 + + 3^2 + foldr (\a bs -> a^2 + bs) 0 []
        = 1^2 + 2^2 + + 3^2 + 0
        = 1 + 4 + 9 + 0
        = 14
    
    在代码中使用
    head
    last
    作为侧点,请注意,它们也可以写成折叠:

    safeHead, safeLast :: [a] -> Maybe a
    
    safeHead = foldr step Nothing
        where step a _ = Just a
    
    safeLast = foldr step Nothing
        where step a Nothing = Just a
              step _ justA = justA
    
    因此,为了以一种杂乱无章、功能齐备的方式解决这些问题,你需要写出一个干净的、多功能的解决方案,将单个片段转换成折叠,然后找出是否以及如何将这些折叠融合在一起

    但作为初学者的练习,这并不值得。首先因此,编译器已经知道如何融合此代码并将其转换为一个漂亮的紧密循环:

    foldr (+) 0 $ map (^2) [lo..hi]
    

    一些不完善的经验法则:

  • 不要使用
    foldl
  • 如果您正在构造一个可以延迟构造的结果,请使用
    foldr
  • 如果您正在构建的结果不能延迟构建,请使用
    Data.List
    模块中的
    foldl'
  • 尽量不要直接使用这两种方法。可以将折叠看作是相对低级的函数,用于构建易于理解的可重用函数
  • 第四点的例子:哪一点更容易理解?例1:

    example1 = foldr step [] [0..]
        where step n ns = if even n then n:ns else ns
    
    例2:

    filter :: (a -> Bool) -> [a] -> [a]
    filter p = foldr step []
        where step a as = if p a then a:as else as
    
    example2 = filter even [0..]
    
    当然是第二个!你想通过把问题分解成更小的问题来解决问题,然后把它们分解成更小的问题。当分裂底部出现褶皱时,这一过程最容易理解

    第2点和第3点:想想
    sum::Num a=>a
    filter::(a->Bool)->[a]->[a]
    之间的区别。
    sum
    的结果是一个数字,
    filter
    的结果是一个列表。在Haskell中,可以惰性地生成和使用列表,但标准数字类型不能

    如果您正在构建的结果是一个列表,那么您需要使用
    foldr
    ,因为实际发生的情况是,结果将按照列表的要求延迟构建。例如,上面的
    过滤器定义就是如此;
    filter
    生成的列表将根据消费者的需求延迟构建。示例(与上述定义相同的
    步骤
    ):

    这里的懒惰意味着一旦产生足够的结果来计算出
    head的结果,对
    foldr
    的评估就会停止
    foldl            :: (a -> b -> a) -> a -> [b] -> a
    foldl f acc []     =  acc
    foldl f acc (x:xs) =  foldl f (f acc x) xs
    
    foldr            :: (a -> b -> b) -> b -> [a] -> b
    foldr f acc []     = acc
    foldr f acc (x:xs) = f x (foldr f acc xs)
    
    foldl f 0 [1,2,3]
    foldl f (f 0 1) [2,3]
    foldl f (f (f 0 1) 2) [3]
    foldl f (f (f (f 0 1) 2 ) 3) []
    f (f (f 0 1) 2 ) 3
    
    foldr f 0 [1,2,3]
    f 1 (foldr f 0 [2,3])
    f 1 (f 2 (foldr f 0 [3]))
    f 1 (f 2 (f 3 (foldr f 0 []))
    f 1 (f 2 (f 3 0))