如何尽早获得IO函数中转发的结果?

Jör*_*ann 4 io haskell

我创建了一系列流程“步骤”,这些步骤的实施方式使得可以尽早输出信息。

例如

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 方法是否可以实现这一点?我是否需要管道来实现这一目标?

已经考虑过以下主题:

Haskell - 从句柄读取行而不阻塞

HTN*_*TNW 7

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)