【问题标题】:Why doesn't print force entire lazy IO value?为什么不打印强制整个惰性 IO 值?
【发布时间】:2017-01-05 00:02:35
【问题描述】:

我正在使用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

【问题讨论】:

  • 打印只是putStrLnshow。所以你可能应该问的是“为什么'Show'不完全评估价值?”。我怀疑一旦您查看 Show 实例以了解任何身体类型,答案就会很明显。另请注意,唯一会被强制响应的部分是正文,而不是状态或其他字段。
  • 重新阅读您的问题,您似乎希望在对另一个值 body 调用 print 时评估一个值 response。是这样吗?如果是这样,您为什么首先会期望这种行为?
  • 第一个 sn-p 中的 show (statusCode) ... 行发生了什么?
  • 这与懒惰无关,这是“简单”案例中的 Response L.ByteString 和 tls 案例中的 Response BodyReader 之间的区别。 BodyReader 不能直接打印,因为它是一个 IO 操作。但这是一个可以重复的动作,每次都会产生一个新的块。它遵循熟悉的协议,即当它“完成”时,它得到一个空字节串。在您的 tls 情况下,您只是打印第一个块,但您需要一个循环来打印结果,直到您遇到一个空块。
  • @Michael 评论是一个贫乏的空间,这可能解释了为什么您将代码放在 lpaste 中而不是内联。为什么不把它变成一个答案,在那里你有足够的空间来包含所有细节而无需链接到其他地方呢? =)

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


【解决方案1】:

这与懒惰无关,而是与 Simple 模块中的 Response L.ByteString 和 TLS 模块中的 Response BodyReader 之间的区别有关。

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

{-# 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

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

这种行为很简单,但有点技术性。您只能通过手写递归处理BodyReader。但是http-client 库的目的是使http-conduit 之类的事情成为可能。 withResponse 的结果类型为 Response (ConduitM i ByteString m ())ConduitM i ByteString m () 是字节流的管道类型;这个字节流将包含整个文件。

http-client/http-conduit 材料的原始形式中,Response 包含这样的管道; BodyReader 部分后来被分解为 http-client,因此它可以被 pipes 等不同的流媒体库使用。

举个简单的例子,在streamingstreaming-bytestring 库的相应http 材料中,withHTTP 给你一个Response (ByteString IO ()) 类型的响应。 ByteString IO () 顾名思义,是 IO 中产生的字节流的类型; ByteString Identity () 相当于一个惰性字节串(实际上是一个纯粹的块列表)。在这种情况下,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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-19
    • 1970-01-01
    • 2019-05-31
    • 1970-01-01
    • 2010-09-20
    • 1970-01-01
    • 2020-04-04
    • 1970-01-01
    相关资源
    最近更新 更多