我想编写一个遍历列表的函数,更新一个累加器,直到该累加器达到某个条件或到达列表的末尾。例如,产品功能在累加器达到零后立即停止。

我知道如何通过手动编写递归来进行编码:

{-# LANGUAGE BangPatterns #-}

prod :: [Integer] -> Integer
prod xs =
    go 1 xs
  where
    go 0   _       = 0
    go !acc []     = acc
    go !acc (x:xs) = go (acc * x) xs

但是有没有办法使用折叠和其他高阶函数对此进行编码?

我想到的一件事是定义
mult 0 _ = 0
mult x y = x * y

然后使用foldl'。但是,这并不会很早就解决,因此会浪费一些性能。

我们无法使用foldr,因为它以错误的顺序通过列表,并且其“及早爆发”的方式是查看列表中的元素而不是查看累加器(如果累加器有一个类型与列表元素不同)。

最佳答案

一种简单的方法是在允许提前退出的monad中进行计算,例如EitherMaybe:

{-# LANGUAGE BangPatterns #-}
import Data.Functor ((<$))
import Data.Maybe (fromMaybe)
import Control.Monad

prod :: [Integer] -> Integer
prod = fromMaybe 0 . prodM

-- The type could be generalized to any MonadPlus Integer
prodM :: [Integer] -> Maybe Integer
prodM = foldM (\ !acc x -> (acc * x) <$ guard (acc /= 0)) 1

在计算的每个步骤中,我们都会检查累加器是否为非零。如果为零,guard会调用mplus,它会立即退出计算。例如,以下立即退出:
> prod ([1..5] ++ [0..])
0

10-08 12:35