我创建了一系列流程“步骤”,这些步骤的实施方式使得可以尽早输出信息。
例如
mainFromSettings :: Settings -> IO ()
...
mainFromSettings ... =
do
Sys.setInputEcho False
Sys.hSetBuffering Sys.stdout Sys.NoBuffering
sContent <- getContents
let
records :: [[String]]
records = Rcr.fromContent rcrConfig sContent
...
print records
每次从惰性 IO 流“sContent”构建记录时,上面的代码片段都会打印一条记录。
我想这是这样工作的,因为一旦输入一行,“打印记录”就会占据准备打印的列表的头部。我还假设当列表的顺序相反时这将不起作用。
不幸的是,在其中一个流程步骤中它并不是这样工作的。对于此流程步骤,在显示单个字符之前,记录列表必须完整。这个函数的不同之处在于一个额外的 IO 函数,它为每条记录提供额外的信息。
为了理解这个问题,我创建了以下代码来模拟这个额外的 IO 功能。该代码还模仿已经可用的记录(以避免与打开句柄有关的冲突)。
import qualified System.IO as Sys
import qualified System.IO.Echo as Sys
main :: IO ()
main =
Sys.setInputEcho False >>
Sys.hSetBuffering Sys.stdout Sys.NoBuffering >>
Sys.hSetBuffering Sys.stdin Sys.NoBuffering >>
checkAll ["12","34","56"] >>=
print
checkAll :: [String] -> IO [String]
checkAll [] = return []
checkAll (s:lrs) =
do
l <- getLine
let
symbol = if l == s then "==" else "/="
p1 = l ++ symbol ++ s
p2 <- checkAll lrs
return (p1 : p2)
上面的代码只有在所有三行都完成后才完成。
我还尝试了“foldrM”替代方案,但它不起作用,实际上:
import qualified System.IO as Sys
import qualified System.IO.Echo as Sys
import qualified Data.Foldable as Fld
main :: IO ()
main =
Sys.setInputEcho False >>
Sys.hSetBuffering Sys.stdout Sys.NoBuffering >>
Sys.hSetBuffering Sys.stdin Sys.NoBuffering >>
checkAll [] ["12","34","56"] >>=
print
checkAll :: [String] -> [String] -> IO [String]
checkAll = Fld.foldrM f
where
f :: String -> [String] -> IO [String]
f s ls =
do
l <- getLine
if l == s
then return (ls ++ [l ++ "==" ++ s])
else return (ls ++ [l ++ "/=" ++ s])
还有这个,只有当所有三行都完成时才完成。
一旦我删除了中间的 IO 功能,就像这里:
import qualified System.IO as Sys
import qualified System.IO.Echo as Sys
main :: IO ()
main =
Sys.setInputEcho False >>
Sys.hSetBuffering Sys.stdout Sys.NoBuffering >>
Sys.hSetBuffering Sys.stdin Sys.NoBuffering >>
getContents >>=
print . lines
...它按预期工作,即使功能线位于两者之间。
那么我该怎么做才能实现这种行为呢?使用这种惰性 IO 方法是否可以实现这一点?我是否需要管道来实现这一目标?
已考虑以下主题:
checkAll
字面上对 do
getLine
说,然后递归地执行 checkAll
before return
ing 任何数据。 “惰性”IO 并不是惰性求值的自然结果(普通代码是免费获得的)。这是由unsafeInterleaveIO
引入的神奇行为。对此函数的调用隐藏在 getContents
等库函数中。
你可以通过自己调用
checkAll
让unsafeInterleaveIO
参与懒惰IO。
import qualified System.IO as Sys
import qualified System.IO.Echo as Sys
import qualified System.IO.Unsafe as Sys
main :: IO ()
main = do
Sys.setInputEcho False
Sys.hSetBuffering Sys.stdout Sys.NoBuffering
Sys.hSetBuffering Sys.stdin Sys.NoBuffering
print =<< checkAll ["12", "34", "56"]
checkAll :: [String] -> IO [String]
checkAll [] = return []
checkAll (s : lrs) = do
l <- getLine
let symbol = if l == s then "==" else "/="
p1 = l ++ symbol ++ s
p2 <- Sys.unsafeInterleaveIO $ checkAll lrs -- just added a call here
return (p1 : p2)
我do强烈建议使用适当的流媒体库而不是惰性IO。惰性 IO 是一个巧妙的技巧,它可以使非常简单的程序比“应有”的响应速度更快,但永远不应该依赖它来保证程序的正确性。本着这种精神,我要指出,您建议的
conduit
包确实太过分了。你只需要 streaming
的 Stream
,也就是免费的 monad 转换器 FreeT
。