考虑片段 -
getLine >>= \_ -> getLine >>= putStr
Run Code Online (Sandbox Code Playgroud)
它做了合理的事情,要求两次字符串,然后打印最后一个输入.因为编译器无法知道外部效果getLine有什么,所以即使我们丢弃了第一个结果,它也必须执行它们.
我需要的是将IO Monad包装到另一个Monad(M)中,允许IO计算有效地为NOP,除非使用它们的返回值.所以上面的程序可以改写成类似的东西 -
runM $ lift getLine >>= \_ -> lift getLine >>= lift putStr
Run Code Online (Sandbox Code Playgroud)
哪里
runM :: M a -> IO a
lift :: IO a -> M a
Run Code Online (Sandbox Code Playgroud)
并且仅要求用户输入一次.
但是,我无法弄清楚如何编写这个Monad以达到我想要的效果.我不确定它是否可能.有人可以帮忙吗?
ham*_*mar 11
懒惰的IO通常使用unsafeInterleaveIO :: IO a -> IO a,它会延迟IO操作的副作用,直到需要它的结果,所以我们可能不得不使用它,但是让我们先解决一些小问题.
首先,lift putStr不会键入check,因为putStr类型String -> IO ()和lift类型IO a -> M a.我们必须使用类似的东西lift . putStr.
其次,我们必须区分应该是懒惰的IO操作和不应该操作的IO操作.否则putStr永远不会执行,因为我们没有()在任何地方使用它的返回值.
考虑到这一点,这似乎适用于您的简单示例,至少.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import System.IO.Unsafe
newtype M a = M { runM :: IO a }
deriving (Monad)
lazy :: IO a -> M a
lazy = M . unsafeInterleaveIO
lift :: IO a -> M a
lift = M
main = runM $ lazy getLine >> lazy getLine >>= lift . putStr
Run Code Online (Sandbox Code Playgroud)
然而,正如CA McCann指出的那样,你可能不应该将它用于任何严肃的事情.Lazy IO已经不赞成了,因为它很难推断出副作用的实际顺序.这会让它变得更难.
考虑这个例子
main = runM $ do
foo <- lazy readLn
bar <- lazy readLn
return $ foo / bar
Run Code Online (Sandbox Code Playgroud)
读入的两个数字的顺序将完全未定义,并且可能根据编译器版本,优化或星形的对齐而改变.这个名字unsafeInterleaveIO长而丑陋是有充分理由的:提醒你使用它的危险.让人们知道它何时被使用而不是隐藏在monad中是一个好主意.
没有明智的方法可以做到这一点,因为说实话,这并不是一件明智的事情.引入monadic I/O 的全部目的是在存在惰性求值的情况下为效果提供明确定义的排序.如果你真的必须把它扔出窗外当然是可能的,但是我不确定这会解决什么实际问题,而不是更容易编写令人困惑的错误代码.
也就是说,以受控的方式引入这种东西就是"懒惰的IO"已经做到的.对此的"原始"操作,unsafeInterleaveIO大致实现return . unsafePerformIO,加上一些细节,使事情表现得更好.应用unsafeInterleaveIO的一切,通过隐藏在你的"懒IO"单子的绑定操作,可能会完成不明智的概念,你是后.
小智 5
你正在寻找的不是真正的单子,除非你想要处理不安全的东西,比如unsafeInterleaveIO.
相反,这里更清晰的抽象是箭头.
我认为,以下可能有效:
data Promise m a
= Done a
| Thunk (m a)
newtype Lazy m a b =
Lazy { getLazy :: Promise m a -> m (Promise m b) }
Run Code Online (Sandbox Code Playgroud)