如果没有手工编写的递归,如何在Haskell中突破纯循环?

如果没有手工编写的递归,如何在Haskell中突破纯循环?,haskell,recursion,fold,Haskell,Recursion,Fold,我想写一个函数,通过一个列表更新累加器,直到累加器达到某个条件,或者我到达列表的末尾。例如,累加器一达到零就停止的乘积函数 我知道如何手工编写递归代码: {-# LANGUAGE BangPatterns #-} prod :: [Integer] -> Integer prod xs = go 1 xs where go 0 _ = 0 go !acc [] = acc go !acc (x:xs) = go (acc * x

我想写一个函数,通过一个列表更新累加器,直到累加器达到某个条件,或者我到达列表的末尾。例如,累加器一达到零就停止的乘积函数

我知道如何手工编写递归代码:

{-# 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,因为它以错误的顺序遍历列表,并且它“提前爆发”的方式是查看列表中的元素,而不是查看累加器(如果累加器的类型与列表元素不同,这将很重要).

看来,
scanl
是最简单的列表组合器,可以为您提供所需的内容。例如,这不会计算第二个列表的
1
10

Prelude> let takeUntil _ [] = []; takeUntil p (x:xs) = if p x then [x] else (x: takeUntil p xs)
Prelude> (last . takeUntil (==0) . scanl (*) 1) ([1..10] ++ [0..10])
0
takeUntil
似乎不存在于标准库中。这就像是
takeWhile
,但也会给出第一个失败的元素

如果您想正确地执行此操作,您应该注意最后的部分

如果您想要一个功能强大的通用解决方案,我想
mapAccumL
是一个不错的选择。

一个简单的方法是在允许提前退出的monad中进行计算,例如
或者
可能

{-# 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

您可以使用
Monoids
mconcat
@viorior:这在累加器和列表元素具有不同类型的一般情况下不起作用。您可以在本例中使用
foldr
,因为
foldr
mult
运算符一样是非严格的,所以
foldr1 mult$[1,2]+[0..]
非常快速地为您提供
0
。也许你最好举一个加法的例子,当累加器为零时,你就停止,这样你就不能从数据中判断它是什么:如果你想让它在累加器上短路,你想请求
mult\u0=0
而不是相反,如
foldr:(a->b->b)->b->[a]->b
-第二个参数是累加器。@大卫杨:在我的实际问题中,唯一的方法是查看累加器,因为函数不是像
*
那样的可交换和关联函数。我想我不应该选择“产品”作为例子。顺便说一句,你不需要bang模式
!acc
因为您在它上的模式与
0
匹配。这将迫使它自己,完全正确
scanl
为我们提供了从左到右的流程,而
foldr
是一个早期突破:
takeUntil p=foldr(\x r->head([[x]| px]+[r])[]last
在这里不是局部的,因为
scanl
总是生成一个非空列表。:)当然,在
scanl
适合的地方,
iterate
unbover
也是如此<代码>展开器甚至会自动停止。
最后。takeUntil p
可以重写为
头。takeWhile(not.p)
也有同样的效果。@WillNess:确实这里没有提到对
last
的偏爱,但理想的写作方式应该是不使用
last
,并且通过构造可以明显看出整体性。@chunksOf50你的意思是
dropWhile(not.p)>>head
。。。但这也不正确(如果
p
不成立怎么办?)。
> prod ([1..5] ++ [0..])
0