我的函数式编程课程中有一个赋值,要求我重写几个函数,比如map
和filter
是尾递归的。
我并非100%确定如何解决此问题,但我知道您可以通过致电foldr
和foldl
来定义功能。我知道foldl
是尾递归的,所以如果我可以用filter
定义说foldl
,它也会变成尾递归吗?
答案 0 :(得分:6)
有两种方法可以使递归函数尾递归:
考虑map
函数的定义:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
在累加器传递样式中,我们有一个额外的参数来累积结果:
mapA :: (a -> b) -> [a] -> [b] -> [b]
mapA _ [] = id
mapA f (x:xs) = mapA f xs . (f x :)
原始的map
函数可以恢复如下:
map :: (a -> b) -> [a] -> [b]
map f xs = reverse $ mapA f xs []
请注意,我们需要reverse
结果。这是因为mapA
反向累积结果:
> mapA (+1) [1,2,3,4,5] []
> mapA (+1) [2,3,4,5] [2]
> mapA (+1) [3,4,5] [3,2]
> mapA (+1) [3,5] [4,3,2]
> mapA (+1) [5] [5,4,3,2]
> mapA (+1) [] [6,5,4,3,2]
> [6,5,4,3,2]
现在,考虑继续传递风格:
mapK :: (a -> b) -> [a] -> ([b] -> r) -> r
mapK _ [] k = k []
mapK f (x:xs) k = mapK f xs (k . (f x :))
原始的map
函数可以恢复如下:
map :: (a -> b) -> [a] -> [b]
map f xs = mapK f xs id
请注意,我们不需要reverse
结果。这是因为虽然mapK
反向累积了连续,但是当最终应用于基本情况时,展开连续以产生正确顺序的结果:
> mapK (+1) [1,2,3,4,5] id
> mapK (+1) [2,3,4,5] (id . (2:))
> mapK (+1) [3,4,5] (id . (2:) . (3:))
> mapK (+1) [4,5] (id . (2:) . (3:) . (4:))
> mapK (+1) [5] (id . (2:) . (3:) . (4:) . (5:))
> mapK (+1) [] (id . (2:) . (3:) . (4:) . (5:) . (6:))
> (id . (2:) . (3:) . (4:) . (5:) . (6:)) []
> (id . (2:) . (3:) . (4:) . (5:)) [6]
> (id . (2:) . (3:) . (4:)) [5,6]
> (id . (2:) . (3:)) [4,5,6]
> (id . (2:)) [3,4,5,6]
> id [2,3,4,5,6]
> [2,3,4,5,6]
请注意,在这两种情况下,我们都要做两倍的工作量:
有些函数可以在累加器传递样式中有效写入(例如sum
函数):
sumA :: Num a => [a] -> a -> a
sumA [] = id
sumA (x:xs) = sumA xs . (+ x)
原始的sum
函数可以恢复如下:
sum :: Num a => [a] -> a
sum xs = sumA xs 0
请注意,我们不需要对结果进行任何后期处理。
但是,以尾递归样式编写的列表函数总是需要反转。因此,我们不以尾递归样式编写列表函数。相反,我们依赖懒惰来处理所需的列表。
应该注意的是,延续传递风格只是累加器传递风格的一个特例。由于foldl
都是尾递归并使用累加器,因此您可以使用mapA
编写mapK
和foldl
,如下所示:
mapA :: (a -> b) -> [a] -> [b] -> [b]
mapA f xs acc = foldl (\xs x -> f x : xs) acc xs
mapK :: ([b] -> r) -> (a -> b) -> [a] -> r
mapK k f xs = foldl (\k x xs -> k (f x : xs)) k xs []
对于,mapK
如果您将k
视为id
,则会获得map
:
map :: (a -> b) -> [a] -> [b]
map f xs = foldl (\k x xs -> k (f x : xs)) id xs []
同样,filter
:
filter :: (a -> Bool) -> [a] -> [a]
filter p xs = foldl (\k x xs -> k (if p x then x : xs else xs)) id xs []
你有它,尾递归map
和filter
函数。但是,不要忘记他们实际上做了两倍的工作。此外,他们不会为无限列表工作,因为在到达列表末尾之前不会生成结果(对于无限列表永远不会发生)。
答案 1 :(得分:2)
我怀疑教授/讲师正在期待使用尾递归的解决方案"直接",即词汇,在函数的源代码中,而不是间接地,或动态"动态& #34;,其中尾递归仅在某些子例程调用范围内的运行时发生。
否则,您也可以提供,例如Prelude.foldl
作为您自定义foldl
的实现,因为它可能会在引擎盖下使用尾递归,因此可以使用它:
import Prelude as P
foldl = P.foldl
但显然不会接受这样的事情。