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 (&&)
与 andM
、liftM2Lazy (||)
与 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
的异常捕获和 unsafeInterleave
ing 功能来编写通用提升功能。但是这些函数是如此具体并且如此容易被错误使用,我怀疑你最好不要编写它们。
这对于一般的 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
为了回答
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 (&&)
与 andM
、liftM2Lazy (||)
与 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
的异常捕获和 unsafeInterleave
ing 功能来编写通用提升功能。但是这些函数是如此具体并且如此容易被错误使用,我怀疑你最好不要编写它们。
这对于一般的 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