在定义函数中使用折叠

时间:2019-02-06 07:40:33

标签: haskell folding

向我介绍了在定义函数中使用fold的方法。我有一个想法,但我不确定为什么要这么做。对我来说,感觉就像是简化数据类型和数据值的名称一样。如果可以向我展示一些使用fold的示例,那就太好了。

data List a = Empty | (:-:) a (List a)

--Define elements
List a :: *
[] :: List a
(:) :: a -> List a  -> List a

foldrList :: (a -> b -> b) -> b -> List a -> b
foldrList f e Empty = e
foldrList f e (x:-:xs) = f x (foldrList f e xs)

2 个答案:

答案 0 :(得分:2)

折叠的想法很强大。折叠函数(Haskell基础库中的foldrfoldl)来自称为高阶函数的函数族(对于那些不知道的人-这些函数将函数作为参数或返回函数作为其输出)。

由于可以更清楚地表达程序的意图,因此可以使代码更清晰。使用fold函数编写的函数强烈表明有意遍历该列表并重复应用该函数以获得输出。对于简单的程序,使用标准的递归方法是很好的选择,但是当复杂度增加时,可能难以快速了解正在发生的事情。

由于传递函数作为参数的性质,可以通过折叠实现更大的代码重用。如果程序的某些行为受到布尔值或枚举值的影响,则可以将该行为抽象为单独的函数。然后可以将单独的函数用作fold的参数。这样可以实现更大的灵活性和简便性(因为有2个更简单的功能与1个更复杂的功能)。

高阶函数对于Monads也是必不可少的。

对这个问题的评论也应归功于它的多样性和信息性。

答案 1 :(得分:2)

高阶函数,例如foldrfoldlmapzipWith和&c。捕获递归的常见模式,因此可以避免手动编写递归定义。这使您的代码更高级,更易读:程序员不必单步执行代码并推断递归函数的功能,而可以推理出更高级别组件的组成。

举一个极端的例子,考虑手动递归计算标准差:

standardDeviation numbers = step1 numbers
  where

    -- Calculate length and sum to obtain mean
    step1 = loop 0 0
      where
        loop count sum (x : xs) = loop (count + 1) (sum + x) xs
        loop count sum [] = step2 sum count numbers

    -- Calculate squared differences with mean
    step2 sum count = loop []
      where
        loop diffs (x : xs) = loop ((x - (sum / count)) ^ 2 : diffs) xs
        loop diffs [] = step3 count diffs

    -- Calculate final total and return square root
    step3 count = loop 0
      where
        loop total (x : xs) = loop (total + x) xs
        loop total [] = sqrt (total / count)

(为了公平起见,我还对求和进行内联,因此有点过分,但这大致就是通常用命令式语言(手动循环)完成的方式。)

现在考虑使用对标准函数的调用组成的版本,其中一些是高阶的:

standardDeviation numbers          -- The standard deviation
  = sqrt                           -- is the square root
  . mean                           -- of the mean
  . map (^ 2)                      -- of the squares
  . map (subtract                  -- of the differences
      (mean numbers))              --   with the mean
  $ numbers                        -- of the input numbers
  where                            -- where
    mean xs                        -- the mean
      = sum xs                     -- is the sum
      / fromIntegral (length xs)   -- over the length.

我希望这种更具说明性的代码也更具可读性-无需过多注释,就可以用两行代码整齐地编写。显然,它比低级递归版本更正确。

此外,summaplength都可以用折叠以及许多其他标准功能(例如product,{{1})来实现},andor等。折叠不仅对列表而且对各种容器(参见concat类型类)都是极为普遍的操作,因为它捕获了从容器的所有元素中递增计算的模式。

使用折叠而不是手动递归的最后一个原因是性能:由于懒惰和优化,GHC在使用基于Foldable的函数时知道如何执行操作,因此编译器可能会融合一系列折叠(地图, &c。)一起在运行时合并成一个循环。