How to make boolean logic with IO Monad more expressive?

Hi, I think `a || b && c` is more clear than ``` if a then True else if b then if c then True else False else False ``` And some languages, `pureComputing || (ioOperation && pure2)` involves IO only when pureComputing is False. So in Haskell, is it possible to get both benefits? ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ``` ``` if pureComputing then return True else do status <- ioOperation if status then return pure2 else return False -- Looks ugly ```

Not with the standard `||` and `&&` operations. You'd have to define new versions with a `Monad` constraint. The `monad-loops` package is pretty nice for short-circuiting stuff. See, for example, `andM` and `orM`. But it doesn't provide any binary operators. https://hackage.haskell.org/package/monad-loops / Emil Den 2019-05-27 kl. 07:07, skrev Magicloud Magiclouds:
Hi,
I think `a || b && c` is more clear than ``` if a then True else if b then if c then True else False else False ```
And some languages, `pureComputing || (ioOperation && pure2)` involves IO only when pureComputing is False.
So in Haskell, is it possible to get both benefits? ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ``` ``` if pureComputing then return True else do status <- ioOperation if status then return pure2 else return False -- Looks ugly ``` _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

Good call. I see `orM` is actually a wrapper of the "ugly" nested-if. So operators can be another wrapper away. Thanks. On Mon, May 27, 2019 at 3:35 PM Emil Axelsson <78emil@gmail.com> wrote:
Not with the standard `||` and `&&` operations. You'd have to define new versions with a `Monad` constraint.
The `monad-loops` package is pretty nice for short-circuiting stuff. See, for example, `andM` and `orM`. But it doesn't provide any binary operators.
https://hackage.haskell.org/package/monad-loops
/ Emil
Den 2019-05-27 kl. 07:07, skrev Magicloud Magiclouds:
Hi,
I think `a || b && c` is more clear than ``` if a then True else if b then if c then True else False else False ```
And some languages, `pureComputing || (ioOperation && pure2)` involves IO only when pureComputing is False.
So in Haskell, is it possible to get both benefits? ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ``` ``` if pureComputing then return True else do status <- ioOperation if status then return pure2 else return False -- Looks ugly ``` _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

On May 27, 2019, at 1:07 AM, Magicloud Magiclouds
wrote: ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ```
With two helpers: (<&&>) :: IO Bool -> IO Bool -> IO Bool (<&&>) ma mb = ma >>= (\a -> if not a then return False else mb) (<||>) :: IO Bool -> IO Bool -> IO Bool (<||>) ma mb = ma >>= (\a -> if a then return True else mb) you'd write: pure preComputing <||> (ioOperation <&&> pure pure2) You could even define suitable fixity to make <&&> have higher precedence than <||> and not need any parentheses. -- Viktor.

Cool. Thanks.
On Mon, May 27, 2019 at 5:10 PM Viktor Dukhovni
On May 27, 2019, at 1:07 AM, Magicloud Magiclouds
wrote: ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ```
With two helpers:
(<&&>) :: IO Bool -> IO Bool -> IO Bool (<&&>) ma mb = ma >>= (\a -> if not a then return False else mb)
(<||>) :: IO Bool -> IO Bool -> IO Bool (<||>) ma mb = ma >>= (\a -> if a then return True else mb)
you'd write:
pure preComputing <||> (ioOperation <&&> pure pure2)
You could even define suitable fixity to make <&&> have higher precedence than <||> and not need any parentheses.
-- Viktor.
_______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

Viktor Dukhovni
On May 27, 2019, at 1:07 AM, Magicloud Magiclouds
wrote: ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ```
With two helpers:
(<&&>) :: IO Bool -> IO Bool -> IO Bool (<&&>) ma mb = ma >>= (\a -> if not a then return False else mb)
(<||>) :: IO Bool -> IO Bool -> IO Bool (<||>) ma mb = ma >>= (\a -> if a then return True else mb)
These generalise to any Applicative, I think: import Control.Applicative (liftA2) (<&&>) = liftA2 (&&) (<||>) = liftA2 (||) -- Jack

