Reputation: 170745
To answer another question, I wrote
andM :: (Monad m) => m Boolean -> m Boolean -> m Boolean
andM m1 m2 = do
x <- m1
case x of
True -> m2
False -> return False
which doesn't evaluate m2
if not necessary, unlike liftM2 (&&) m1 m2
(in IO
).
Is there a way to write liftM2Lazy
with the same type as liftM2
(or more restricted in some way?) which preserves laziness in general? So that e.g. liftM2Lazy (&&)
is indistinguishable from andM
, liftM2Lazy (||)
from orM
(with the obvious definition), etc.?
Upvotes: 2
Views: 107
Reputation: 370162
This is not possible for general monads, but for the specific case of IO
it can be achieved pretty easily (and comparatively safely) by using unsafeInterleaveIO
, which turns an IO action lazy:
import System.IO.Unsafe
liftIO2Lazy :: (a -> b -> c) -> IO a -> IO b -> IO c
liftIO2Lazy f io1 io2 = do
x <- unsafeInterleaveIO io1
y <- unsafeInterleaveIO io2
return $ f x y
The result will be lazy in exactly the same arguments, in which f
is lazy, so it works even for functions that don't follow the same left-to-right short-circuit logic as &&
and ||
:
ioTrue = putStrLn "TRUE" >> return True
ioFalse = putStrLn "FALSE" >> return False
liftIO2Lazy (&&) ioTrue ioFalse
-- Prints both messages
liftIO2Lazy (||) ioTrue ioFalse
-- Only prints TRUE
liftIO2Lazy (flip (||)) ioTrue ioFalse
-- Only prints FALSE
liftIO2Lazy (const (const 42)) ioTrue ioFalse
-- No output
Upvotes: 3
Reputation: 152837
No, this is not possible in general -- it requires a source-to-source translation to lift lazy functions to lazy monadic functions.
For IO
specifically, and where one knows in advance which arguments a function is lazy in (and how "deeply" lazy it is -- that is, how far into the returned structure one needs to evaluate to discover whether one needs to execute the other action), one can use IO
's exception-catching and unsafeInterleave
ing powers to write a generic lifting function. But such functions are so specific and so easy to use incorrectly that I suspect you're better off not writing them at all.
Upvotes: 4
Reputation: 170745
An approach using spoon, which is a bit of cheating:
liftM2Lazy f m1 m2 =
case teaspoon $ f undefined undefined of
Just res -> return res
Nothing ->
do x1 <- m1
case teaspoon $ f x1 undefined of
Just res -> return res
Nothing ->
do x2 <- m2
return $ f x1 x2
Upvotes: 0