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中(。

有没有办法用与liftM2相同的类型(或以某种方式更受限制?(编写liftM2Lazy,从而总体上保持懒惰?因此,例如liftM2Lazy (&&)andMliftM2Lazy (||)orM(具有明显的定义(等没有区别?

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

特别是对于IO,并且事先知道函数在哪些参数中是懒惰的(以及它有多"深度"懒惰 - 也就是说,需要评估返回的结构多远才能发现是否需要执行另一个动作(,可以使用IO的异常捕获和unsafeInterleave能力来编写一个通用的提升函数。但是这样的函数是如此具体,如此容易错误地使用,以至于我怀疑你最好不要写它们。

这对于一般的monads是不可能的,但是对于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是懒惰的,因此它甚至适用于不遵循与&&||相同的从左到右短路逻辑的函数:

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

一种使用勺子的方法,这有点作弊:

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

最新更新