haskell - 为什么不打印强制整个惰性 IO 值?

标签 haskell conduit haskell-pipes http-conduit lazy-io

我正在使用http-client使用 TLS 连接获取响应正文的教程。既然我可以观察到 print 是由 withResponse 调用的,为什么 print 不强制将整个响应发送到以下片段中的输出?

withResponse request manager $ \response -> do
    putStrLn $ "The status code was: " ++
    body <- (responseBody response)
    print body

我需要这样写:

response <- httpLbs request manager

putStrLn $ "The status code was: " ++
           show (statusCode $ responseStatus response)
print $ responseBody response

我想要打印的正文是一个惰性字节字符串。我仍然不确定是否应该期望 print 打印整个值。

instance Show ByteString where
    showsPrec p ps r = showsPrec p (unpackChars ps) r

最佳答案

这与懒惰无关,而是与 Response L.ByteString 之间的差异有关。你可以使用 Simple 模块,以及 Response BodyReader您可以通过 TLS 模块获得。

您注意到 BodyReaderIO ByteString 。但特别是,这是一个可以重复的操作,每次都使用下一个字节 block 。它遵循的协议(protocol)是从不发送空字节串,除非它位于文件末尾。 ( BodyReader 可能被称为 ChunkGetter )。 bip下面就像您所写的:提取 BodyReader 后/IO ByteString来自Response ,它执行它来获取第一个 block ,并打印它。但不会重复该操作来获得更多 - 所以在这种情况下我们只看到创世记的前几章。您需要的是一个循环来耗尽 block ,如 bop 中所示。下面,这导致整个 King James 圣经溢出到控制台中。

{-# LANGUAGE OverloadedStrings #-} 
import Network.HTTP.Client
import Network.HTTP.Client.TLS
import qualified Data.ByteString.Char8 as B

main = bip
-- main = bop

bip = do 
  manager <- newManager tlsManagerSettings
  request <- parseRequest "https://raw.githubusercontent.com/michaelt/kjv/master/kjv.txt"
  withResponse request manager $ \response -> do
      putStrLn "The status code was: "  
      print (responseStatus response)
      chunk  <- responseBody response
      B.putStrLn chunk

bop = do 
  manager <- newManager tlsManagerSettings
  request <- parseRequest "https://raw.githubusercontent.com/michaelt/kjv/master/kjv.txt"
  withResponse request manager $ \response -> do
      putStrLn "The status code was: " 
      print (responseStatus response)
      let loop = do 
            chunk <- responseBody response
            if B.null chunk 
              then return () 
              else B.putStr chunk  >> loop 
      loop

循环不断返回以获取更多 block ,直到获得一个空字符串(代表 eof),因此在终端中它会打印到 Apocalypse 的末尾。

这种行为很简单,但有点技术性。您只能使用BodyReader通过手写递归。但http-client的目的图书馆的目的是制作类似 http-conduit 的东西可能的。结果是 withResponse 类型为 Response (ConduitM i ByteString m ())ConduitM i ByteString m ()字节流的管道类型是怎样的;该字节流将包含整个文件。

原始形式为http-client/http-conduit Material ,Response包含一个像这样的导管; BodyReader部分后来被分解为 http-client因此它可以被不同的流媒体库使用,例如 pipes .

那么举个简单的例子,在对应的http素材中为 streaming streaming-bytestring 图书馆, withHTTP 给你一个 Response (ByteString IO ()) 类型的响应。 ByteString IO ()顾名思义,是 IO 中产生的字节流的类型; ByteString Identity ()相当于一个惰性字节串(实际上是一个纯粹的 block 列表)。 ByteString IO ()在这种情况下将代表直到启示录的整个字节流。因此,通过导入

 import qualified Data.ByteString.Streaming.HTTP as Bytes -- streaming-utils
 import qualified Data.ByteString.Streaming.Char8 as Bytes -- streaming-bytestring

该程序与惰性字节串程序相同:

bap = do 
    manager <- newManager tlsManagerSettings
    request <- parseRequest "https://raw.githubusercontent.com/michaelt/kjv/master/kjv.txt"
    Bytes.withHTTP request manager $ \response -> do 
        putStrLn "The status code was: "
        print (responseStatus response)
        Bytes.putStrLn $ responseBody response

事实上,它稍微简单一些,因为你没有“从 IO 中提取字节”:

        lazy_bytes <- responseStatus response
        Lazy.putStrLn lazy_bytes

但只要写

        Bytes.putStrLn $ responseBody response

您只需直接“打印”它们即可。如果您只想从 KJV 的中间查看一点,您可以使用惰性字节串执行您的操作,并以以下内容结尾:

        Bytes.putStrLn $ Bytes.take 1000 $ Bytes.drop 50000 $ responseBody response

然后你就会看到一些关于亚伯拉罕的事情。

withHTTP对于 streaming-bytestring只是隐藏了我们需要使用 BodyReader 的递归循环 Material 来自http-client直接地。这是相同的,例如与 withHTTP您可以在 pipes-http 中找到,它将字节串 block 流表示为 Producer ByteString IO () ,与 http-conduit 相同。在所有这些情况下,一旦您掌握了字节流,您就可以按照流 IO 框架的典型方式来处理它,而无需手写递归。他们都使用 BodyReader来自http-client做到这一点,这是该库的主要目的。

关于haskell - 为什么不打印强制整个惰性 IO 值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41475255/

相关文章:

haskell - 在haskell中,我如何才能将当前语言环境中的unicode字符大写

list - 列表理解 Haskell 中的意外并行语句

haskell - 如何使 Pipe 与 Haskell 的 Pipe 库并发?

haskell-pipes - 使用 haskell 管道字节串逐行迭代文件

http - 我怎么知道在我的 Haskell 程序中哪里可以抛出异常?

haskell - OCaml 或 Haskell 中的机器学习?

haskell - 多态结果类型 GADT 函数

haskell - 使用Conduit实现累加功能

haskell - 导管异常

haskell - STT导管如何吊装