在IO Monad中的短路AND

6

我知道这个问题已经在这里被提出,但我不相信没有一个直截了当的答案。

我知道将副作用隐藏在(&&)中不是一件好事,但在我的情况下,副作用只是检查外部世界的某些东西(存在文件,检查修改时间等,询问用户是/否问题)。

那么有没有类似于Haskell的方法,如果cond1为false,就不执行cond2呢?

cond1, cond2 :: IO bool


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

我原本期待的是类似于cond <- all [con1, con2]或等效的表达方式,但我找不到任何东西。

更新

我看到了很多手动解决方案。我仍然觉得这个函数应该在某处存在。 惰性求值的一个优点是它不仅可以像C语言中硬编码的&&一样短路。令人惊讶的是,在Haskell中,当进入命令式模式时,甚至不能短路&&。 虽然所有的解决方案都使用if来短路求值,但是否有一种方法可以创建一个通用的惰性liftM2呢?


不使用任何特殊组合器:andM [] = return True,然后 andM (x:xs) = do b <- x; if b then andM xs else return False - ErikR
正如我在下面所说的,andM是在monad-loops库中定义的,人们期望在这里找到像这样的东西http://hackage.haskell.org/package/monad-loops-0.4.2.1/docs/Control-Monad-Loops.html#v:andM。 - Michael
@Arthur 没有看到更新。 - mb14
5个回答

8
这是Pipes.Prelude.and的作用,它遍历一个惰性流的有副作用条件,并在任何条件为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

相关链接:


4
您想要的操作可以明确地定义。
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

当然,你也可以使用反引号将此函数作为中缀运算符使用:
x `shortCircuitAnd` y == shortCircuitAnd x y

1
r1 && r2实际上是r2,因为r1if保证。因此,do r1 <- x; if r1 then y else return False可行。 - chi

4

虽然这和其他一些人所说的并没有什么不同,但是最简单的方法不就是模仿and的定义吗:

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

然后我们得到,比如说:
 > 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

如果andM没有“短路”,那么未定义的单元格将被评估并返回异常。
有点令人烦恼的是,liftM2(&&)不能像人们期望的那样工作。
编辑:我刚刚注意到,正如人们可能预期的那样,这在monad-loops软件包中定义:http://hackage.haskell.org/package/monad-loops-0.4.2.1/docs/src/Control-Monad-Loops.html#andM

3

我们可以使用像 MaybeT 这样的单子变换器,它是 MonadPlus 的一个实例。其思想是使用 guardFalse 的结果转换成将停止计算的 mzero。然后,我们将产生的 Maybe 转换回 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)

1
我认为最好在你的回答中添加一些解释,而不是留下一大块代码。 - AdamMc331

3

我会将单子定义为:

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

然后执行getAllM . mconcat . map AllM $ [con1, con2]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接