我正在尝试编写reduce
类型的[a -> a -> a] -> [a] -> a
函数,该函数会将n
值列表与n - 1
二元运算符列表一起折叠,如以下示例:
reduce [(+), (*), (-)] [2, 3, 5, 7]
运行此示例应返回18,并且等效于以下表达式:
(((2 + 3) * 5) - 7)
使用递归实现此函数非常简单:
reduce (f : fs) (x1 : x2 : xs) = reduce fs (f x1 x2 : xs)
这个函数的概念非常接近左折叠,唯一的区别是它使用多个运算符而不是单个运算符,我认为它也可以使用foldl
或等效的高阶函数来实现,使用显式递归。
考虑到这一点,并使用应用功能,我提出了以下实现:
reduce fs (x : xs) = foldl (flip id) x (getZipList $ ZipList fs <*> ZipList xs)
由于依赖ZipList看起来相当冗长和冗余,我现在想知道是否有更好的实现这个函数,使用相同的方法不使用显式递归。
答案 0 :(得分:3)
zipWith
是你失踪的功能:
reduce fs (x:xs) = foldl (flip id) x $ zipWith flip fs xs
基本上,通过将运算符与输入值组合来创建单个参数函数的列表(使用flip,因此该值用作第二个参数而不是第一个参数)。其余的与你的功能相同。
只是一个设计说明,目前函数不是全部 - 如果为值传递一个空列表,则唯一可以返回的值是bottom(即undefined
)。这是因为无法创建a
值。这样的功能可能会更好:
reduce2 :: [a -> b -> a] -> a -> [b] -> a
reduce2 fs x xs = foldl (flip id) x $ zipWith flip fs xs
由于第一个值是单独传递的,因此任何一个列表都可以为空,并且该函数仍然会完成(如果其中一个列表比另一个列表短,则它将忽略它不需要的值)。它也更通用,因为值列表可能与函数的结果具有不同的类型。
答案 1 :(得分:1)
怎么样:
reduce :: [a->a->a] -> [a] -> a
reduce ops args =
let ops' = map flip ops
appliedOps = zipWith ($) ops' $ tail args
in foldl' (flip ($)) (head args) appliedOps
另请参阅this question关于Haskell中的函数应用程序。请注意,最后一个答案实际上使用了ZipList
和Control.Applicative。