Alexey Romanov
Alexey Romanov

Reputation: 170745

A lazy version of liftM2

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

Answers (3)

sepp2k
sepp2k

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

Daniel Wagner
Daniel Wagner

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 unsafeInterleaveing 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

Alexey Romanov
Alexey Romanov

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

Related Questions