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

问题描述 投票:0回答:1

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

例如

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

已考虑以下主题:

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

haskell io
1个回答
1
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.