With side effect, they are not the same.
Viktor's nested-if does not trigger unnecessary IO action, while liftA2 does.
On Mon, May 27, 2019 at 5:38 PM Jack Kelly
Viktor Dukhovni
writes: On May 27, 2019, at 1:07 AM, Magicloud Magiclouds
wrote: ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ```
With two helpers:
(<&&>) :: IO Bool -> IO Bool -> IO Bool (<&&>) ma mb = ma >>= (\a -> if not a then return False else mb)
(<||>) :: IO Bool -> IO Bool -> IO Bool (<||>) ma mb = ma >>= (\a -> if a then return True else mb)
These generalise to any Applicative, I think:
import Control.Applicative (liftA2)
(<&&>) = liftA2 (&&) (<||>) = liftA2 (||)
-- Jack _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

Magicloud Magiclouds
With side effect, they are not the same. Viktor's nested-if does not trigger unnecessary IO action, while liftA2 does.
Ah, of course. Still, there's no need to restrict their type to IO; any Monad should be fine. -- Jack
On Mon, May 27, 2019 at 5:38 PM Jack Kelly
wrote: Viktor Dukhovni
writes: On May 27, 2019, at 1:07 AM, Magicloud Magiclouds
wrote: ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ```
With two helpers:
(<&&>) :: IO Bool -> IO Bool -> IO Bool (<&&>) ma mb = ma >>= (\a -> if not a then return False else mb)
(<||>) :: IO Bool -> IO Bool -> IO Bool (<||>) ma mb = ma >>= (\a -> if a then return True else mb)
These generalise to any Applicative, I think:
import Control.Applicative (liftA2)
(<&&>) = liftA2 (&&) (<||>) = liftA2 (||)

On Mon, May 27, 2019 at 07:37:37PM +1000, Jack Kelly wrote:
Viktor Dukhovni
writes: On May 27, 2019, at 1:07 AM, Magicloud Magiclouds
wrote: ``` status <- ioOperation -- IO-ed anyway return $ pureComputing || (status && pure2) ```
With two helpers:
(<&&>) :: IO Bool -> IO Bool -> IO Bool (<&&>) ma mb = ma >>= (\a -> if not a then return False else mb)
(<||>) :: IO Bool -> IO Bool -> IO Bool (<||>) ma mb = ma >>= (\a -> if a then return True else mb)
These generalise to any Applicative, I think:
import Control.Applicative (liftA2)
(<&&>) = liftA2 (&&) (<||>) = liftA2 (||)
As written, the above Applicative version won't short-circuit (is strict in both arguments) in the IO Monad. For example, the below will still read stdin: pure False <&&> (getLine >>= return . read) The generalized Monadic version will short-circuit. (<&&>) :: Monad m => m Bool -> m Bool -> m Bool (<&&>) ma mb = ma >>= (\a -> if not a then return False else mb) (<||>) :: Monad m => m Bool -> m Bool -> m Bool (<||>) ma mb = ma >>= (\a -> if a then return True else mb) -- Viktor.

On Mon, May 27, 2019 at 05:46:11AM -0400, Viktor Dukhovni wrote:
The generalized Monadic version will short-circuit.
(<&&>) :: Monad m => m Bool -> m Bool -> m Bool (<&&>) ma mb = ma >>= (\a -> if not a then return False else mb)
(<||>) :: Monad m => m Bool -> m Bool -> m Bool (<||>) ma mb = ma >>= (\a -> if a then return True else mb)
I should mention that a compatible even better generalized interface is available via Control.Selective: (<||>): http://hackage.haskell.org/package/selective-0.2/docs/Control-Selective.html... (<&&>): http://hackage.haskell.org/package/selective-0.2/docs/Control-Selective.html... -- Viktor.
participants (4)
-
Emil Axelsson
-
Jack Kelly
-
Magicloud Magiclouds
-
Viktor Dukhovni