该pipes
包允许您将数据生成与数据消费分开。您将程序编写为 logString
的生产者,然后在运行时选择如何使用这些String
s。
例如,假设您有以下简单程序:
import Control.Proxy
program :: (Proxy p) => () -> Producer p String IO r
program () = runIdentityP $ forever $ do
lift $ putStrLn "Enter a string:"
str <- lift getLine
respond $ "User entered: " ++ str
该类型表示它是一个Producer
of String
s(在本例中为日志字符串),它也可以IO
使用lift
. IO
因此,对于不涉及日志记录的普通命令,您只需使用lift
. 每当您需要记录某些内容时,您都可以使用该respond
命令,该命令会生成一个String
.
这会创建一个抽象的字符串生产者,它没有指定如何使用它们。这让我们可以推迟选择如何使用生成String
的 s。每当我们调用该respond
命令时,我们都会抽象地将我们的日志字符串交给一些尚未指定的下游阶段,该阶段将为我们处理它。
现在让我们编写一个程序,该程序Bool
从命令行获取一个标志,该标志指定是否将输出写入stdout
或写入文件"my.log"
。
import System.IO
import Options.Applicative
options :: Parser Bool
options = switch (long "file")
main = do
useFile <- execParser $ info (helper <*> options) fullDesc
if useFile
then do
withFile "my.log" WriteMode $ \h ->
runProxy $ program >-> hPutStrLnD h
else runProxy $ program >-> putStrLnD
如果用户在命令行中没有提供任何标志,则useFile
默认为False
,表示我们要登录到stdout
。如果用户提供--file
标志,则useFile
默认为True
,表示我们要登录到"my.log"
。
现在,检查两个if
分支。第一个分支使用运算符String
将生成的 s馈送program
到文件中(>->)
。可以将其视为接受 a并创建 s 的抽象消费者的hPutStrLnD
东西,该消费者将每个字符串写入该句柄。当我们连接到时,我们将每个日志字符串发送到文件:Handle
String
program
hPutStrLnD
$ ./log
Enter a string:
Test<Enter>
User entered: Test
Enter a string:
Apple<Enter>
User entered: Apple
^C
$
第二个if
分支将String
s 馈送到putStrLnD
,它只是将它们写入stdout
:
$ ./log --file
Enter a string:
Test<Enter>
Enter a string:
Apple<Enter>
^C
$ cat my.log
User entered: Test
User entered: Apple
$
尽管从生产中解耦生成,pipes
仍然立即流式传输所有内容,因此输出阶段(即hPutStrLnD
和putStrLnD
)将在生成后立即写出,Strings
并且不会缓冲String
s 或等待程序完成。
请注意,通过将生成与实际日志记录操作分离,我们获得了在最后时刻String
注入消费者依赖项的能力。String
要了解有关如何使用的pipes
更多信息,我建议您阅读教程。pipes