6

I know the question has been asked there but I can't believe there is no straight answer.

I understand it's not good to hide side-effect inside a (&&) but in my case the side-effect are just checking something in the outstide world (existence of a file, check modification time etc, ask user a yes/no quistion).

So what is the haskell way to something like this, so that cond2 is not executed if cond1 is false.

cond1, cond2 :: IO bool


main = do
   cond <- liftM2 (&&) con1 con2
   if cond
   then   result1
   else   result2

I was expecting something like cond <- all [con1, con2] or equivalent, but I can't find anything.

Update

I can see lots of manual solution. I'm still puzzled that this function doesn't exists somewhere. One advantage of lazzy evaluation is it doesn't only short-circuit for hard-coded && like in C. It is really strange that when in imperative mode, Haskell can't even short-circuit &&. Although, all solution use somehow and if to short-circuit the evaluation. Is there not a way to make a generic lazzy liftM2 ?

Community
  • 1
  • 1
mb14
  • 22,276
  • 7
  • 60
  • 102
  • Without using any special combinators: `andM [] = return True` and then `andM (x:xs) = do b <- x; if b then andM xs else return False` – ErikR Nov 23 '14 at 18:47
  • As I said below, `andM` is defined in the monad-loops library, where one expects to find things like this http://hackage.haskell.org/package/monad-loops-0.4.2.1/docs/Control-Monad-Loops.html#v:andM – Michael Nov 24 '14 at 16:51
  • @Arthur didn't see the update. – mb14 Nov 24 '14 at 20:17

5 Answers5

8

This is what Pipes.Prelude.and does, going over a lazy stream of effectfully-generated conditionals and short-circuiting if any of them are False:

import Pipes (each)
import qualified Pipes.Prelude as Pipes

conds :: [IO Bool]
conds = ...

main = do
    cond <- Pipes.and (each conds >-> Pipes.sequence)
    print cond

Relevant links:

Gabriella Gonzalez
  • 34,863
  • 3
  • 77
  • 135
4

The operation you want is simple to define explicitly.

shortCircuitAnd :: Monad m => m Bool -> m Bool -> m Bool
shortCircuitAnd x y = do
   r1 <- x -- perform effect of x
   if r1 
     then y -- perform effect of y and return result
     else return False -- do *not* evaluate or perform effect of y

Of course, you can use this function infix as well, using backticks:

x `shortCircuitAnd` y == shortCircuitAnd x y
Christian Conkle
  • 5,932
  • 1
  • 28
  • 52
  • 1
    `r1 && r2` is actually `r2` since `r1` is guaranteed by the `if`. Hence, `do r1 <- x ; if r1 then y else return False` works. – chi Nov 23 '14 at 18:45
4

This isn't different from what some others are saying, but isn't it simplest just to emulate the definition of and:

 andM = foldr (&&&) (return True)
  where ma &&& mb = ma >>= \p -> if p then mb else return p

then we get, say:

 > let test1 = putStrLn "This test succeeds" >> return True
 > let test2 = putStrLn "This test fails" >> return  False
 > andM [test1,test2,undefined,undefined]
 This test succeeds
 This test fails
 False

If andM didn't 'short-circuit', then the undefined cell would have been evaluated and returned an exception.

It is a little irritating that liftM2 (&&) doesn't work as one hoped.

Edit: I just noticed that, as one might have expected, this is defined in the monad-loops package http://hackage.haskell.org/package/monad-loops-0.4.2.1/docs/src/Control-Monad-Loops.html#andM

Michael
  • 2,889
  • 17
  • 16
3

We can use a monad transformer like MaybeT, which is an instance of MonadPlus. The idea is to use guard to turn False results into mzeros that will halt the computation. And then we convert the resulting Maybe back into a Bool.

import Control.Monad.Trans
import Control.Monad.Trans.Maybe

effyand :: (Functor m, Monad m) => [m Bool] -> m Bool
effyand = fmap isJust . runMaybeT . mapM_ (lift >=> guard)
danidiaz
  • 26,936
  • 4
  • 45
  • 95
3

I'd define a monoid like

newtype AllM m = AllM { getAllM :: m Bool }

instance (Monad m) => Monoid (AllM m) where
    mempty = AllM (return True)
    mappend (AllM u) (AllM v) = AllM $
        u >>= \x -> if x then v else return False

and then getAllM . mconcat . map AllM $ [con1, con2].

Petr
  • 62,528
  • 13
  • 153
  • 317