使用foldr或foldl的Haskell转换函数
我正在努力理解foldr和foldl,使用它们的主要好处是什么,foldl应该是尾部递归的,但是什么是foldr,通常是递归的 我有一个简单的函数,它接受int的列表并返回第一个和最后一个元素之间的平方和使用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实现
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'
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))