折叠值列表与二元运算符列表

时间:2014-02-15 04:15:54

标签: haskell fold higher-order-functions applicative

我正在尝试编写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看起来相当冗长和冗余,我现在想知道是否有更好的实现这个函数,使用相同的方法不使用显式递归。

2 个答案:

答案 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。