一种不同的思考方式 - foldr 之所以存在,是因为经常使用以下递归模式:
-- Example 1: Sum up numbers
summa :: Num a => [a] -> a
summa [] = 0
summa (x:xs) = x + suma xs
取数字的乘积甚至反转列表在结构上看起来与前面的递归函数非常相似:
-- Example 2: Reverse numbers
reverso :: [a] -> [a]
reverso [] = []
reverso (x:xs) = x `op` reverso xs
where
op = (\curr acc -> acc ++ [curr])
上述示例中的结构仅在初始值(0
对于 summa 和[]
对于 reverso)以及第一个值和递归调用之间的运算符(+
对于 summa 和(\q qs -> qs ++ [q])
对于 reverso)方面有所不同。所以上述例子的函数结构一般可以看成是
-- Generic function structure
foo :: (a -> [a] -> [a]) -> [a] -> [a] -> [a]
foo op init_val [] = init_val
foo op init_val (x:xs) = x `op` foo op init_val xs
要查看这个“通用” foo 是否有效,我们现在可以使用 foo 重写逆向,并将运算符、初始值和列表本身传递给它:
-- Test: reverso using foo
foo (\curr acc -> acc ++ [curr]) [] [1,2,3,4]
让我们给 foo 一个更通用的类型签名,以便它也适用于其他问题:
foo :: (a -> b -> b) -> b -> [a] -> b
现在,回到您的问题 - 我们可以像这样编写过滤器:
-- Example 3: filter
filtero :: (a -> Bool) -> [a] -> [a]
filtero p [] = []
filtero p (x:xs) = x `filterLogic` (filtero p xs)
where
filterLogic = (\curr acc -> if (p curr) then curr:acc else acc)
这再次具有与 summa 和 reverso 非常相似的结构。因此,我们应该能够使用 foo 来重写它。假设我们要过滤列表 [1,2,3,4] 中的偶数。然后我们再次传递 foo 运算符(在本例中filterLogic
)、初始值和列表本身。filterLogic
在这个例子中,我们需要一个p
函数,称为谓词,我们必须为调用定义它:
let p = even in foo (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4]
Haskell 中的 foo 称为foldr。因此,我们使用 foldr 重写了过滤器。
let p = even in foldr (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4]
因此,过滤器可以用foldr编写,正如我们所见:
-- Solution 1: filter using foldr
filtero' :: (a -> Bool) -> [a] -> [a]
filtero' p xs = foldr (\curr acc -> if (p curr) then curr:acc else acc) [] xs
至于map,我们也可以写成
-- Example 4: map
mapo :: (a -> b) -> [a] -> [b]
mapo f [] = []
mapo f (x:xs) = x `op` (mapo f xs)
where
op = (\curr acc -> (f curr) : acc)
因此可以使用foldr重写。例如,要将列表中的每个数字乘以 2:
let f = (* 2) in foldr (\curr acc -> (f curr) : acc) [] [1,2,3,4]
因此,map可以用foldr编写,正如我们所见:
-- Solution 2: map using foldr
mapo' :: (a -> b) -> [a] -> [b]
mapo' f xs = foldr (\curr acc -> (f curr) : acc) [] xs