我创建了一系列流程“步骤”,这些步骤的实施方式使得可以尽早输出信息。
例如
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
Run Code Online (Sandbox Code Playgroud)
每次从惰性 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)
Run Code Online (Sandbox Code Playgroud)
上面的代码只有在所有三行都完成后才完成。
我还尝试了“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])
Run Code Online (Sandbox Code Playgroud)
此外,只有当所有三行都完成时它才完成。
一旦我删除了中间的 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
Run Code Online (Sandbox Code Playgroud)
...它按预期工作,即使功能线位于两者之间。
那么我该怎么做才能实现这种行为呢?使用这种惰性 IO 方法是否可以实现这一点?我是否需要管道来实现这一目标?
已经考虑过以下主题:
checkAll从字面上看是说 to do getLine,然后在 ing 任何数据checkAll 之前 return递归地执行。“惰性”IO 并不是惰性求值的自然结果(普通代码是免费获得的)。这是由 引入的神奇行为unsafeInterleaveIO。对此函数的调用隐藏在库函数中,例如getContents.
你可以checkAll通过调用自己来参与惰性IO unsafeInterleaveIO。
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)
Run Code Online (Sandbox Code Playgroud)
我强烈建议使用适当的流媒体库而不是惰性 IO。惰性 IO 是一个巧妙的技巧,可以使非常简单的程序比“应该”的响应速度更快,但永远不应该依赖。本着这种精神,我要指出,conduit你建议的软件包确实有点过分了。streaming除了s之外,您不需要任何其他东西Stream,即免费的 monad 转换器FreeT。
使用该包时,streaming它看起来像这样:
import qualified System.IO as Sys
import qualified System.IO.Echo as Sys
import Streaming.Prelude as S
import Streaming as S
main :: IO ()
main = do
Sys.setInputEcho False
Sys.hSetBuffering Sys.stdout Sys.NoBuffering
-- may be not needed:
-- Sys.hSetBuffering Sys.stdin Sys.NoBuffering
S.mapM_ putChar $ showStream $ S.mapM check $ S.each ["12", "34", "56"]
putStrLn ""
check :: String -> IO String
check s = do
l <- getLine
pure (l ++ (if l == s then "==" else "/=") ++ s)
showStream :: (Show a, Monad m) => Stream (Of a) m r -> Stream (Of Char) m r
showStream xs = do
S.yield '['
r <- S.intercalates (S.each ", ") $ S.maps (\(x :> r) -> r <$ S.each (Prelude.show x)) xs
S.yield ']'
pure r
Run Code Online (Sandbox Code Playgroud)