问题
I want to write a function that goes through a list updating an accumulator until that accumulator reaches a certain condition or I get to the end of the list. For example, a product function that stops as soon as its accumulator reaches zero.
I know how to code it by writing the recursion by hand:
{-# 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
but is there a way to code this using folds and other higher order functions?
One thing that comes to mind is defining
mult 0 _ = 0
mult x y = x * y
and then using foldl'. However, this doesn't break out early so its a bit wasteful of performance.
We can't use foldr since it goes through the list in the wrong order and its way of "breaking out early" is by looking at the elements of the list instead of looking at the accumulator (this would have mattered if the accumulator had a different type than the list elements).
回答1:
One simple way is to do the computation in a monad that allows to escape early, such as Either or Maybe:
{-# 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
At each step of the computation we check if the accumulator is nonzero. And if it's zero, guard invokes mplus, which exits the computation immediately. For example the following exits immediately:
> prod ([1..5] ++ [0..])
0
回答2:
It seems that scanl is the simplest list combinator that gives you what you want. For example this won't evaluate the 1 to 10 of the second list.
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 doesn't seem to exist in the standard library. It's like takeWhile but also gives you the first failing element.
If you want to do this properly you should take care with that partial last.
If you want a powerful general solution I guess mapAccumL is the way to go.
来源:https://stackoverflow.com/questions/21238399/how-do-i-break-out-of-a-pure-loop-in-haskell-without-hand-written-recursion