liftM2 的惰性版本

A lazy version of liftM2

为了回答,我写了

andM :: (Monad m) => m Boolean -> m Boolean -> m Boolean
andM m1 m2 = do
  x <- m1
  case x of
    True -> m2
    False -> return False

如果没有必要,它不会评估 m2,这与 liftM2 (&&) m1 m2(在 IO 中)不同。

有没有一种方法可以将 liftM2Lazy 写成与 liftM2 相同的类型(或在某种程度上更受限制?),从而保持惰性?所以例如liftM2Lazy (&&)andMliftM2Lazy (||)orM(具有明显的定义)等无法区分?

一种使用spoon的方法,有点作弊:

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

不,这通常是不可能的 -- 它需要源到源的转换才能将惰性函数提升为惰性单子函数。

对于 IO 具体来说,人们可以提前知道 哪个 函数在哪个参数中是惰性的(以及它是多么 "deeply" 惰性——是,需要评估返回的结构多远才能发现是否需要执行其他操作),可以使用 IO 的异常捕获和 unsafeInterleaveing 功能来编写通用提升功能。但是这些函数是如此具体并且如此容易被错误使用,我怀疑你最好不要编写它们。

这对于一般的 monad 是不可能的,但是对于 IO 的特定情况,可以通过使用 unsafeInterleaveIO 来很容易地(并且相对安全地)实现它,这会使 IO 操作变得惰性:

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

在完全相同的参数中结果将是惰性的,其中 f 是惰性的,因此它甚至适用于不遵循与 [ 相同的从左到右短路逻辑的函数=15=] 和 ||:

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