另一种思考方式 - 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 和 [] 用于反向)以及第一个值和递归调用之间的运算符(+ 用于 summa 和 (\q qs -> qs ++ [q])反向)。所以上面例子的函数结构一般可以看成是
-- 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]
如我们所见,filter 可以用 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