How do I break out of a pure loop in Haskell without hand-written recursion?

浪子不回头ぞ 提交于 2019-12-05 01:51:52

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!